Making dynamic scope helpers in Rails - ruby-on-rails

Several of my partials can be rendered in two "modes". If full_display is false, I don't render several fields. To make things easy I wanted to make one of the "modes" default - if full_display is not defined, treat it as false. I came up with this code:
(((not defined?(full_display).nil?) && full_display) || false)
Quite a lot to move around. It would be nice to put it inside a helper or something, but since Ruby has only lexical scope I can't think of any good way to do it.
Bad ideas that I've already tried:
on the top of the partial do <% display = long code that is above %> and use display through the code, but creating local variables in a view looks bad and has to be copied into every partial using full_display.
wrap it inside a string, put it into a helper and use eval(display_helper) in view, but obviously this creates security concerns.

That's quite a convoluted way of saying something as simple as:
defined?(full_display) && full_display
In Ruby there are two values that are non-true, nil and false, all others evaluate as true, which includes 0, empty string, among others, that would otherwise evaluate as false in Perl, PHP, and C. Testing with .nil? is usually reserved for those rare cases where you want to differentiate between false and undefined, and this is sometimes the case with boolean fields where a missing value is different from a false value.
In any case, in the view space it is a lot easier to assign defaults using the or-equals operator ||= like this:
<% full_display ||= false %>
That is equivalent to full_display = full_display || false. This does two things. First, it defines the full_display variable even if it was not previously created, and secondly it ensures that the it will contain a value that is at least false, never undefined.
You will see the ||= default pattern a lot in Ruby code as it's an easy way to assign something in the case where it will be nil otherwise.

Related

What is the purpose of nil? and exists? methods in ruby/rails conditions?

I often (very often) see code like this in the conditions:
<% catalog.each do |article| %>
<% if article.image.nil? %>
...
or (e.g. seen there)
<% catalog.each do |article| %>
<% if article.image.exists? %>
...
However we all know that nil interprets like false in the conditions. ActiveRecord query returns nil if nothing found.
Why not simply write:
<% unless article.image %>
(unless there is article do something)
instead of
<% if article.image.nil? %>
(if there is nothing at article.image do something)
and
<% if article.image %> instead of <% if article.image.exists? %>
I usually write the code without nil? and exists?. What am I missing? Is there any pitfalls?
In your example, and in many typical RESTful Rails patterns, you're correct that using the implicit version is identical in behavior.
But there are certainly suations where nil? and exists? are necessary and/or more readable...
For instance, nil? is the only option when you're reading a boolean attribute, and you need different behavior for false vs nil (since both are falsey in Ruby).
Or, assume in your example, that each Article has many images, and you define a method to get an Article's first image according to display order:
def primary_image
images.order(display_order: "ASC").limit(1)
end
In this case, if your Article doesn't have any images, primary_image will return an empty collection, which is truthy. So, in this case, you'd need to use exists? (or present?). Rails provides several convenience methods for checking data records.
(or make sure the primary_image method returns nil or false when the collection is empty)
Personally, I rarely use either one of them. Like you suggested, I mostly just use if or unless depends on the situation without bothering with nil? or exists?
As for the difference between the two:
nil? is a Ruby method to see whether the object is nil or not.
exists? is a Rails method to see whether the record exists in the database
I guess, just use whichever is more efficient. I have been trying to convert my codes to use try to avoid no method error.
Most of the time it's just to be more explicit I suppose.
In your example above it doesn't matter if you check explicitly or implicitly.
But if you want to check explicitly for nil for example, you use the #nil? because sometimes you want to react differently if false is returned.
As an example: false and nil are often not interchangeable for boolean values in a database.
Like #lusketeer already said is nil? a method of the ruby standard library and exists? a method of the Rails framework.
But I think almost always you are good with the implicit way.

Is there any way to define a model's attribute as always html_safe?

