I want to check when attributes on a model have changed. I have attempted to check values != the value on the form before doing a save but that code is really ugly and is not working well at times. Same with using update_column which does not do the validations in my model class. If I use update_attributes without doing something else I will not be able to check when a field has been updated from my understanding. From my web research on Stack Overflow and other sites it appears that using ActiveModel Dirty is the way to go.
I have looked at this: http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
My hope is to use this to check if boolean flags changed on a model after using update_attributes. I attempted to do the minimum implementation as described in the included link. I added the following to my ActiveRecord class:
include ActiveModel::Dirty
define_attribute_methods [:admin]
I tried adding the three attributes I wanted to keep track of. I started with just one attribute to see if I could get it working. I received the following error when I ran an rspec test. Once I removed the argument I had no errors.
Exception encountered: #<ArgumentError: wrong number of arguments (1 for 0)>
After I removed the argument I decided to include similar methods in my model using admin instead of name. Other Rspec tests broke on the save method. However I feel the problem is with how I am implementing ActiveModel Dirty.
I have read on other Stack Overflow posts where commenters stated that this was included in 3.2.8 so I upgraded from 3.2.6 to 3.2.8. I did not understand what that meant so after getting errors I decide just to leave the include ActiveModel::Dirty statement and try to use admin_changed? Of course it did not work.
I have not been able to find anything about how to initially set things up for this other than the link I included here. All the other research I have found assumes that the initial setup was correct and that updating to the current stable version of Rails would take care of their problems.
Any help would be appreciated on how to implement this. Doing the minimal implementation as stated in the link is not working. Maybe there is something else I am missing.
The problem appears to be that ActiveRecord redefines the define_attribute_methods method to accept 0 arguments (because ActiveRecord automatically creates attribute methods for every column in the database table): https://github.com/rails/rails/blob/master/activerecord/lib/active_record/attribute_methods.rb#L23
This overrides the define_attribute_methods method provided by ActiveModel: https://github.com/rails/rails/blob/master/activemodel/lib/active_model/attribute_methods.rb#L240
Solution:
I figured out a solution that worked for me...
Save this file as lib/active_record/nonpersisted_attribute_methods.rb: https://gist.github.com/4600209
Then you can do something like this:
require 'active_record/nonpersisted_attribute_methods'
class Foo < ActiveRecord::Base
include ActiveRecord::NonPersistedAttributeMethods
define_nonpersisted_attribute_methods [:bar]
end
foo = Foo.new
foo.bar = 3
foo.bar_changed? # => true
foo.bar_was # => nil
foo.bar_change # => [nil, 3]
foo.changes[:bar] # => [nil, 3]
However, it looks like we get a warning when we do it this way:
DEPRECATION WARNING: You're trying to create an attribute `bar'. Writing arbitrary attributes on a model is deprecated. Please just use `attr_writer` etc.
So I don't know if this approach will break or be harder in Rails 4...
See also:
Track dirty for not-persisted attribute in an ActiveRecord object in rails
https://github.com/adzap/validates_timeliness/issues/73
Try adding adding =, like this:
define_attribute_methods = [:admin]
That change worked for me. Not sure if it has something to do with this?
Related
I'm working on upgrading a Ruby 2.2.2 (Rails 4.1) app to Ruby 2.5.7 (Rails 5.2) and for a couple of models I'm getting some errors
From searching around, it sounds like there are some generic activerecord validation rules / messages? The messages are:
Status is invalid
User is invalid`
I am a novice at best with Ruby - so any suggestions on the best way to work through this error are appreciated!
In Rails 5, whenever a belongs_to association is defined, it is required to have the associated record present by default. That means, compared to Rails 4, each belongs_to :foo association basically adds internally a validate :foo, presence: true to the code too.
You have two choices:
Follow the new Ruby on Rails conventions and fix your tests by adding all required associated objects to the models.
Switch back to the old behavior for these kinds of associations by adding , optional: true to each belongs_to :foo line in your code.
There is actually the third option to switch off this behavior in the whole application, by adding a line like this to your application.rb
Rails.application.config.active_record.belongs_to_required_by_default = true
But that means your application will not follow Ruby on Rails conventions and defaults anymore and IMHO this ofter leads to problems with a later update.
Therefore my advice is: Fix your tests now and only make those associations optional that are really optional from the user's point of view – this might take a bit longer but causes certainly less trouble in the future.
I have been chasing an issue down for a while now, and still cannot figure out what's happening. I am unable to edit documents made from my gem through normal persistence methods, like update or even just editing attributes and calling save.
For example, calling:
Scram::Policy.where(id: a.id).first.update!(priority: 12345)
Will not work at all (there are no errors, but the document has not updated). But the following will work fine:
Scram::Policy.collection.find( { "_id" => a.id } ).update_one( { "$set" => {"priority" => 12345}})
I am not sure what I'm doing wrong. Calling update and save on any other model works fine. The document in question is from my gem: https://github.com/skreem/scram/blob/master/lib/scram/app/models/policy.rb
I cannot edit its embedded documents either (targets). I have tried removing the store_in macro, and specifying exactly what class to use using inverse_of and class_name in a fake app to reimplement these classes: https://github.com/skreem/scram-implementation/blob/master/lib/scram/lib/scram/app/models/policy.rb
I've tried reimplementing the entire gem into a clean fake rails application: https://github.com/skreem/scram-implementation
Running these in rails console demonstrates how updating does not work:
https://gist.github.com/skreem/c70f9ddcc269e78015dd31c92917fafa
Is this an issue with mongoid concerning embedded documents, or is there some small intricacy I am missing in my code?
EDIT:
The issue continues if you run irb from the root of my gem (scram) and then run the following:
require "scram.rb"
Mongoid.load!('./spec/config/mongoid.yml', :test)
Scram::Policy.first.update!(priority: 32) #=> doesn't update the document at all
Scram::Policy.where(id: "58af256f366a3536f0d54a61").update(priority: 322) #=> works just fine
Oddly enough, the following doesn't work:
Scram::Policy.where(id: "58af256f366a3536f0d54a61").first.update(priority: 322)
It seems like first isn't retrieving what I want. Doing an equality comparison shows that the first document is equal to the first returned by the where query.
Well. As it turns out, you cannot call a field collection_name or else mongoid will ensure bad things happen to you. Just renaming the field solved all my issues. Here's the code within mongoid that was responsible for the collision: https://github.com/mongodb/mongoid/blob/master/lib/mongoid/persistence_context.rb#L82
Here's the commit within my gem that fixed my issue: https://github.com/skreem/scram/commit/25995e955c235b24ac86d389dca59996fc60d822
Edit:
Make sure to update your Mongoid version if you have dealt with this issue and did not get any warnings! After creating an issue on the mongoid issue tracker, PersistenceContext was added to a list of prohibited methods. Now, attempting to use collection_name or collection as a field will cause mongoid to spit out a couple of warnings.
Fix commit: https://github.com/mongodb/mongoid/commit/6831518193321d2cb1642512432a19ec91f4b56d
O.k. this is driving me crazy - if anyone could help that would be great.
I have a simple table "threads" with an auto id and a "name" field with one record (id = 1, name = "space").
I have a model named "Thread.rb".
class Thread < ActiveRecord::Base
end
I have a controller that calls:
#thread = Thread.find_by_id(1)
But when loading a page I get the following error in the controller:
undefined method `find_by_id' for Thread:Class
I've used find_by_sql numerous times in the same project with no problem, but when using the simple activerecord accessors rails errors out on "find", "find_by_id" etc.
I'm on rails 4.0.2
I'm pretty sure Thread is a reserved word in Rails, I recall having the same issue. Renaming your class should solve it. I was able to find this community authored site with a list of reserved Rails words.
rails has the autoloading mechanism by hooking into the const_missing and the combination of some convention on constant naming and autoload_paths.
If you refer to a constant that is already predefined, either in the ruby standard library or the rails, then it will refer that constant to the already loaded one.
Thread is part of ruby standard library, which is why when you say Thread.find_by_id(1), it is referring to that instead of your model.
Refer to Thread and Module#const_missing for more info.
I've inherited a project in which an ActiveRecord model is giving me strange behaviour. This is the only model that's behaving this way, so far.
car = Car.first
car.respond_to?(:each) # true
There is no each method defined in Car that I can find.
I read in another post that you can find out who injected the method, so I gave it a shot:
car.method(:each) # raises "undefined method `each' for class `Car'
I want to use Car with draper but because my instance behaves like an Enumerable, draper treats it as such. Draper is not the issue, however.
What should I do to figure it out?
The comments to my question helped me find it! I used Dmitry's binary-search to figure out what caused it, and found the problem in a gem that the model is using. The gem, Mark Reed, overrides respond_to. So the lesson is: if responds_to is funky, perhaps it's being overridden somewhere.
Prior to Rails 3, creating a plugin to extend find was relatively easy: basically override the find method itself in ActiveRecord::Base, calling super if needed.
Now that Arel is being used in Rails 3, (specifically I'm using Rails 3.1), how would I go about doing something similar? The problem is that many of the old find methods are deprecated in favor of scopes like where, order, limit, etc. At what point(s) in the Rails source should I try to override the default behavior?
I'm sure it's going to be a bit more convoluted than this, but the closest thing I can find that seems like it might be appropriate is the construct_finder_arel method in ActiveRecord::Base.
After digging through Rails source, regardless of Arel being used, find_by_sql method is called on whatever model is performing the find. This can be extended via alias_method_chain as follows:
find_by_sql_with_customization(*args)
results = find_by_sql_without_customization
# do something with results here
results
end
alias_method_chain :find_by_sql, :customization