Instance variables seem to lose their values - ruby-on-rails

My report.rb class contains the following code:
def create
#start_term = ::SchoolTerm.find(1705265)
#end_term = ::SchoolTerm.currently_enrolling
current = 0
total = all_terms.size
#terms = {}
all_terms.each do |t|
#terms[t.id] = Business::Sales::RevenueByWeek::Term.new(t)
Rails.logger.info("#{#terms} is #terms in report#create")
current += 1
self.progress = 100.0 * (Float(current) / Float(total))
end
end
def all_terms
::SchoolTerm.between(#start_term, #end_term) - RevenueGoal.terms_without
end
def each
Rails.logger.info("#{#terms} is #terms in report.each")
all_terms.each do |t|
yield #terms[t.id] if #terms[t.id]
end
end
The logger line inside create shows that #terms has the correct values while looping through different school terms. However, the logger line in the each method shows #terms as empty. When I try to load up the report, I get this error:
You have a nil object when you didn't expect it!
You might have expected an instance of ActiveRecord::Base.
The error occurred while evaluating nil.[]
Extracted source (around line #24):
</tr>
</thead>
<% report.each do |term| %>
<tbody class="term_<%= term.id %>">
<tr class="term">
<th><%= term.term %></th>
I have verified that Business::Sales::RevenueByWeek::Term.new returns the correct data. Any ideas why I'm getting this nil object error? I thought that instance variables retained their values, but somehow this one is getting lost.
Thanks.

I'm assuming report.rb is an active record model. In that framework, #create isn't meant to be a constructor, it only creates rows in the underlying table. I see you set #terms = {} there, however.
If your report instance is queried out of the database, #create is never run, and so your member is never initialized.
I suggest you hide uses of #terms (even internal ones) behind a method named terms (that is, unless you've already declared it as an AR attribute, or similar.) Your method can lazy-initialize the member as needed.

report.rb is not an active record model - no database tables are used. The create method is used to create an instance of the report, with cascading calls to other classes needed to get the values for the report, such as the Term.new call.
After the create method finishes, a view is rendered - the error above comes from one of the partials in that view.
I have placed various logger calls in the create method to verify that it is indeed getting called and that the #terms variable in the create method is what I expect it to be. What I can't figure out is why the filled-in #terms hash isn't available in the each method. The create method logger calls show up before the each method logger calls, and #terms is okay in create but empty or nil in each. Shouldn't #terms retain its value for the whole instance - and do it without hiding things in another method?

I solved the problem. What was happening was that the #terms variable was supposed to be written to a cache. We are using memcached, and the #terms data was more than 1 MB is size, so the Cache.write statement was failing. Then when #terms needed to be pulled from the cache in the 'each' method, there was nothing there. The quick-and-dirty solution was to cut down the size of the #term data so that the Cache.write was successful. A long term solution is to replace memcached with Resque, which has no such limitations on the size of the data corresponding to one key.

Related

Why I cannot access to this instance variable (in model) from another method?

I have a method in my model and I call it with before_create:
def first_position
if [...]
[...]
else
#last_known = Picture.where(user_id: user_id).order('position desc').first
self.position = nil
end
end
And I have another method that I call with after_commit:
def default_position
pictures = Picture.where(user_id: user_id).where('created_at > ?', #last_known.created_at)
i = #last_known.position + 1
pictures.each do |pic|
pic.position = i
i += 1
end
end
But in default_position, #last_known returns nil. Do you know why?
EDIT:
Well, I discovered that I have two commit, but the one that concerns the picture is second, so #last_known is set at the first commit but disappears at the second commit.
Initially I thought that ActiveRecord reloads the record sometime before the after_commit, or at least reinitializes it. I've checked with Rails 5.2.1 and nothing happens with instance variables, they remain correctly set. This means that (unless you're using older Rails), there simply are no pictures for the given user and your code does not seem to handle that.
Also, your after_commit will run after you update the object as well, which might be the issue as your variable will not be set in that case.

Cannot cache from decorator (draper)

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

Serialized column by model in rails work correctly only after refresh

In my model I have:
class Log < ActiveRecord::Base
serialize :data
...
def self.recover(table_name, row_id)
d = Log.where(table_name: table_name, row_id: row_id).where("log_type != #{symbol_to_constant(:delete)}").last
row = d.data
raise "Nothing to recover" if d.nil?
raise "No data to recover" if d.data.nil?
c = const_get(table_name)
ret = c.create(row.attributes)
end
And in my controller I calling it as:
def index
Log.recover params[:t], params[:r]
redirect_to request.referer
end
The problem is, if I access this page for the first time, I am getting error specified below, but after refresh, is everything OK. Where can be problem?
undefined method `attributes' for #<String:0x00000004326fc8>
In data column are saved instances of models. For the first time column isn't properly unserialized, it's just yaml text. But after refresh everything is fine. That's confusing, what is wrong? Bug in rails?
It's not every time, sometimes in first access everything is okey.
Deyamlizing an object of class Foo will do funny things if there is no class Foo. This can quite easily happen in development becauses classes are only loaded when needed and unloaded when rails thinks they might have changed.
Depending on whether the class is loaded or not the YAML load will have different results (YAML doesn't know about rail's automatic loading stuff)
One solution worth considering is to store the attributes hash rather than the activerecord object. You'll probably avoid problems in the long run and it will be more space efficient in the long wrong - there's a bunch of state in an activerecord object that you probably don't care about in this case.
If that's not an option, your best bet is probably to make sure that the classes that the serialized column might contain are loaded - still a few calls to require_dependency 'foo' at the top of the file.

Help with ruby and def respond_to?(method,*args, &block) in library

I have a Gem that deals with images that get modified. I want to modify it to update the old image but, I need to be able to get the object id.
Right now it uses the following code:
def respond_to?(method,*args, &block)
puts ("++++METHOD #{method.to_s} ARGS #{args} BLOCK #{block}")
args.each do |value|
puts ("ARGS #{value}")
end
but I don't know how to get id from something I pass in nor do I know how to pass the id in, I've tried
#asset.image.s_245_245(:asset_id=>#asset.id) with no success. Args returns nothing. What am I doing wrong?
Update: I am currently reading http://www.simonecarletti.com/blog/2009/09/inside-ruby-on-rails-extract_options-from-arrays/
Update: This too returned blank.
Your question is very unclear. How is the method respond_to? related to your problem with object id?
I am guessing that in reality you wanted to override method_missing, because now you do not call respond_to?, or it is not shown in your examples.
If you have not defined such method, calling image.s_245_245 will trigger method_missing (in the image object) with the parameters you used for respond_to?.
There is a rule, which says that if you use method_missing to handle some calls, then you should also modify respond_to?, and make it returning true when asked for the methods handled by method_missing.
As for object ID, there are two possibilities:
Every object in ruby responds to .object_id (which returns an internal identifier of every object)
ActiveRecord objects respond to .id (which is a primary key in the database).
This is just a side-note, because I suppose that if you start experimenting with method_missing instead of respond_to? you will know which one you want.

Trouble accessing object attributes from cache

I am caching several User objects using Rails.cache.write but when I retrieve it from cache and try to call an attribute method (id, name, email, etc.) on any of the User objects I am getting
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.include?
I can call inspect on the User objects and I see all the attributes. Any ideas? Thanks
I'm using the standard cache configuration, no modification. Here is some code:
I am writing to the cache in my model inside an instance method that gets called when the user logs in like so:
cached_friends = self.friends
Rails.cache.write(self.id.to_s+"_friends", cached_friends)
Then in my controller I have a before filter:
def get_friends_from_cache
if current_user
friends = Rails.cache.read(current_user.id.to_s+"_friends")
friends.each do |friend|
if !friend.facebook_id.nil?
#other stuff....
end
end
end
end
I get the error on the if !friend.facebook_id.nil? line. facebook_id is a column in the db. Basically there are no User instance methods available for each User object. But I know the objects are there because I can insert raise "#{friends.first.inspect}" and everything spits out as I would expect.
This happens in development mode (i.e. with the setting config.cache_classes = false). The root cause:
ActiveRecord strips the attribute accessor methods from the class objects
MemoryStore (i.e. the default cache store) does not martial/unmartial objects
One workaround is to use MemCacheStore another one is to set config.cache_classes = true. See also Comments on railscasts

Resources