I have a model called Feature with a variable called body_string, which contains HTML markup I'd like to render, rather than escape.
Every time I reference body_string in my views, I need to use <%=raw or .html_safe. This seems redundant and not-so-DRY.
Is there any way that I can establish once-and-for-all the body_string variable as html_safe?
I'm assuming this would happen in the app/models/feature.rb file, but I can't figure out what the right syntax would be, exactly. I've thought of this:
def body_string
return self.body_string.html_safe
end
But Rails doesn't like it; it raises a stack level too deep exception.
Naturally I could define a variable/method with a different name:
def safe_body_string
return self.body_string.html_safe
end
And then just change all references in the views from body_string to safe_body_string. But somehow this seems almost as un-DRY as simply using raw or .html_safe in the first place.
Any insights to how best to handle this? I feel like there must be something really elegant that I'm just not seeing.
Just use read_attribute to avoid the recursive call to body_string:
def body_string
read_attribute(:body_string).html_safe
end
read_attribute is complemented by write_attribute for setting attributes from within your model.
A note on style: Don't use explicit returns unless you actually need them. The result of the last statement in a method is implicitly the value returned from the method.
While #meager's answer will definitely work, I don't think this logic belongs in a model. Simply because it adds view-level concerns (HTML safeness) to the model layer, which should just include business logic. Instead, I would recommend using a Presenter for this (see http://nithinbekal.com/posts/rails-presenters/ or find a gem for this -- I personally love Display Case). Your presenter can easily override the body_string method and provide the .html_safe designation when displaying in the view. This way you separate your concerns and can continue to get body_string from other models without mixing in the view concern.
Maybe this gem is useful for you. I also wanted to stop repeating html_safe all the time when the content is completely trustable.
http://rubygems.org/gems/html_safe_attribute
Or you can also use this approach,
def body_string
super && super.html_safe
end

Is it possible to define a constant in an eRuby template in Rails

Since I hate calling methods with boolean literals that don't say anything about their intent I tried defining a constant in my eruby template as follows: <% NO_NIL_PADDING = false %> for a call to in_groups_of. However this resulted in a dynamic constant assignment error. Any way to get around this? I could probably define the constant in the environment somewhere, but atm I'd prefer to keep the constant definition as close to its usage as possible until it starts getting necessary to move its definition to a higher level.
I don't know what else you're doing in the template that might be causing it, but just setting the constant works for me:
ruby-1.8.7-p249 > template = ERB.new "<% DONT_SET_CONSTANTS_IN_VIEWS = false %>"
=> #<ERB:0x100485890 #src="_erbout = ''; DONT_SET_CONSTANTS_IN_VIEWS = false ; _erbout", #filename=nil, #safe_level=nil>
ruby-1.8.7-p249 > template.result(binding)
=> ""
on a side note, however, I'd strongly recommend not doing this. As someone who's inherited a lot of code in the last year, you're making serious headaches for yourself and potentially someone else. Is an option like that actually likely to change per business logic? I would try to pick a sane default and just use it. If its not being used anywhere else, why create a constant?

Avoiding nil in Rails views

