Ruby W/ Sinatra: What Is the Equivalent of a .Js.Erb from Rails

Ruby w/ Sinatra: what is the equivalent of a .js.erb from rails?

Based on your description, I'm guessing that your desire is to have portions of a page editable and replaced via AJAX. If this is wrong, please clarify.

I do this in my Sinatra apps by including (my own) AJAXFetch jQuery library and writing code as shown below. This lets me use the partial both when rendering the page initially as well as when editing via AJAX, for maximum DRYness. The AJAXFetch library handles all AJAX fetch/swap through markup alone, without needing to write custom JS on the pages that use it.

helpers/partials.rb

require 'sinatra/base'
module Sinatra
module PartialPartials
ENV_PATHS = %w[ REQUEST_PATH PATH_INFO REQUEST_URI ]
def spoof_request( uri, headers=nil )
new_env = env.dup
ENV_PATHS.each{ |k| new_env[k] = uri.to_s }
new_env.merge!(headers) if headers
call( new_env ).last.join
end
def partial( page, variables={} )
haml page, {layout:false}, variables
end
end
helpers PartialPartials
end

routes/bug.rb

get '/bug/:bug_id' do
if @bug = Bug[params[:bug_id]]
# ...
haml :bug
end
end

# Generate routes for each known partial
partials = %w[ bugdescription bughistory bugtitle fixer
pain project relatedbugs status tags version votes ]
partials.each do |part|
[ part, "#{part}_edit" ].each do |name|
get "/partial/#{name}/:bug_id" do
id = params[:bug_id]
login_required
halt 404, "(no bug ##{id})" unless @bug = Bug[id]
partial :"_#{name}"
end
end
end

post "/update_bug/:partial" do
id = params[:bug_id]
unless params['cancel']=='cancel'
# (update the bug based on fields)
@bug.save
end
spoof_request "/partial/#{params[:partial]}/#{id}", 'REQUEST_METHOD'=>'GET'
end

views/bug.haml

#main
#bug.section
= partial :_bugtitle
.section-body
= partial :_bugdescription
<!-- many more partials used -->

views/_bugtitle.haml

%h1.ajaxfetch-andswap.editable(href="/partial/bugtitle_edit/#{@bug.pk}")= title

views/_bugtitle_edit.haml

%form.ajaxfetch-andswap(method='post' action='/update_bug/bugtitle')
%input(type="hidden" name="bug_id" value="#{@bug.id}")
%h1
%input(type="text" name="name" value="#{h @bug.name}")
%span.edit-buttons
%button(type="submit") update
%button(type="submit" name="cancel" value="cancel") cancel

Ruby w/ Sinatra: Could I have an example of a jQuery AJAX request?

See my answer to your other recent question for a comprehensive setup for sending and receiving HTML partials in a production Sinatra app.

As Sinatra is a nice lightweight framework, you are free (forced?) to come up with your own workflow and code for implementing partials and handling such calls. Instead of my explicit route-per-partial, you might choose to define a single regex-based route that looks up the correct data based on the URL or param passed.

In general, if you want Sinatra to respond to a path, you need to add a route. So:

get "/new_game" do
# This block should return a string, either directly,
# by calling haml(:foo), erb(:foo), or such to render a template,
# or perhaps by calling ...to_json on some object.
end

If you want to return a partial without a layout and you're using a view, be sure to pass layout:false as an option to the helper. For example:

get "/new_game" do
# Will render views/new_game.erb to a string
erb :new_game, :layout => false
end

If you want to return a JSON response, you should set the appropriate header data:

get "/new_game" do
content_type :json
{ :foo => "bar" }.to_json
end

If you really want to return raw JavaScript code from your handler and then execute that...well, here's how you return the JS:

get "/new_game" do
content_type 'text/javascript'
# Turns views/new_game.erb into a string
erb :new_game, :layout => false
end

It's up to you to receive the JS and *shudder* eval() it.

How to call js and css files inside a html.erb file

You can include the JavaScript in the .html.erb file in the same way you load the text file. The simplest (code wise) solution is doing something along the lines of this:

plan.html.erb

<script>
<%= File.read('some/file.js') %>
</script>

However if you are expecting a <script src="some/file.js"></script> as result you'll have to create your own helper or use an existing one from some light weight web framework. A simple example might be:

lib/html_helpers.rb

require 'builder'

module HtmlHelpers

def javascript_include_tag(path)
Builder::XmlMarkup.new.script('', src: path)
#=> %{<script src="#{html_escaped_path}"></script>}
end

end

