Overwritten model getter executed on save? - ruby-on-rails

I'm currently working on fixing a bug in Rails plugin and I just found the method which causes the issue. My guess is that it's somehow related to one of the getter methods being overwritten in its ActiveRecord class Sprint. The method name is burndown and there is also an attribute called burndown which stores serialized Hash.
class Sprint < ActiveRecord::Base
serialize :burndown, Hash
...
def touch!
... do stuff ...
self.burndown = nil
self.save!
end
def burndown
... some crazy-ass method ...
end
end
So the burndown method somehow gets executed on save but I'm not really sure why, as there are no callbacks defined for the Sprint class. Is it possible that overwriting the getter method causes that?

Getter method gets called on save even if you don't have any validations or callback.
"/home/user/.rvm/gems/ruby-1.9.3-p194/gems/activemodel-3.2.13/lib/active_model/dirty.rb:143:in `attribute_change'"
"/home/user/.rvm/gems/ruby-1.9.3-p194/gems/activemodel-3.2.13/lib/active_model/dirty.rb:117:in `block in changes'"
"/home/user/.rvm/gems/ruby-1.9.3-p194/gems/activemodel-3.2.13/lib/active_model/dirty.rb:117:in `map'"
"/home/user/.rvm/gems/ruby-1.9.3-p194/gems/activemodel-3.2.13/lib/active_model/dirty.rb:117:in `changes'"
"/home/user/.rvm/gems/ruby-1.9.3-p194/gems/activerecord-3.2.13/lib/active_record/attribute_methods/dirty.rb:23:in `save'"
"/home/user/.rvm/gems/ruby-1.9.3-p194/gems/activerecord-3.2.13/lib/active_record/transactions.rb:259:in `block (2 levels) in save'"
"/home/user/.rvm/gems/ruby-1.9.3-p194/gems/activerecord-3.2.13/lib/active_record/transactions.rb:313:in `block in with_transaction_returning_status'"
"/home/user/.rvm/gems/ruby-1.9.3-p194/gems/activerecord-3.2.13/lib/active_record/connection_adapters/abstract/database_statements.rb:192:in `transaction'"
"/home/user/.rvm/gems/ruby-1.9.3-p194/gems/activerecord-3.2.13/lib/active_record/transactions.rb:208:in `transaction'"
"/home/user/.rvm/gems/ruby-1.9.3-p194/gems/activerecord-3.2.13/lib/active_record/transactions.rb:311:in `with_transaction_returning_status'"
"/home/user/.rvm/gems/ruby-1.9.3-p194/gems/activerecord-3.2.13/lib/active_record/transactions.rb:259:in `block in save'"
"/home/user/.rvm/gems/ruby-1.9.3-p194/gems/activerecord-3.2.13/lib/active_record/transactions.rb:270:in `rollback_active_record_state!'"
"/home/usha/.rvm/gems/ruby-1.9.3-p194/gems/activerecord-3.2.13/lib/active_record/transactions.rb:258:in `save'"
So if you override getter for an attribute, make sure that it still performs its original function

Related

How to call another method from a self method with ruby?

