Breaking apart some Ruby code taken from Rails - ruby-on-rails

Would someone be able to break down the Ruby specifics of what each of these statements consist of in as far as methods, parameters, block interpretations etc. This is very common to see in Rails code and I'm trying to understand how the Ruby interpreter reads this code:
respond_to do |format|
format.xml { render :layout => false }
end
In as far as I understand, respond_to is a method that's taking one parameter to it, a block. So I'm guessing it's written something like:
def respond_to(&block)
block.call
end
.. or something similar?
in the block itself, format is the object respond_to passes into the block and xml is what the request is set to, at which point it calls a block in itself if the request is asking for XML type data and goes ahead and invokes a render method, passing it a keyword based argument, :layout => false?
Would someone clean up my understanding of how they above works. This type of code is all over Rails and I'd like to understand it before using it more.

This is a typical Implementation Pattern for Internal DSLs in Ruby: you yield an object to the block which then itself accepts new method calls and blocks and thus guides the interface. (Actually, it's pretty common in Java, too, where it is used to get meaningful code completion for Internal DSLs.)
Here's an example:
def respond_to
yield FormatProxy.new(#responders ||= {})
end
class FormatProxy
def initialize(responders)
#responders = responders
end
def method_missing(msg, *args, &block)
#responders[msg] = [args, block]
end
end
Now you have a mapping of formats to executable pieces of code stored in #responders and you can call it later and in a different place, whenever, whereever and however often you want:
respond_to do |f|
f.myformat { puts 'My cool format' }
f.myotherformat { puts 'The other' }
end
#responders[:myformat].last.call # => My cool format
#responders[:myotherformat].last.call # => The other
As I hinted at above, if instead of a dumb proxy object that simply uses method_missing, you were to use one which had the most important methods (xml, html, json, rss, atom and so on) predefined, a sufficiently intelligent IDE could even give you meaningful code completion.
Note: I have absolutely no idea whether this is how it is implemented in Rails, but however it is implemented, it is probably some variation of this.

You've basically got it, and the source code is readable enough to figure out what's going on. When you want to source dive something like this, there are only a few steps needed.
1. Figure out where Rails is.
$ gem environment
RubyGems Environment:
- RUBYGEMS VERSION: 1.3.5
- RUBY VERSION: 1.8.7 (2009-06-12 patchlevel 174) [i686-darwin9.8.0]
- INSTALLATION DIRECTORY: /opt/ruby-enterprise-1.8.7-2009.10/lib/ruby/gems/1.8
...
2. Figure out where in Rails the code is.
$ cd /opt/ruby-enterprise-1.8.7-2009.10/lib/ruby/gems/1.8/gems # installation directory from above + "/gems"
$ ack "def respond_to"
...
actionpack-2.3.5/lib/action_controller/mime_responds.rb
102: def respond_to(*types, &block)
...
3. Dive in.
$ vim actionpack-2.3.5/lib/action_controller/mime_responds.rb

If you have such kind of questions or not sure how things work, the best way to find it out is to go to the source (which in Ruby is very readable).
For this particular question, you can go to mime_respond.rb. Line 187 ATM.
The comment explains:
# Here's the same action, with web-service support baked in:
#
# def index
# #people = Person.find(:all)
#
# respond_to do |format|
# format.html
# format.xml { render :xml => #people.to_xml }
# end
# end
#
# What that says is, "if the client wants HTML in response to this action, just respond as we
# would have before, but if the client wants XML, return them the list of people in XML format."
# (Rails determines the desired response format from the HTTP Accept header submitted by the client.)
Additionally respond_to takes a block OR mime types to respond with.
I would really recommend to have a look at the code there.
The comments are very comprehensive.

def respond_to(&block)
block.call
end
This is the definition of a method with one parameter. The & tells the interpreter that the parameter may also be given, when we're gonna' call the method, in the block do ... end form: respond_to do puts 1 end. This parameter can also be any object that responds to a call metod (like a Proc or a lambda):
a = lambda{ puts 1 }; respond_to(a)
respond_to do |format|
format.xml { render :layout => false }
end
This calls the respond_to method with one parameter, the do ... end block. In the implementation of this second respond_to method this block is called similar to the following:
def respond_to(&block)
block.call(#format) # or yield #format
end
so in order to conform to the 1 parameter call, our block of code must also accept 1 parameter, which in the do ... end' syntax is given between the bars|format|`

Related

Rails controller not decoding urls. Is this right?

I am making a request as
http://localhost:3000/templates/2.jpg?template_usage=0
and everything works fine.
But when I make the request as
http://localhost:3000/templates/2.jpg%3Ftemplate_usage%3D0
I receive an
ActionController::UnknownFormat - ActionController::UnknownFormat:
because the controller understands that the format is jpg?template_usage=0
Is this the expected behaviour. How could I work around this?
This is the controller code
def show
respond_to do |format|
format.html do
... return html
end
format.jpg do
... return jpg
end
end
end
This is exactly right and it's exactly the purpose of escaping characters.... if you want a ? to be passed as data not as the start of parameters you would escape it.
If for some reason you must have the escaped characters passed then you'd have to parse the format yourself but this is a code smell.
def show
my_format = params[:format].split('?').first
case my_format
when 'jpg'
...
Note that you'd also need to take any arguments passed and parse them yourself.

Rails Brakeman warning: Dynamic Render Path false alarm?

I'm just getting started with Rails, so I'm using Brakeman to learn about potential vulnerabilities in my newbie code. It's throwing a high-confidence "Dynamic Render Path" warning about the following code in my show.js.erb file:
$('#media-fragment').html('<%= escape_javascript(render(params[:partial])) %>');
I actually expected this was a problem, so no surprise there. So I changed it to the following:
# controller:
def show
if legal_partial?
#allowed_partial = params[:partial]
else
raise StandardError, "unexpected partial request: #{params[:partial]}"
end
end
private
def legal_partial?
%w(screenshots video updates).include? params[:partial]
end
# ...
# show.js.erb
$('#media-fragment').html('<%= escape_javascript(render(#allowed_partial)) %>');
Although I believe the code is now safe, Brakeman is still unhappy with this. Is there a more idiomatic way to control rendering of a partial based on user input?
Update (2/5/2016):
This has been fixed as of Brakeman 3.0.3.
If the legal_partial? method is inlined like this:
def show
if %w(screenshots video updates).include? params[:partial]
#allowed_partial = params[:partial]
else
raise StandardError, "unexpected partial request: #{params[:partial]}"
end
end
Brakeman will be able to detect the guard condition and will no longer warn about the later render call.
Original answer:
Unfortunately, Brakeman does not know that if legal_partial? is a proper guard. All it knows is that params[:partial] is assigned to #allowed_partial, and that is then passed to render.
You may be able to tell that #allowed_partial will always be a safe value. At that point, you have to consider whether or not it makes sense to add complexity in order to make a tool happy.
Just as an example, you could do this:
def show
render_allowed_partial params[:partial]
end
def render_allowed_partial name
if %w(screenshots video updates).include? name
#allowed_partial = name
else
raise StandardError, "unexpected partial request: #{params[:partial]}"
end
end
It's basically the same thing, except now you are hiding the assignment of #allowed_partial from Brakeman.
(Warning: Not necessarily "best" way of doing this.)
Using brakeman 4.2.0
I had a similar issue trying to render a specific hand-positioned-and-named template. Every product of my app required that specific named template. The template name came from the controller params as params[:a_particular_slug].underscore.
I solved with something like this:
def show
if #products = Product.where(a_slug: params[:a_particular_slug])
render template: lookup_context.find(params[:a_particular_slug].underscore, ["featured_products"])
else
render_404
end
end
Here I'm looking for a template. If you need to use a partial, be aware that lookup_context.find third params set to true allows to search for partials.
You can find more about lookup_context.find here
Hope this helps.

Define timer method controller rails

I have a method in controller that calls another method created in a module like this example:
def example
#var1 = ModuleName::ClassName.get()
respond_to do |format|
format.json { render json: #var1}
end
end
The method get() goes to a website looking for information and returns an array.
Everything works perfectly, but I wonder if in the controller there is a way to set a timeout if the application takes a long time to run! Is it possible?
here is one way (a more general way) you could do that..
def example
Timeout::timeout(40) do # 40 sec, change it to anything you like
#var1 = ModuleName::ClassName.get()
rescue Timeout::error
# do something (maybe set #var1's value if it couldn't get desired array)
end
respond_to do |format|
format.json { render json: #var1}
end
end
If under ModuleName::ClassName.get() you imply some kind of third-party ruby http library, then it's likely that you should set some kind of timeout parameter (depends on library). You just pass a desired timeout in seconds (or whatever measurement you want).
Thus the pseudo-code might look like this:
ModuleName::ClassName.get(10)
For more detailed answer, can you please be more specific about how are you doing a call to external service?

Backbone.js and Rails - How to handle params from Backbone models?

In a standard Rails controller, I'd create a record like this:
#user = User.new(params[:user])
This assumes that the form parameters that come in are nested.
I've been playing with Backbone.js and I noticed that by default, Backbone doesn't nest the parameters the way a normal Rails form might, which is actually something I expected. So I'm wondering what I should do...
Do I
figure out on the server-side if it's a request from Backbone by looking at accepts headers, etc and manipulate the params myself so I can keep my controller code small:
do_some_params_manipulation_with(params)
#user = User.new(params[:user])
respond_to do |format|
if #user.save
format.html {redirect_to users_url}
format.json {render :json => #user.to_json }
end
end
Or, do I instantiate the object in each branch which ends up with repeated code but might be more maintainable in the long run....
respond_to do |format|
format.html do
#user = User.new(params[:user])
if #user.save
redirect_to users_url
end
end
format.json do
#user = User.new(params) # and rely on mass-assignment protection
if #user.save
render :json => #user.to_json
end
end
end
or do I modify my Backbone.js models by overriding the .toJSON method (which I'm not entirely sure how to do because I don't know enough about Backbone.js yet) so that it nests the params?
In this situation, I have access to both sides of the app, but I am interested in what others are doing.
It's nice when you can have the general Rails forms and Backbone forms match with respect to the root node. That's why in my last application I chose to override the Backbone models' toJSON method.
You could override the global toJSON method as Raimonds Simanovskis suggested. But even the non-DRY way approach isn't so bad. Just one line of boilerplate for each model definition:
// Depends on Underscore.js
User = Backbone.Model.extend({
toJSON: function() {
return { user: _.clone( this.attributes ) }
},
// Your other methods here
});
Edit: Corrected code sample. Sorry for the errors, I was translating from CoffeeScript to JavaScript.
If you are using the backbone-rails gem, looks like you can do
var User = Backbone.Model.extend({
paramRoot: 'user'
});
Around line 45 on github
Credit PL J and stream 7 on this link
I have made a little hack to namespace save requests under model.name property.
It monkey patches toJSON() during sync() call only and restores original method so you can use it as usual.
I have implemented it in CoffeeScript.
Check it here
It should be noted that if you opt for the currently accepted answer (patching toJSON at the model level) you are affecting reading as well. Maybe that goes without saying, maybe not. But you will have a lot of work to do when rendering models/collections if you put this patch into affect in a backbone app. Therefore, I personally wouldn't use it as-is.
In one of the answers to Rails mass assignment and Backbone.js there is mentioned patch https://gist.github.com/719080 which I think will do what you need.
As of Rails 3.1 there's now a new initializer called wrap_parameters.rb which handles this problem by default. The code that handle this case is:
# Disable root element in JSON by default.
ActiveSupport.on_load(:active_record) do
self.include_root_in_json = false
end
Bada bing!

Loading a page into memory in Rails

My rails app produces XML when I load /reports/generate_report.
On a separate page, I want to read this XML into a variable and save it to the database.
How can I do this? Can I somehow stream the response from the /reports/generate_report.xml URI into a variable? Or is there a better way to do it since the XML is produced by the same web app?
Here is my generate_report action:
class ReportsController < ApplicationController
def generate_report
respond_to do |format|
#products = Product.all
format.xml { render :layout => false }
end
end
end
Here is the action I am trying to write:
class AnotherController < ApplicationController
def archive_current
#output = # get XML output produced by /reports/generate_report
# save #output to the database
respond_to do |format|
format.html # inform the user of success or failure
end
end
end
Solved: My solution (thanks to Mladen Jablanović):
#output = render_to_string(:file => 'reports/generate_report.xml.builder')
I used the following code in a model class to accomplish the same task since render_to_string is (idiotically) a protected method of ActionController::Base:
av = ActionView::Base.new(Rails::Configuration.new.view_path)
#output = av.render(:file => "reports/generate_report.xml.builder")
Perhaps you could extract your XML rendering logic to a separate method within the same controller (probably a private one), which would render the XML to a string using render_to_string, and call it both from generate_report and archive_current actions.
What I typically do in this type of situation is to create a separate module/class/model to generate the report (it could even potentially be right in the Product model). This separate component could be in app/models or it could be in lib. In any case, once you have it extracted you can use it anywhere you need it. The controller can call it directly. You can generate it from the console. You can have a cron job generate it. This is not only more flexible, but it also can help smooth out your request response times if the report becomes slow to generate.
Since you are using a template it's understandable that the controller route is convenient, but even if you have to include some kind of ruby templating system in your auxiliary lib, it's still probably going to be less hassle and more flexible then trying to go through the controller.
#output = Product.all.to_xml
I'm sorry, is you question about Xml or about sessions? I mean is the fact that your action generates Xml material to the question? Or do you just want to save the output of the action for latter use?
You said on a "separate" page - you mean on another request? (like after user approved it?)
Why do you want to save the output? Because it should be saved exactly as rendered? (for example user can get frustrated if he clicked to save one report and you saved another)
Or is this thing expensive to generate?
Or may be, I got it wrong and it's about refactoring?

Resources