I am running into an issue when looking into fragment-level caching within my Rails 3.0.4 application with memcached. I am a bit confused with what is going on, but I think it's something to do with the way the output is being pulled from within the caching region. I am running memcached locally in -vv mode, and can see the key for the fragment getting saved/pulled correctly, the problem is the value of the item within memcached.
Here is what I'm doing:
< ... html before ... >
<%= cache("item_#{i.id}") do %>
<%= render :partial => 'shared/item', :locals => { :item => i, :functionality => [:set_as_default] } %>
<% end %>
< ... html after ... >
When I look at the value of the key within the cache, it has html from within the page that is in that fragment cache block, but ALSO OUTSIDE of that (from both the html before and html after areas). Here is the interesting part though, and is kind of the reason I think its related to capturing the output--it doesn't do the whole page, only some of the html before and some after.
According to the rails fragment cacheing guide, I think I'm doing things correctly (http://guides.rubyonrails.org/caching_with_rails.html#fragment-caching). Does anyone have thoughts as to what could be going on?
Your help is much appreciated!
-Eric
In this case, you are using ERB incorrectly. Basically take out the = sign. What your doing is your returning the value of the block too and hence why you are seeing double output.
<% cache("item_#{i.id}") do %>
Also, ActiveRecord objects respond to an internally baked in #cache_key method. Try to take advantage of that. The default #cache_key for an ActiveRecord object uses the class name, the object id and the updated_at timestamp too. The cache method should be able to take multiple args or an array and it will inturn call cache_key for every object that responds to it. Using this method, it means you will cache miss when the object is updated to, pretty cool stuff. So, IIRC
<% cache("item",i) do %>
Related
I use fragment caching in Rails 4.x.
I specify my own key using something like this in my view:
<% cache "my-key" %>
html
<% end %>
This produces a key similar to this in my Redis database:
views/my-key/dca9add42d461d4c76103c08d12a6571
When I want to expire this key, I use the expire_fragment command in a controller like so:
expire_fragment("my-key")
But this command only produces a cache key like so:
views/my-key
What I don't understand is, what is the /dca9add42d461d4c76103c08d12a6571 part?
How does the original cache command not produce it, or how can I reproduce the /dca9add42d461d4c76103c08d12a6571 suffix when I want to expire the fragment.
I'm not sure if this is by design, or whether there is a bug in Rails. But what is happening is the suffix /dca9add42d461d4c76103c08d12a6571 that's added to the end of the cache key when I use the cache command in my views, is a digest, which you can skip by calling cache like so:
<% cache("my-key", { :skip_digest: true }) %>
html
<% end %>
That way the suffix (digest) gets left off the key.
Now where I think a possible bug (?) is coming in, is that the expire_fragment command doesn't add the digest to the end of the key - so I never actually end up expiring my fragments.
So my answer for now - is to use :skip_digest: true if I want to manually expire them.
According to the Rails documentation, you can just pass in the key as a string:
expire_fragment('my-key')
http://guides.rubyonrails.org/caching_with_rails.html#fragment-caching
If you're doing it from the command line, you might have to go through whatever controller the view is rendered by:
MyController.new.expire_fragment 'my-key'
I'm rendering a view in Rails using form_for and nested_form_fields. Here, #procedure_step is a record that has_many :procedure_step_actions, each of which belongs_to :error, which is a ProcedureError that has (among some relations to other models) an integer :code that I'm trying to access and print out to the page. Here's my template:
<%= form_for #procedure_step do |f| %>
<%= f.nested_fields_for :procedure_step_actions do |act| %>
<%= act.object.error.code %>
<% end %>
<% end %>
When I run this, I get undefined method 'code' for nil:NilClass. Okay, so my relations are messed up and I can't access act.object.error, right? Changing my template to display that instead yields #<ProcedureError:0x0000000ece02a8>, which is what one would expect of a functioning relation. Dumping its contents to the screen using debug shows all the attributes of the record, including code, but I still can't access it with the original template! Clearly act.object.error is not nil, so Rails telling me that act.object.error is nil doesn't make any sense to me.
Frustrated, I tried to work around the problem by using act.object.error.to_json. This printed the correct JSON for the record with all its attributes. Using JSON.load() on this gave me a correct Hash of all the attributes, but using [:code] to try to access the code gives me undefined method '[]' for nil:NilClass. Again, I know that object isn't nil, but Rails still refuses to allow me to access it.
Running out of ideas, I tried to use regular expressions to pull the code out of the raw JSON string. /"code":([0-9]+)/.match(act.object.error.to_json) returned #<MatchData "\"code\":69" 1:"69">, which is right. I used [1] to try to access the code number that was matched, but again I got undefined method '[]' for nil:NilClass.
Enough with ActiveRecord, I thought to myself. I decided to turn to raw SQL queries. I got the ID of the error in question using act.object.error_id, then printed that to the screen first to make sure I could access it. Luckily, I could. Then I inserted it into my SQL query with "... WHERE id = #{act.object.error_id}". I refreshed the page again and was greeted with a SQL error. It showed the final SQL query string I had generated, but it ended with WHERE id =. The ID of the error didn't get added to the string. ProcedureError.find(action.object.error_id) gave a similar error.
I'm totally out of ideas. What could possibly be preventing me from accessing one simple integer in so many different ways?
There are at least a couple of issues here. The first is that you probably want to be using fields_for, rather than nested_fields_for, if you're using 4.x.
The second is similar to what the first answer has indicated. You have a nested fields form, which allows you to nest one level in, but you are trying to nest two levels in. By addressing your law of demeter violation you should be able to make some more progress.
Debugging things like this you can get more information by throwing in a binding.pry or byebug right in your erb.
<%- binding.pry %>
Then reload the page. Your server will be stopped at that point in your code and you can play with variable values to learn more about what's going on.
One thing I can see right off the bat is you are violating Law of Demeter here
act.object.error.code
The form object obviously has to stay but you can delegate access to the subobjects by making a method on the procedure_step which can help with handling nulls, and other error cases.
Try delegating that first as I'm not sure if the scope that is created by nested_forms_for will allow the ActiveRecord::Relation object to perform properly. I'll double check locally.
A delegation might look like the following
class ProcedureStepActions
belongs_to :error
def error_code
#error.code
end
end
EDIT:
Other things that might be helpful are the version of Ruby and Rails you are using and any other additional gems or libraries.
Does Rails offer any way to exit early or "return" from a view that is being rendered?
For example, I have a view structured as follows:
<h1>Your records summary</h1>
<% if #current_user.has_records? %>
<p>You don't have any records.</p>
<% else %>
... long block of view emission code here ...
<% end %>
In non-erb code, I'd just return from that if condition, and wouldn't need to put that long block inside an else. It would look like:
<h1>Your records summary</h1>
<% if #current_user.has_records? %>
<p>You don't have any records.</p>
<% return (or something) %>
<% end %>
... long block of view emission code here ...
Is there a way to do something like that in the erb?
EDIT: To be clear, I'm not asking for alternate approaches, such as have been suggested. I'm not a Rails noob. I really just want to know whether Ruby provides a mechanism for what I suggested or not.
This is an old question, but I figured I'd take the time to spare future people the hour or so of their lives trying to figure out if this is doable.
I've discovered that, as far back as Rails 3.2, this is actually possible.
It requires some knowledge of ActionView internals and I do not advocate anyone do this in production code. Further, this particular implementation only works with the built-in ERB engine used by Rails which, itself, uses Erubi.
Alright, now that we're done with the warnings, on to the action!
The surprisingly simple answer is to return #output_buffer.to_s.
As long as you've closed all your HTML tags before returning, it seems to work and everything is correctly rendered.
For the curious, this output buffer is currently configured in the initializer for the Erubi ERB handler. With the rest of the work done by the underlying Erubi::Engine implementation.
I've poked around at something that provides a less brittle way to go about returning early, but I've unsuccessful so far.
In Rails 3.2.2, return inside a template works just fine for me.
<% return unless allowed_tags %>
IIRC there's no built-in mechanism for exiting a template early. This is in line with how (I think) a view layer should behave, although erb isn't quite a template engine.
The old-timey solution was to wrap your template in a catch and throw something from within the template, like a symbol.
You could also embed the eval inside a lambda, allowing return to work.
Still ew, though.
The best way is to handle the conditional in your controller before rendering the template. There you can early return a different template (like the Add Record form) or the results template.
You can also offload that logic to Javascript in the view itself.
I have a block
<% cache 'unique_key', 60.minutes.from_now do %>
...
<% begin %>
...
<% rescue %>
...
<%end>
<% end %>
and I'm trying to make the implementation more robust by only caching (and thus allowing the user to see) the rescue message if there isn't a previous value already in the cache. Currently, if the response in the begin block sends back an error for any reason, I'm caching the user viewed error message. I would prefer to fall back onto the old cached data. The problem that I can't get past is -
Where is cache storing the data?
Every time I try Rails.cache.read 'unique_key', I get nil back. Is cache not storing the value in memcached? Is there a way that I can dump the cache to screen?
I couldn't follow the rails source. It seemed to me the the fragment_for method in cache was a rails 3 thing, and thus, I didn't debug further.
The cache view helper constructs a cache key based on the arguments you give it. At a minimum it adds the prefix 'views/' to the key.
You can use the fragment_cache_key helper to find out what cache key rails is using for any of your calls to cache. If you just want to grab what is currently stored, read_fragment does that. Of course with your particular usage, if your block is executed again it is because the 60 minutes are up: the cached value has been deleted from memcache.
With the memcache store you can't list all of the keys currently in the store - it's just something thy memcached itself doesn't support.
I solved this by using the fetch method. I used
<% Rails.cache.fetch('unique_key', :expires_in => 60.minutes){
begin
...
rescue
...
end
} %>
When I did this, I could successfully find the key. I'm still not sure why I couldn't find the cached data after adding the fragment_cache_key that I found, but using Rails.cache.fetch seemed to do the trick.
I am implementing caching into my Rails project via Memcached and particularly trying to cache side column blocks (most recent photos, blogs, etc), and currently I have them expiring the cache every 15 minutes or so. Which works, but if I can do it more up-to-date like whenever new content is added, updated or whatnot, that would be better.
I was watching the episode of the Scaling Rails screencasts on Memcached http://content.newrelic.com/railslab/videos/08-ScalingRails-Memcached-fixed.mp4, and at 8:27 in the video, Gregg Pollack talks about intelligent caching in Memcached in a way where intelligent keys (in this example, the updated_at timestamp) are used to replace previously cached items without having to expire the cache. So whenever the timestamp is updated, the cache would refresh as it seeks a new timestamp, I would presume.
I am using my "Recent Photos" sideblock for this example, and this is how it's set up...
_side-column.html.erb:
<div id="photos"">
<p class="header">Photos</p>
<%= render :partial => 'shared/photos', :collection => #recent_photos %>
</div>
_photos.html.erb
<% cache(photos) do %>
<div class="row">
<%= image_tag photos.thumbnail.url(:thumb) %>
<h3><%= link_to photos.title, photos %></h3>
<p><%= photos.photos_count %> Photos</p>
</div>
</div>
<% end %>
On the first run, Memcached caches the block as views/photos/1-20110308040600 and will reload that cached fragment when the page is refreshed, so far so good. Then I add an additional photo to that particular row in the backend and reload, but the photo count is not updated. The log shows that it's still loading from views/photos/1-20110308040600 and not grabbing an updated timestamp. Everything I'm doing appears to be the same as what the video is doing, what am I doing wrong above?
In addition, there is a part two to this question. As you see in the partial above, #recent_photos query is called for the collection (out of a module in my lib folder). However, I noticed that even when the block is cached, this SELECT query is still being called. I attempted to wrap the entire partial in a block at first as <% cache(#recent_photos) do %>, but obviously this doesn't work - especially as there is no real timestamp on the whole collection, just it's individual items of course. How can I prevent this query from being made if the results are cached already?
UPDATE
In reference to the second question, I found that unless Rails.cache.exist? may just be my ticket, but what's tricky is the wildcard nature of using the timestamp...
UPDATE 2
Disregard my first question entirely, I figured out exactly why the cache wasn't refreshing. That's because the updated_at field wasn't being updated. Reason for that is that I was adding/deleting an item that is a nested resource in a parent, and I probably need to implement a "touch" on that in order to update the updated_at field in the parent.
But my second question still stands...the main #recent_photos query is still being called even if the fragment is cached...is there a way using cache.exists? to target a cache that is named something like /views/photos/1-2011random ?
One of the major flaws with Rails caching is that you cannot reliably separate the controller and the view for cached components. The only solution I've found is to embed the query in the cached block directly, but preferably through a helper method.
For instance, you probably have something like this:
class PhotosController < ApplicationController
def index
# ...
#recent_photos = Photos.where(...).all
# ...
end
end
The first instinct would be to only run that query if it will be required by the view, such as testing for the presence of the cached content. Unfortunately there is a small chance that the content will expire in the interval between you testing for it being cached and actually rendering the page, something that will lead to a template rendering error when the nil-value #recent_photos is used.
Here's a simpler approach:
<%= render :partial => 'shared/photos', :collection => recent_photos %>
Instead of using an instance variable, use a helper method. Define your helper method as you would've the load inside the controller:
module PhotosHelper
def recent_photos
#recent_photos ||= Photos.where(...).all
end
end
In this case the value is saved so that multiple calls to the same helper method only triggers the query once. This may not be necessary in your application and can be omitted. All the method is obligated to do is return a list of "recent photos", after all.
A lot of this mess could be eliminated if Rails supported sub-controllers with their own associated views, which is a variation on the pattern employed here.
As I've been working further with caching since asking this question, I think I'm starting to understand exactly the value of this kind of caching technique.
For example, I have an article and through a variety of things I need for the page which include querying other tables, maybe I need to do five-seven different queries per article. However, caching the article in this way reduces all those queries to one.
I am assuming that with this technique, there always needs to have at least "one" query, as there needs to be "some" way to tell whether the timestamp has been updated or not.