# app/models/product.rb
class Product < ApplicationRecord
def self.method1(param1)
# Here I want to call method2 with a parameter
method2(param2)
end
def method2(param2)
# Do something
end
end
I call method1 from controller. When I run the program. I got an error:
method_missing(at line method2(param2))
.rbenv/versions/2.3.1/lib/ruby/gems/2.3.0/gems/activerecord-5.0.0/lib/active_record/relation/batches.rb:59:in `block (2 levels) in find_each
...
class Product < ApplicationRecord
def self.method1(param1)
# Here I want to call method2 with a parameter
method2(param2)
end
def self.method2(param2)
# Do something
end
end
Explanation: first one is a class method, the latter was an instance method. Class methods don't need a receiver (an object who call them), instance methods need it. So, you can't call an instance method from a class method because you don't know if you have a receiver (an instanciated object who call it).
It does not work because method2 is not defined for Product object.
method2 is an instance method, and can be called only on the instance of Product class.
Of course #Ursus and #Andrey Deineko answers are right solution of this problem. Besides that, if anyone want to know how we can call instance method with in class method(though this is not actually class method in ruby) for those self.new.instance_method.

When monkey-patching a single method in a gem can I make it aware of an active record in my app?

I am looking at this answer to monkey-patch https://stackoverflow.com/a/21446021.
In this example the gem (someone else's library) has the method someMethod() which will eventually call the method I patched.
My app looks like this
activeRecord = ... # gets an ActiveRecord object that I want to update
options = {..}
#gem_object = SomeLibrary::Class.new(options)
#gem_object.someMethod()
Now someMethod() is going to eventually call the patched method. I want to make the patched method aware of activeRecord so it can update it. I am wondering if I can avoid adding activeRecord as a parameter to all the methods in the stack-trace when someMethod() is called.
I'd store it in a plain class
class StoreRecord
def self.activeRecord
#active_record
end
def self.activeRecord=(record)
#active_record = record
end
end
so your code becomes
StoreRecord.activeRecord = ... # gets an ActiveRecord object that I want to update
options = {..}
#gem_object = SomeLibrary::Class.new(options)
#gem_object.someMethod()
and you can access StoreRecord.activeRecord in your patched method.

Rails get objects in front of dot of a class method

students = Student.limit(3)
students.approve()
def self.approve
# ...
end
I want to get students and use each_with_index, but it fails.
I tried self, it points out
"NoMethodError: undefined method `each_with_index' for #<Class:0x00000007a1b920>".
I just want the objects in front of my method, how can I do this, thanks !
In the approve method you have an ActiveRecord::Relation, but you need an array instead:
def self.approve_all
all.each_with_index do |user, index|
...
end
end
Class methods in ActiveRecord models can be called not only on the class itself, but also on ActiveRecord::Relation objects.
So for example, we can call the .approve_all method not only directly on the class: Student.approve_all, but also on any relation for the Student class: Student.order(name: :asc).limit(10).where(state: "not_approved").approve_all
But i think the method should be at least named update_all, and i would try to figure out a better solution for this problem.

RSpec Shoulda validates_presence_of nilClass

When I use Shoulda's validates_presence_of, it stumbles on a before_validation callback.
before_validation :set_document, :set_product, :set_price
I'm trying to get this spec to pass:
it { should validate_presence_of(:quantity).with_message("Please a quantity.") }
I have database defaults of 0 for a line item's quantity, unit_price, tax_rate, and price. Before a line item is validated I compute the price from the other attributes in case they have changed.
I get this error, and similar errors, for all of the attributes involved in this computation:
3) LineItem
Failure/Error: it { should validate_presence_of(:quantity).with_message("Please a quantity.") }
NoMethodError:
undefined method `*' for nil:NilClass
# ./app/models/line_item.rb:153:in `total_price'
# ./app/models/line_item.rb:223:in `set_price'
# ./spec/models/line_item_spec.rb:32:in `block (2 levels) in <top (required)>'
My callback, set_price, is very simple:
def set_price
self.price = total_price
end
And the total_price method is very simple as well:
def total_price
quantity * unit_price * (1 + tax_rate/100)
end
I'd appreciate any help with this one as I'm completely stumped. I did see some people post about custom validation methods. This seems so basic I can't figure it out how to proceed.
Since total_price runs before validation, quantity can be nil at the time the callback is executed. This is in fact what happens behind the scenes when the Shoulda matcher runs, which is why you get an error. It's trying to send the * method to quantity, which is nil.
Use after_validation or before_save instead.

how do use define_method on an instance of an object on objects of unknown type?

