Wonderful Monday(?)
*All characters appearing in this article are fictitious
9:00 AM
Me「It’s Monday already. I really don’t feel like working」
Me「Unfortunatelly, it can not be helped. Gotta check Slack…」
Slack「You received a message」
Me「Here we go… Let me see this message…」
…
Me「It seems I have a User and Profile models, and need to update the Profile before updating the User」
Me「Well, let’s dive in…」
The Code
Me「Ok, we’ll start by checking the code to understand what I have to do:」
class User < ApplicationRecord
has_one :profile, dependent: :destroy # User has one Profile
before_update :update_profile # and I have to implement this callback
...
def update_profile
# TODO
end
end
Me「Let’s see the message again, to understand exactly what I should do here:」
Mr. PM「Should check if either email or password will change. If so, we should set a flag」
Mr. PM「And btw, I’ll be offline for some beer」
Me「Wow, beer in the morning…」
Me「Anyways, let’s keep going…」
Let’s check these Modules
Me「First of all, I’ll need to search for something to help me with this feature」
3 hours later
Me「Humm, these modules ActiveModel::Dirty and ActiveRecord::AttributeMethods::Dirty are interesting. Let’s play a little:」
user = User.new
=> #<User id: nil, created_at: nil, updated_at: nil, name: nil, email: nil>
user.changed?
=> false # Cool, it checks if the object user has changed
user.name = "Harry" # Let's add a name
=> "Harry"
user.changed?
=> true # Good, It shows changed
Me「Interesting… let’s try more of these methods」
Aggressively Reading ActiveModel::Dirty Docs…
user = User.new
=> #<User id: nil, created_at: nil, updated_at: nil, name: nil, email: nil>
user.changed?
=> false
user.name = "Harry"
=> "Harry"
user.changed?
=> true
user.changed # Nice, it shows the attributes with changes
=> ["name"]
user.name_changed? # This checks if the attribute has been changed
=> true
user.email_changed? # email has not changed to it should be false:
=> false
user.name_change # This shows an array with the changes for the attribute:
=> [nil, "Harry"]
user.name = "Steven"
=> "Steven"
user.name_change # Wait, shouldn't it be ["Harry", "Steven"]?
=> [nil, "Steven"]
user.name_was # Humm, and wasn't this supposed to be "Harry"??Weird...
=> nil
user.save!
=> true
user.name = "John"
=> "John"
user.name_change # Ohh, I think I got it! I had to save the object:
=> ["Steven", "John"]
user.name_was # Now it makes sense:
=> "Steven"
Me「So, recapping what I learned:」
changed?
=> Check if the object has changed
changes => See all the changes to the model’s attributes
[attr_name]_changed? => Check if the attribute has been changed
attribute_was => Attribute before the change
Me「It’s a little complicated…」
Me「Those methods seem to be very reliant on data from the database」
Me「There are many other methods to try, but these might be just what I need…」
Implementing the feature
Me「Finally…」
Me「Time to get the work done!」
Me「Let’s see the code again:」
class User < ApplicationRecord
has_one :profile, dependent: :destroy
before_update :update_profile
...
def update_profile
# TODO
end
end
Me「So, What I need to do is to set email and password flags to the Profile model」
Me「"What are these flags going to do inside the Profile?", you may ask… I have no idea, hehe」
Me「It looks like I can use [attr_name]_changed?
to send a boolean flag.」
Me「However, ActiveRecord::AttributeMethods::Dirty
seems to offer a more idiomatic alternative called: will_save_changeto[attr_name]?
」
Me「This should make the code more understandable」
class User < ApplicationRecord
has_one :profile, dependent: :destroy
before_update :update_profile
...
def update_profile
attributes = {
email_flag: will_save_change_to_email?, # check if the email has changed
password_flag: will_save_change_to_password? # check if the password has changed
}
profile.update!(attributes) # update the profile with the flags
end
end
Me「Yep, I did it!」
Me「Let’s add this change (with some tests, of course) to a new commit and push the changes」
Me「Done! Now, I should go out for some beers, too!」
Afterstory
Me「These modules I discovered are really useful. There are so many other methods that I can use!」
Me「Next time I need to deal with this kind of problem, I have a solution!」
Me「BTW, I can’t forget to send a message to PM saying I finished the feature:」
Me「Hey! I’ve finished the feature」
Mr. PM「Great!」
Mr. PM「And I have news for you」
Me「What news?」
Mr. PM「We won’t need this feature anymore…」
Me「!?」
Reference:
ActiveModel::Dirty – Rails API
ActiveRecord::AttributeMethods::Dirty – Rails API
We want to work with you. Check out our "What We Do" section!