plan.html.erb

<% require 'html_helpers' %>
<% include HtmlHelpers %>
<%= javascript_include_tag('some/file.js') %>

Keep in mind that the first solution doesn't escape any HTML characters. Meaning that if your script contains </script> everything following that tag will be interpreted as HTML.

Rails Ajax - Sinatra - Amazon API and back

It seems that you've faced with ajax 'cross-domain security' problem. Try to use JSONP (JSON with padding).

Change your sinatra get handler:

get '/' do  
req = AmazonProduct["us"]
req.configure do |c|
c.key = KEY
c.secret = SECRET
c.tag = TAG
end
req << { :operation => 'ItemSearch',
:search_index => "DVD",
:response_group => %w{ItemAttributes Images},
:keywords => "nikita",
:sort => "" }
resp = req.get
@item = resp.find('Item').shuffle.first

content_type :json
callback = params.delete('callback') # jsonp
json = @item.to_json

if callback
content_type :js
response = "#{callback}(#{json})"
else
content_type :json
response = json
end
response
end

And change your Ajax request:

$.getJSON("http://address_of_sinatra?callback=?",
function(data) {
console.log(data);
});

Or you can add dataType: 'jsonp' to your $.ajax request.
After that you should see data object in js debugger (at least it's working in my case :D )

MVC pattern for sinatra frame work

Sinatra is a lightweight library, that aims to stay out of your way, leaving the door open for you to include or create what you need per project.

That said, you can create your own MVC on top of Sinatra rather easily, and incorporate ActiveRecord, DataMapper, Sequel, etc... for your Models. Here's a sample structure -

├── Gemfile
├── README.md
├── app
│ ├── controllers
│ │ └── application_controller.rb
│ ├── models
│ │ └── model.rb
│ └── views
│ └── index.erb
├── config
│ └── environment.rb
├── config.ru
├── public
│ └── stylesheets
└── spec
├── controllers
├── features
├── models
└── spec_helper.rb

Gemfile - where all your gems go.

App Directory - folder for MVC directories - models, views, and controllers.

Models Directory - holds the logic behind your application.

Controllers Directory - where the application configurations, routes, and controller actions are implemented.

Views Directory - holds the code that will be displayed in the browser.

config.ru - the config.ru file is necessary when building Rack-based applications and using rackup/shotgun to start the application server (the ru stands for rackup).

Config Directory - w/ environment.rb fileto connect up all the files in your application to the appropriate gems and to each other.

Public Directory - holds your front-end assets - CSS / JS / Images, etc...

Spec Directory - contains any tests for your applications.

Accessing a variable within a rails thread

UPDATED EDIT AT END: Shows working code. Main module unmodified except for debugging code. Note: I did experience the issue I already noted regarding the need to unsubscribe prior to termination.

The code looks correct. I'd like to see how you are instantiating it.

In config/application.rb, you probably have at least something like:

require 'ws_communication'
config.middleware.use WsCommunication

Then, in your JavaScript client, you should have something like this:

var ws = new WebSocket(uri);

Do you instantiate another instance of WsCommunication? That would set @clients to an empty array and could exhibit your symptoms. Something like this would be incorrect:

var ws = new WsCommunication;

It would help us if you would show the client and, perhaps, config/application.rb if this post does not help.

By the way, I agree with the comment that @clients should be protected by a mutex on any update, if not reads as well. It's a dynamic structure that could change at any time in an event-driven system. redis-mutex is a good option. (Hope that link is correct as Github seems to be throwing 500 errors on everything at the moment.)

You might also note that $redis.publish returns an integer value of the number of clients that received the message.

Finally, you might find that you need to ensure that your channel is unsubscribed before termination. I've had situations where I've ended up sending each message multiple, even many, times because of earlier subscriptions to the same channel that weren't cleaned up. Since you are subscribing to the channel within a thread, you will need to unsubscribe within that same thread or the process will just "hang" waiting for the right thread to magically appear. I handle that situation by setting an "unsubscribe" flag and then sending a message. Then, within the on.message block, I test for the unsubscribe flag and issue the unsubscribe there.

The module you provided, with only minor debugging modifications:

require 'faye/websocket'
require 'redis'

class WsCommunication
KEEPALIVE_TIME = 15 #seconds
CHANNEL = 'vip-deck'

def initialize(app)
@app = app
@clients = []
uri = URI.parse(ENV['REDISCLOUD_URL'])
$redis = Redis.new(host: uri.host, port: uri.port, password: uri.password)
Thread.new do
redis_sub = Redis.new(host: uri.host, port: uri.port, password: uri.password)
redis_sub.subscribe(CHANNEL) do |on|
on.message do |channel, msg|
puts "Message event. Clients receiving:#{@clients.count};"
@clients.each { |ws| ws.send(msg) }
end
end
end
end

def call(env)
if Faye::WebSocket.websocket?(env)
ws = Faye::WebSocket.new(env, nil, {ping: KEEPALIVE_TIME})

ws.on :open do |event|
@clients << ws
puts "Open event. Clients open:#{@clients.count};"
end

ws.on :message do |event|
receivers = $redis.publish(CHANNEL, event.data)
puts "Message published:#{event.data}; Receivers:#{receivers};"
end

ws.on :close do |event|
@clients.delete(ws)
puts "Close event. Clients open:#{@clients.count};"
ws = nil
end

ws.rack_response
else
@app.call(env)
end
end
end

The test subscriber code I provided:

# encoding: UTF-8
puts "Starting client-subscriber.rb"
$:.unshift File.expand_path '../lib', File.dirname(__FILE__)
require 'rubygems'
require 'eventmachine'
require 'websocket-client-simple'

puts "websocket-client-simple v#{WebSocket::Client::Simple::VERSION}"

url = ARGV.shift || 'ws://localhost:3000'

EM.run do

ws = WebSocket::Client::Simple.connect url

ws.on :message do |msg|
puts msg
end

ws.on :open do
puts "-- Subscriber open (#{ws.url})"
end

ws.on :close do |e|
puts "-- Subscriber close (#{e.inspect})"
exit 1
end

ws.on :error do |e|
puts "-- Subscriber error (#{e.inspect})"
end

end

The test publisher code I provided. Publisher and Subscriber could easily be combined, as these are just tests:

# encoding: UTF-8
puts "Starting client-publisher.rb"
$:.unshift File.expand_path '../lib', File.dirname(__FILE__)
require 'rubygems'
require 'eventmachine'
require 'json'
require 'websocket-client-simple'

puts "websocket-client-simple v#{WebSocket::Client::Simple::VERSION}"

url = ARGV.shift || 'ws://localhost:3000'

EM.run do
count ||= 0
timer = EventMachine.add_periodic_timer(5+rand(5)) do
count += 1
send({"MESSAGE": "COUNT:#{count};"})
end

@ws = WebSocket::Client::Simple.connect url

@ws.on :message do |msg|
puts msg
end

@ws.on :open do
puts "-- Publisher open"
end

@ws.on :close do |e|
puts "-- Publisher close (#{e.inspect})"
exit 1
end

@ws.on :error do |e|
puts "-- Publisher error (#{e.inspect})"
@ws.close
end

def self.send message
payload = message.is_a?(Hash) ? message : {payload: message}
@ws.send(payload.to_json)
end
end

A sample config.ru which runs all this at the rack middleware layer:

require './controllers/main'
require './middlewares/ws_communication'
use WsCommunication
run Main.new

This is Main. I stripped it down out of my running version so it might need tweaked if you use it:

%w(rubygems bundler sinatra/base json erb).each { |m| require m }
ENV['RACK_ENV'] ||= 'development'
Bundler.require
$: << File.expand_path('../', __FILE__)
$: << File.expand_path('../lib', __FILE__)

Dir["./lib/*.rb", "./lib/**/*.rb"].each { |file| require file }
env = ENV['OS'] == 'Windows_NT' ? 'development' : ENV['RACK_ENV']

class Main < Sinatra::Base

env = ENV['OS'] == 'Windows_NT' ? 'development' : ENV['RACK_ENV']
get "/" do
erb :"index.html"
end

get "/assets/js/application.js" do
content_type :js
@scheme = env == "production" ? "wss://" : "ws://"
erb :"application.js"
end
end

Render haml with locals and partials within partials to html file

I found other solution to do this.
Instead of using Haml::Engine, once we are already in rails and we can use render itself, i created a to_html function in the model and include the application helper to get the helper_methods that i used in the partial. Then used the render to print the result to a file:

def to_html
ActionView::Base.send :include, ActionView::Helpers::ApplicationHelper

File.open(File.join(Rails.root, "public", 'test.html'), "w") do |file|

file.print ActionView::Base.new(Rails.configuration.paths["app/views"].first).render(
:partial => 'metric_box_sets/metric_box_set',
:format => :html,
:locals => { :metric_box_set => self}
)
end
end

This way i can generate all html snippets that i needed based on the views that i already done.



Related Topics



Leave a reply



Submit