At about the 19:00 mark in his RailsConf presentation, David Heinemeier Hansson talks about the downsides of instance_eval:
For a long time I ranted and raved
against instance_eval, which is the
concept of not using a yielded
parameter (like do |people|) and
just straight do something and then
evaluate what's in that block within
the scope of where you came from (I
don't even know if that's a coherent
explanation)
For a long time I didn't like that
because it felt more complex in some
sense. If you wanted to put your own
code in there were you going to
trigger something that was already
there? Were you going to override
something? When you're yielding a
specific variable you can chain
everything off that and you can know
[you're] not messing with anybody
else's stuff
This sounded interesting, but a) I don't know how how instance_eval works in the first place and b) I don't understand why it can be bad / increase complexity.
Can someone explain?
The thing that instance_eval does is that it runs the block in the context of a different instance. In other words, it changes the meaning of self which means it changes the meaning of instance methods and instance variables.
This creates a cognitive disconnect: the context in which the block runs is not the context in which it appears on the screen.
Let me demonstrate that with a slight variation of #Matt Briggs's example. Let's say we're building an email instead of a form:
def mail
builder = MailBuilder.new
yield builder
# executed after the block
# do stuff with builder
end
mail do |f|
f.subject #subject
f.name name
end
In this case, #subject is an instance variable of your object and name is a method of your class. You can use nice object-oriented decomposition and store your subject in a variable.
def mail &block
builder = MailBuilder.new
builder.instance_eval &block
# do stuff with builder
end
mail do
subject #subject
name name # Huh?!?
end
In this case, #subject is an instance variable of the mail builder object! It might not even exist! (Or even worse, it might exist and contain some completely stupid value.) There is no way for you to get access to your object's instance variables. And how do you even call the name method of your object? Everytime you try to call it, you get the mail builder's method.
Basically, instance_eval makes it hard to use your own code inside the DSL code. So, it should really only be used in cases where there is very little chance that this might be needed.
Ok, so the idea here is instead of something like this
form_for #obj do |f|
f.text_field :field
end
you get something like this
form_for #obj do
text_field :field
end
the first way is pretty straight forward, you end up with a pattern that looks like this
def form_for
b = FormBuilder.new
yield b
b.fields.each |f|
# do stuff
end
end
you yield out a builder object that the consumer calls methods on, and afterwards you call methods on the builder object to actually build the form (or whatever)
the second one is a bit more magical
def form_for &block
b = FormBuilder.new
b.instance_eval &block
b.fields.each |f|
#do stuff
end
end
in this one, instead of yielding the builder to the block, we take the block and evaluate it in the context of the builder
The second one increases complexity because you are sort of playing games with scope, you need to understand that, and the consumer needs to understand that, and whoever wrote your builder needs to understand that. If everyone is on the same page, I don't know that it is nessicarily a bad thing, but i do question the benefits vs the costs, i mean, how hard is it to just tack on an f. in front of your methods?
The idea is that it's a little dangerous in that you can never be quite sure you're not going to break something without reading all the code that deals with the object your using instance_eval on.
Also if you , say, updated a library that didn't change the interface much but changed a lot of the object internals you could really do some damage.
Related
Caching is by far the most logic-intensive part of my view code, so I would like to do fragment caching from inside a decorator, however, I cant do it.
When i do this from my decorator:
def cached_name
h.cache do
"a name here"
end
end
I get this:
You have a nil object when you didn't expect it! You might have
expected an instance of Array. The error occurred while evaluating
nil.length
I instantiate my decorator from inside a controller
#presenter = SomePresenter::new
I am using HAML for my views
How can I succesfully cache from inside my decorator, so my view can do stuff like this
= #decorator.cached_logic_heavy_stuff
UPDATE: I have created a git repo showing my issue: https://github.com/houen/presenter_caching
UPDATE: This maybe works - see the repo
include Haml::Helpers
def another_way_to_try
self.init_haml_helpers
buffer = haml_buffer.buffer
h.with_output_buffer(buffer) do
h.cache do
h.concat "i should still not be empty"
end
end
end
I'd suggest using Rails.cache directly might solve your problem; we do the same thing in our decorators with Rails 4.
def cached_name
Rails.cache.fetch(source) do
source.name # etc.
end
end
If you're using Draper, I believe you don't need to explicitly pass the view context. You will likely want to pass a model or collection to your draper present when you instantiate. Examples:
class UserDecorator < Draper::Base
decorates :user
# additional methods
end
# in the controller
#presenter = UserDecorator.new(#user) # for an instance
#presenter = UserDecorator.decorate(#users) # for a collection
I suspect the nil object error you're getting is coming from another method call that's not listed in your code.
As for fragment caching from your decorator, you'll want to use the concat helper method to get this to work inside the decorator:
# your decorator class
def cached_name
h.cache("some_cache_key") do
h.concat "a name here"
end
end
Rails' cache method tries to infer a cache key based on the view that it's being called from. Since you're not actually calling it from a view (but from inside an instance of a decorator class), I expect that it's bombing when trying to build a cache key.
You might try passing a cache key explicitly, via h.cache "your cache key" do. With a full stack trace, you can figure out where it's throwing the exception, and then work around that, as well. Without the full stack trace, it's harder to help you, though.
Edit: Looking at Rails' caching code, I think this might be a deeper issue; it's attempting to get the length of output_buffer, which isn't going to be available outside of your views' contexts (that is, within Draper). You might try adding:
def output_buffer
h.output_buffer
end
But without testing it, I'm thinking it might not work exactly as planned without some more work. This is just a rough guess - I'd be surprised if this is actually the issue, but hopefully it gets you on the right path.
The note in the source there:
# VIEW TODO: Make #capture usable outside of ERB
# This dance is needed because Builder can't use capture
indicates that this isn't a fully-solved problem, so you may need to do a little digging around in the Rails internals to make this one work.
This works:
include Haml::Helpers
def another_way_to_try
self.init_haml_helpers
buffer = haml_buffer.buffer
h.with_output_buffer(buffer) do
h.cache "some_key10", :expires_in => 10.seconds do
h.concat "i should still not be empty 2"
end
end
end
I'm wondering if there's a specific programming principle (Demeter?) that supports the idea that Rails helpers should never use controller instance variables, rather, they should receive such variables as function parameters. For example, assume my ChickensController#squawk action creates an instance variable called #egg. Furthermore, assume the squawk view contains a call to a helper called cockadoodledoo, implemented like so:
def cockadoodledoo
#egg.to_s
end
Would it be better or unnecessarily verbose to pass #egg as a parameter, such that the view calls cockadoodledoo(#egg) and for the helper to resemble:
def cockadoodledoo(egg)
egg.to_s
end
I hope one of you happy hackers is bored enough on a Friday afternoon to assert an answer. Cockadoodledoo!
This question here is similar, but was never accurately answered.
Receive them as a param. Otherwise, as the app grows, it gets very difficult to trace where the instance vars are being set when refactoring, troubleshooting, etc.
Also, I believe there's a general best practice to only use instance vars in views within the initial template...and from there you should pass the var into helpers and other partials.
I'd say you should always pass the variables explicitly to your helper for 2 reasons:
you control exactly what you do
above all, you can test your helper
I don't know if there is any named principle governing this sort of thing but I would pass an argument. Not only will the argument make your helper easier to test and your application's data flow easier to follow but it will also let you use one helper for a single instance as well as a list; if you pass an argument then both:
<%= cockadoodledoo #egg %>
and:
<% #eggs.each do |egg| %>
<%= cockadoodledoo egg %>
<% end %>
will work as expected without introducing a special cockadoodledoo that handles a list in #eggs rather than a single #egg.
Since helper messages are mixed in to all controllers, hence available to all views (including partials and layouts), it's always wise to establish a clear contract - the parameters.
The only exception I could think of is when a instance variable is also available to all views and controllers, like a menu or something similar.
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
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.
Something like this:
class Category
SOME_CATEGORY = find_by_name("some category")
end
Category::SOME_CATEGORY
tried without a problem, but want to know if it is a bad idea, and the reasons if any..
thanks
If you don't want to hit the database each time you'll have to cache the model. There are several ways to do this, but one quick way is using Memoization. This was introduced in Rails 2.2.
class Category < ActiveRecord::Base
class << self
extend ActiveSupport::Memoizable
def named(name)
find_by_name(name)
end
memoize :named
end
end
Use it like this.
Category.named("some category") # hits the database
Category.named("some category") # doesn't hit the database
The cache should stay persistent across requests. You can reset the cache by passing true as the last parameter.
Category.named("some category", true) # force hitting the database
What do you want to do?
Maybe:
class Category
def self.some_category
Category.find_by_name("some category")
end
end
So you can call:
Category.some_category
=> <Category#2....>
It's not a terrible idea, but it's not really a good one either. It doesn't really fall in line with the way Rails does things. For one thing, you'll end up with a lot of ugly constant code. Too many ALL_CAPS_WORDS and your Ruby starts to look like C++. Bleah.
For another, it's inflexible. Are you going to make one of these constants for every category? If you add a new category two months from now, will you remember to update your Rails code, add a new constant, redeploy it and restart your server?
If it's important to you to be able to access categories very easily, and not repeat DB queries, here's a bit of metaprogramming that'll automatically look them up and create static methods like Lichtamberg's for you on first access:
def self.method_missing(category, *args) # The 'self' makes this a class method
#categories ||= {}
if (#categories[category] = find_by_name(category.to_s))
class_eval "def self.#{category.to_s}; #categories[#{category}]; end"
return #categories[category]
end
super
end
With this method in place, whenever you first call Category.ham, it'll create a class method that returns the value of find_by_name("ham") -- so that neither the query nor method_missing() runs again the next time you call it. This is pretty much the way the OpenStruct class works, BTW; look it up in the Pickaxe book if you want to learn more.
(Of course you'll still have the risk that, because these are all memoized, your Rails app won't reflect any changes you make to your category objects. This makes the assumption that changes won't happen or don't really matter. It's up to you to determine whether that assumption is valid for your app. You could always put an after_update callback in your code that resets ##categories if that's a problem; but at that point this starts to get complicated.)