4

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.

tshepang
  • 12,111
  • 21
  • 91
  • 136

2 Answers2

8

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:

Community
  • 1
  • 1
Tyler Rick
  • 9,191
  • 6
  • 60
  • 60
  • Tyler I am in the process of creating Rails 4 versions of all my Rails 3 applications. I have decided to take a look at this again after working on other features of my website. I am at the point where I have added so many booleans that attempting to implement ActiveModel::Dirty makes sense. Hopefully Rails 4 will make this process easier. I will probably check that before proceeding. Thanks so much for posting your solution. If I am successful in implementing it I will accept your answer. – Pamela Cook - LightBe Corp Sep 14 '13 at 15:58
-3

Try adding adding =, like this:

define_attribute_methods = [:admin]

That change worked for me. Not sure if it has something to do with this?

kwh941
  • 235
  • 2
  • 9
  • kwh941, I decided to write compares between the model and view not long after I posted this question since I have another deadline for my project to complete very soon. The logic works well. I decided yesterday to change my define_attribute_methods statement as suggested. I was able to add the methods I wanted to track to the statement with no errors. However when I started the simple implementation from the link, many of my automated tests broke. I decided to abandon using ActiveDirty at this point since my comparison logic works. It's too much trouble for a working application. – Pamela Cook - LightBe Corp Nov 18 '12 at 18:03
  • 2
    define_attribute_methods = [:admin] would set a *local* variable named define_attribute_methods. That's not what you want. You want to call the class-level method define_attribute_methods from ActiveModel. The link you gave shows that they got *rid of* the = in order to fix a typo. – Tyler Rick Jan 22 '13 at 20:56
  • 1
    Perhaps you should update your answer so that people don't have to look at the comments to notice your typo? :) – Brendon Muir Mar 26 '13 at 08:52