I'm sure this has been asked already, but I can't find the answer.
I have a Project model, which has a belongs_to relationship with my Client model. A client has a name, but a project doesn't necessarily have a client.
In my view, I've got code like this:
<%=h project.client && project.client.name %>
because if the project doesn't have a client then trying to access project.client.name causes a NoMethodError (nil doesn't have a method called name).
The question is, is it acceptable to have this kind of nil checking in the view, or should I be looking for another way around it?
Just use
project.client.try(:name)
I think its perfectly acceptable - this is view logic, you are more or less deciding whether or not to show portions of your view, based on whether there is data.
I run into this all the time, and yes it's annoying. Even when there is supposed to never be a nil, dirty data that I inherited sometimes triggers it.
Your solution is one way of handling it. You could also add a method to Project called client_name that displays the client name if it exists, but then you are linking the models together more than some people recommend.
def client_name
client && client.name
end
You could also make a helper method to do it, but you can end up writing a lot of them. :)
As mentioned by Skilldrick below, this is also useful to add a default string:
def client_name
client ? client.name : "no client"
end
You can use delegate in your Project class, so this way you will respect the Law of demeter which says that you should "talk only to your immediate friends".
project.rb
class Project
delegate :name, to: :client, prefix: true, allow_nil: true
end
So this way the project object will know where to ask about the client's name:
#You can now call
project.client_name
See more about delegate in the Rails documentation.
my hacky solution is to yield a block and rescue the error. Many would say using rescue as logic is very bad form. Just don't use this where you would actually need to know when something is nil and shouldn't be.
In application_helper.rb:
def none_on_fail
begin
return yield
rescue
return "(none entered)"
end
end
Then in the view:
<%= none_on_fail { project.client.name } %>
Then methods can be chained as deep as needed and it can be used on any method BUT it will cover up other potential problems with models/relationships/methods if they exist. I would equate it to taking out a splinter with a flamethrower. Very effective with painful consequences if used improperly.
I think these checks can usually be eliminated with a bit of thought. This has the benefit of keeping your view code cleaner, and more importantly, keeping logic out of the view layer, which is a best practice. Some templating engines don't allow any logic in the view.
There are at least a couple of scenarios. Let's say you have a show action that depends on an instance variable. I'd say if the record is not found the controller should not render the html, by redirecting or something else. If you have a loop in the view for an array, use #array.each do |a| end so that it doesn't evaluate if the array is empty. If you truly want an application default in the view, try loading it from a config file, e.g. #page_title || #{#APP_CONFIG['page_title']} (see Railscasts #85). Remember you may want to change these strings later, for example translating the UI.
Those are a couple scenarios where presence checks and usage of try can be avoided. I'd try to avoid them if possible. If you can't avoid them, I'd put the conditional checks in a view helper and add a helper unit test for it to verify (and document) both code paths.

defined? method in Ruby and Rails

I have a quite old templating system written on top of ERB. It relies on ERB templates stored in database. Those are read and rendered. When I want to pass data from one template to another I use the :locals parameter to Rails render method. For setting default variables of those variables in some templates I use the defined? method which simply tells me if local variable has been defined and if not I initialize it with default value like this:
unless defined?(perex)
perex = true
end
I am upgrading the app to latest Rails and I see some weird behavior. Basically this sometimes works (sometimes perex is undefined) and sometimes it does not (perex is defined and set to nil). This happens without anything else changing.
I have two questions:
Is there any better way other than using defined? which is proving unreliable (was reliable for several years on top Rails 1.6)? Such a way should not result in me rewriting all the templates.
I have been going through Ruby docs and was not able to find anything about defined? method. Was it deprecated or am I just plain blind?
Edit: The actual issue was caused by what seems to be a Ruby/eRB bug. Sometimes the unless statement would work, but sometimes not. The weird thing is that even if the second line got executed perex stil stayed nil to the rest of the world. Removing defined? resolved that.
First: actually, defined? is an operator.
Second: if I understand your question correctly, the way to do it is with this Ruby idiom:
perex ||= true
That'll assign true to perex if it's undefined or nil. It's not exactly what your example does, since yours doesn't evaluate the assignment when the value is nil, but if you are relying on that then, in my opinion, without seeing it, you're not writing clear code.
Edit: As Honza noted, the statement above will replace the value of perex when it's false. Then I propose the following to rewrite the minimum number of lines:
perex ||= perex.nil? # Assign true only when perex is undefined or nil
The safest way of testing if a local is defined in a Rails template is:
local_assigns[:perex]
This is documented in the Rails API together with the explanation that defined? cannot be used because of a implementation restriction.
Per mislav's answer, I went looking for that documentation in the Rails API, and found it in Class ActionView::Base (under the heading "Passing local variables to sub templates"). It was hardly worth the search, though, since it barely said anything more than mislav did. Except that it recommends this pattern:
if local_assigns.has_key? :perex
Taking into considerationg mislav's original answer and KenB's elaboration, I think the following is the absolute best approach (though I'm open to opinion). It utilizes Ruby's Hash#fetch method to fallback on an alternate value if the key does not exist in the original hash.
perex = local_assigns.fetch(:perex, true)
This is even better than the ||= method that most users will suggest since sometimes you will want to allow false values. For example, the following code will never allow a false value to be passed in:
perex = local_assigns[:perex] || true

Resources