How to ensure attribute is updated only once in Rails 3 Active Record? - ruby

Wondering what could be the best way to handle situations where I need to update particular column and once updated it should not allow user to update again.
I tried using changed? method which checks for record that has been changed and not yet saved. But this would not check a particular attribute in that row.
Any suggestions?
Thanks in advance.

rails g migration add_value_changed_to_YOUR_TABLE_NAME value_changed:boolean
set in the generated file
default: false
and run the migration.
And now, once the value is changed, update this value_changed to true:
before_update :update_value_changed, :check_value_changed
private
def update_value_changed
update(value_changed: true) unless value_changed
end
def check_value_changed
if value_changed
do_something_like_raise_error_or_do_not_save_changes
else
something_else
end
end

Let's assume that you have user class:
class User < ActiveRecord::Base
before_save :check_name_not_changed
private
def check_name_not_changed
if self.name.present? && self.name_changed?
errors.add(:name, "can not be updated")
end
end
end
I am assuming that the name attribute was some how set before.

Related

How to call after_update callback for a particular column updation?

I have one model called points and in that i have 2 columns named clicked_at and opened_at. Those two columns will not be entered while creating record. Both the columns will be updated manually. Now i want to call a callback only for updating clicked_at column. Is there any way to do this? Help will be appreciated.
You have to check it manually.
def my_callback
if clicked_at_changed?
clicked_at_did_change
end
end
def clicked_at_did_change
# do stuff
end
for this ..observers will be a good option and there are many options when you want to call your code..while updating/creating/deleting/editing/saving ...it goes on.
class PointObserver < ActiveRecord::Observer
##this method will be called everytime AFTER you update point object
def after_update(point)
point.clicked_at =Time.now
point.opened_at =Time.now
point.save!
end
end
enable this observer in application.rb
# Activate observers that should always be running.
config.active_record.observers = :point_observer
You can do whatever you want and also you have many callbacks such as before_save,before_update..etc.
Moreover you can place all your observers in app/observers/..

attr_accessor breaks 2 of my methods in Rails

I have a User model. One of its attributes is a string called :access which can be either nil, "admin", or "active".
Now inside the User model I have the following methods:
def admin?
self.access == "admin"
end
def active?
self.access == "active"
end
They work fine. But if I add attr_accessor :access to the model something breaks. My admin? and active? methods no longer work. When I go into rails console and get a User out of the database I can see that user = User.find(7) shows access is set to "admin". But if I type user.access it returns nil. user.admin? returns false.
attr_accessor is overwriting the default methods of reading and writing an attribute from db. Why do you need attr_accessor if it is already a column in db and Rails provides you with the read and write methods. Are you confusing it with attr_accessible?

creating stub for after_create hook in rspec

my model code is:
class User < ActiveRecord::Base
after_create :create_node_for_user
def create_node_for_user
UserGraph.create(user_id: self.id)
end
end
and test for User model:
it "create node in graph database on user creation" do
userr = FactoryGirl.build(:user)
UserGraph.should_receive(:create).with(user_id: userr.id)
userr.save
end
but my test is failing with message
Failure/Error: userr.save
<UserGraph (class)> received :create with unexpected arguments
expected: ({:user_id=>nil})
got: ({:user_id=>94})
what might be wrong?
The explanation given by Yves is correct: the user id is nil until the record is saved because it is autogenerated by the DB. Here's an alternate approach:
it "create node in graph database on user creation" do
userr = FactoryGirl.build(:user)
create_args = nil
UserGraph.should_receive(:create) { |*args| create_args = args }
userr.save
expect(create_args).to eq(:user_id => userr.id)
end
Essentially, this moves the expectation about what the arguments should be so that it comes after the record has been saved, when the record has an id.
The Problem is that the userr you build with FactoryGirl does not have an ID. Thats why the expectation tells you that you expected :user_id=>nil. The ID will be generated when AR saves the record, so there is no way that you can guess the generated ID ahead of time. You could use a less restrictive assertion on the mock:
UserGraph.should_receive(:create).with(hash_including(:user_id))
This will verify that a hash is passed with a :user_id key. You can find more about hash_including here: http://rubydoc.info/gems/rspec-mocks/RSpec/Mocks/ArgumentMatchers:hash_including
Another thing you can try (not sure if it works) is to match against the kind_of matcher of rspec. This would make sure that a number was passed with :user_id
UserGraph.should_receive(:create).with(:user_id => kind_of(Numeric))

How can I have a DataMapper callback happen in a transaction?

I'd like to be able to define a callback in DataMapper and have this happen in a transaction along with the accompanying update. For example:
class Comment
include DataMapper::Resource
has n, :updates
property :id, Serial
property :text, String
after :update do
self.updates.create(:text => self.text)
end
end
I think it's clear what the above code is trying to do: any time a Comment is updated, also create a corresponding Update record. Now, a possible scenario is that you could update a post, creating the update would fail—for whatever reason—and thus some history would be lost. So I would really like for this kind of operation to happen within a transaction.
Is this possible? I can think of some workarounds (e.g., defining a custom update method); but I'm curious to know if there's a "correct" way or if others can think of elegant approaches.
To archive the same with an testable and extendable design I suggest a service object like this:
class CommentUpdate
private_class_method :new
# Run comment update within transaction
def self.run(*args)
Comment.transaction do
new(*args)
end
end
# Initialize object
#
# #param [Comment]
# the comment to update
# #param [String]
# the text to set
#
def initialize(comment, text)
#comment, #text = comment, text
run
end
# Test if update was successful
def successful?
#comment.saved?
end
private
# Run update
def run
#comment.text = #text
if #comment.save
#comment.updates.create(:text => #text)
end
end
end
# usage
comment = Comment.first
update = CommentUpdate.run(comment, "Some new cool text")
update.successful? # Use this to steer your control flow...

Changing associated objects don't get save with rails model object?

having this code block of an example rails model class:
class Block < ActiveRecord::Base
has_many :bricks, :autosave => true
def crunch
bricks.each do |brick|
if brick.some_condition?
brick.name = 'New data'
brick.save # why do I have to call this?
end
end
end
end
class Brick < ActiveRecord::Base
belongs_to :block, :autosave => true
end
I found that the only way to make sure the changes within the associated objects get saved for me, was to call brick.save manually. Even thought I use :autosave => true
Why?
Probably the autosave option has a misleading name. By the way, it's the expected behaviour. The option is meant for association. So if you modify an object in a relation and save the other object then ActiveRecord saves the modified objects. So, in your case, you could change your code to:
def crunch
bricks.each do |brick|
if brick.some_condition?
brick.name = 'New data'
end
end
save # saving the father with autosave should save the children
end
You could use any of the helper methods available: update_attribute, update_attributes, update_column...
More info: Rails: update_attribute vs update_attributes

Resources