So, I'm kind of wanting to do something similar to rspec / mocha's mock, but only for two objects, and not all of them. This is what I have so far:
def mock(obj, method_to_mock, value)
obj.class << obj do
define_method(method_to_mock) do
return value
end
end
end
I got the idea to write it like that from this post: https://stackoverflow.com/a/185969/356849
So then I can do things like:
mock(self.instantiated, :sections, sections)
and it would override the object I have stored in self.instantiated's sections with my array of Section objects, sections.
The reason why I'm doing this, is because I'm storing a serialized and encrypted version of of an object, and I want to be able to unencrypt and unserialize the object, and then restore all the relationships such that I can view that object in my views, as if it were being read from the database. but that's not important, and most of it's done.
So, I want to be able to do this:
mock(<Instance of object>, :<method of object that is going to be overridden, to avoid db access>, <the stuff to return when the overridden method is invoked)
CUrrently, I'm getting an error on the obj.class << obj do line with this:
NoMethodError: undefined method `obj' for #<MyObject::Encrypted:0x7f190eebcd18>
ideas?
UPDATE
changed the second line to class << obj which now infinite loops.
from /home/me/.rvm/gems/ruby-1.8.7-p371#project/gems/activerecord-2.3.15/lib/active_record/connection_adapters/abstract/connection_pool.rb:351:in `retrieve_connection_pool'
from /home/me/.rvm/gems/ruby-1.8.7-p371#project/gems/activerecord-2.3.15/lib/active_record/connection_adapters/abstract/connection_pool.rb:351:in `retrieve_connection_pool'
from /home/me/.rvm/gems/ruby-1.8.7-p371#project/gems/activerecord-2.3.15/lib/active_record/connection_adapters/abstract/connection_pool.rb:325:in `retrieve_connection'
from /home/me/.rvm/gems/ruby-1.8.7-p371#project/gems/activerecord-2.3.15/lib/active_record/connection_adapters/abstract/connection_specification.rb:123:in `retrieve_connection'
from /home/me/.rvm/gems/ruby-1.8.7-p371#project/gems/activerecord-2.3.15/lib/active_record/connection_adapters/abstract/connection_specification.rb:115:in `connection'
from /home/me/.rvm/gems/ruby-1.8.7-p371#project/gems/activerecord-2.3.15/lib/active_record/base.rb:1305:in `columns'
from /home/me/.rvm/gems/ruby-1.8.7-p371#project/gems/activerecord-2.3.15/lib/active_record/base.rb:1318:in `column_names'
from /home/me/.rvm/gems/ruby-1.8.7-p371#project/gems/searchlogic-2.4.28/lib/searchlogic/named_scopes/ordering.rb:35:in `ordering_condition_details'
from /home/me/.rvm/gems/ruby-1.8.7-p371#project/gems/searchlogic-2.4.28/lib/searchlogic/named_scopes/ordering.rb:26:in `method_missing'
from /home/me/.rvm/gems/ruby-1.8.7-p371#project/gems/searchlogic-2.4.28/lib/searchlogic/named_scopes/or_conditions.rb:28:in `method_missing'
from /home/me/.rvm/gems/ruby-1.8.7-p371#project/gems/activerecord-2.3.15/lib/active_record/base.rb:2002:in `method_missing_without_paginate'
from /home/me/.rvm/gems/ruby-1.8.7-p371#project/gems/will_paginate-2.3.16/lib/will_paginate/finder.rb:170:in `method_missing_without_attr_encrypted'
from /home/me/.rvm/gems/ruby-1.8.7-p371#project/bundler/gems/attr_encrypted-a4b25f01d137/lib/attr_encrypted/adapters/active_record.rb:50:in `method_missing'
from /home/me/Work/GravityLabs/project/app/models/proposal/encrypted.rb:119:in `mock'
from /home/me/Work/GravityLabs/project/app/models/proposal/encrypted.rb:79:in `instantiate'
from /home/me/Work/GravityLabs/project/app/models/proposal/encrypted.rb:58:in `each'
from /home/me/Work/GravityLabs/project/app/models/proposal/encrypted.rb:58:in `instantiate'
def mock(obj, method_to_mock, value=nil)
obj.define_singleton_method(method_to_mock) do value end
end
obj.class << obj do makes no sense.
What you probably wanted to say is
def mock(obj, method_to_mock, value)
(class << obj; self; end).class_eval do
define_method(method_to_mock) do
return value
end
end
end
The (class << obj; self; end).class_eval syntax is opening the singleton class of obj returning that singleton class, then invoking class_eval on that singleton class passing the block.
In your syntax, obj.class sends the :class message to obj as a receiver which returns a reference to obj's class (not its singleton class), on which you then try to invoke the << method passing the result of evaluating obj do...end as an arg. Since obj is not a method of self (MyObject::Encrypted:0x7f190eebcd1) you get the NoMethodError.
In modern ruby, instead of saying the relatively arcane, (class << obj; self; end) to get the singleton class, you can use the singleton_class method like so: obj.singleton_class.class_eval do ... end

Resources