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
Making a Soap Request by Using Xml in Rails
Using Will_Paginate Without :Total_Entries to Improve a Lengthy Query
How Rails Delegate Method Works
Actiondispatch::Http::Uploadedfile.Content_Type Not Being Initialized in Rspec Test
Best Way to Work with Large Amounts of CSV Data Quickly
Datetime with Mongodb/Mongoid and Rails 3 Not Populating
What Are Tainted Objects, and When Should We Untaint Them
Parsing JSON Without Quoted Keys
How to Transfer Files Using Ssh and Scp Using Ruby Calls
How to Timeout Flash Messages in Rails
Using Activerecord Interface for Models Backed by External API in Ruby on Rails
How to Ignore or Skip a Test Method Using Rspec
Connecting to Web Services Using Rails (Http Requests)
Facebook Open Graph from Rails Heroku
Rvm VS Native Installation of Ruby
Ruby on Rails Uncapitalize First Letter