when do i need to explicitly save an object in rails - ruby-on-rails

Let's say I have a method
after_create :create_slug
def create_slug
self.slug = "#{model}-#{make}-#{year}"
end
def update_slug(user)
user.slug = "#{model}-#{make}-#{year}"
user.save!
end
I am confused when do I need to explicitly save an object.

When you want to persist it as is and it's in a dirty state.

Related

cannot update a new record on after_create callback

class Model < ActiveRecord::Base
after_create :set_slug
def set_slug
update_column(:slug, to_slug)
end
def to_slug
#code to create slug
end
end
Why does this return 'ActiveRecord::ActiveRecordError: cannot update a new record' if the callback is an after_create? The issue is with "update_column"
Your problem lies in the fact that update_columns doesn't work on new records.
Why not use update_attributes` instead
after_create do
self.update_attributes(slug: to_slug)
end
if you want then you can also try following approach
if new_record?
return
else
update_column(:slug, to_slug)
end
Also check the model side validations. That may also cause the problems.
There is ActiveRecord::Persistence::ClassMethods#update_columns method which contains line
raise ActiveRecordError, "cannot update a new record" if new_record?
therefore you should use update_all, for example:
def set_slug
self.class.where(id: id).update_all(slug: to_slug) if id
end
Hope it will help

Rails Service Object, adding errors to new Model

I recently had a rails model that had several callbacks on it like so:
class Model < ActiveRecord::Base
before_validation :fetch_posts
after_create :build_posts
def fetch_posts
fetch_collection
rescue MyException => e
self.errors.add(:post, e.message)
end
def build_posts
fetch_collection.each do |item|
DifferentModel.build(item)
end
end
def fetch_collection
#collection ||= method_that_fetches_collection_from_external_source
end
end
This was working just fine but it was making it extremely difficult to write tests, as whenever I wanted to create a Model I had to stub out all the callbacks. Enter service objects:
class ModelFetcher
attr_reader :model
def initialize(model)
#model = model
end
def save
model.fetch_posts
if model.save
model.build_posts
return true
else
return false
end
end
end
The problem I'm seeing now, in the case where a model does indeed contain an error (from the fetch posts method), it doesn't get carried over to the model.save call in the SO. That is to say, the Model.new has an error, but once I call .save on that Model.new it doesn't maintain the error and the model saves properly.
I considered adding validate :fetch_posts but then I am back in the same situation I was before as this is essentially a callback.
Any advice on how to structure this better? Is it possible to maintain an error from Model.new to .save? Am I fundamentally misunderstanding something?
Thanks!
Here is an alternate solution which is to overwrite run_validations! since you have none.
class Model < ActiveRecord::Base
after_create :build_posts
def fetch_posts
fetch_collection
rescue MyException => e
self.errors.add(:post, e.message)
end
def build_posts
fetch_collection.each do |item|
DifferentModel.build(item)
end
end
def fetch_collection
#collection ||= method_that_fetches_collection_from_external_source
end
private
def run_validations!
fetch_posts
errors.empty?
end
end
Usually this method looks like
def run_validations!
run_callbacks :validate
errors.empty?
end
but since you have no validations it should serve a similar purpose on #save.
Or as I suggested in a comment you can replace save with model.errors.any? Since save will clear your original errors set by fetch_posts but errors.any? Will check if there were errors during the fecth_posts method.

Reuse same object in other methods within same model

Using Rails 3.2. I have the following code:
# photo.rb
class Photo < ActiveRecord::Base
before_create :associate_current_user
after_save :increase_user_photos_count
after_destroy :decrease_user_photos_count
private
def associate_current_user
current_user = UserSession.find.user
self.user_id = current_user.id
end
def increase_user_photos_count
current_user = UserSession.find.user
User.increment_counter(:photos_count, current_user.id)
end
def decrease_user_photos_count
current_user = UserSession.find.user
User.decrement_counter(:photos_count, current_user.id)
end
end
Before a new record is created, it searches for the current_user. This is alright if it's just 1 new record at a time. But if there are 100 records to be created, it's gonna search for the same current_user 100 times. There is definitely performance issue.
I don't want it to keep finding the current user every time a record is created/photos_count updated, etc.
After refactoring, does this affect other users who are also uploading their photos using their accounts?
Note: For some reasons, I can't use the counter_cache and photos_controller.rb because I am following this example: http://www.tkalin.com/blog_posts/multiple-file-upload-with-rails-3-2-paperclip-html5-and-no-javascript
Thanks.
Use this
def current_user
#current_user ||= UserSession.find.user
end
This will cache the value in the instance variable #current_user unless it's nil (first time in the request), in which case it will set it.

Using after_find callback to overwrite the field values

I have rails 2.3.11. i want to overwrite one of the database field value.But its not overwrite.
def after_find
add_public_uri
end
def add_public_uri
self.uri = uri.to_s
end
Not sure about the syntax, but have you tried
after_find: add_public_uri
private
def add_public_uri
self.uri = uri.to_s
end
Alternatively, you could simply have a customer reader in your model:
def uri
uri.to_s
end

Add save callback to a single ActiveRecord instance, is it possible?

Is it possible to add a callback to a single ActiveRecord instance? As a further constraint this is to go on a library so I don't have control over the class (except to monkey-patch it).
This is more or less what I want to do:
def do_something_creazy
message = Message.new
message.on_save_call :do_even_more_crazy_stuff
end
def do_even_more_crazy_stuff(message)
puts "Message #{message} has been saved! Hallelujah!"
end
You could do something like that by adding a callback to the object right after creating it and like you said, monkey-patching the default AR before_save method:
def do_something_ballsy
msg = Message.new
def msg.before_save(msg)
puts "Message #{msg} is saved."
# Calls before_save defined in the model
super
end
end
For something like this you can always define your own crazy handlers:
class Something < ActiveRecord::Base
before_save :run_before_save_callbacks
def before_save(&block)
#before_save_callbacks ||= [ ]
#before_save_callbacks << block
end
protected
def run_before_save_callbacks
return unless #before_save_callbacks
#before_save_callbacks.each do |callback|
callback.call
end
end
end
This could be made more generic, or an ActiveRecord::Base extension, whatever suits your problem scope. Using it should be easy:
something = Something.new
something.before_save do
Rails.logger.warn("I'm saving!")
end
I wanted to use this approach in my own project to be able to inject additional actions into the 'save' action of a model from my controller layer. I took Tadman's answer a stage further and created a module that can be injected into active model classes:
module InstanceCallbacks
extend ActiveSupport::Concern
CALLBACKS = [:before_validation, :after_validation, :before_save, :before_create, :after_create, :after_save, :after_commit]
included do
CALLBACKS.each do |callback|
class_eval <<-RUBY, __FILE__, __LINE__
#{callback} :run_#{callback}_instance_callbacks
def run_#{callback}_instance_callbacks
return unless #instance_#{callback}_callbacks
#instance_#{callback}_callbacks.each do |callback|
callback.call
end
end
def #{callback}(&callback)
#instance_#{callback}_callbacks ||= []
#instance_#{callback}_callbacks << callback
end
RUBY
end
end
end
This allows you to inject a full set of instance callbacks into any model just by including the module. In this case:
class Message
include InstanceCallbacks
end
And then you can do things like:
m = Message.new
m.after_save do
puts "In after_save callback"
end
m.save!
To add to bobthabuilda's answer - instead of defining the method on the objects metaclass, extend the object with a module:
def do_something_ballsy
callback = Module.new do
def before_save(msg)
puts "Message #{msg} is saved."
# Calls before_save defined in the model
super
end
end
msg = Message.new
msg.extend(callback)
end
This way, you can define multiple callbacks, and they will be executed in the opposite order you added them.
The following will allow you to use an ordinary before_save construction, i.e. calling it on the class, only in this case, you call it on the instance's metaclass so that no other instances of Message shall be affected. (Tested in Ruby 1.9, Rails 3.13)
msg = Message.new
class << msg
before_save -> { puts "Message #{self} is saved" } # Here, `self` is the msg instance
end
Message.before_save # Calling this with no args will ensure that it gets added to the callbacks chain (but only for your instance)
Test it thus:
msg.save # will run the before_save callback above
Message.new.save # will NOT run the before_save callback above

Resources