cannot update a new record on after_create callback - ruby-on-rails

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

Related

before_destroy callback on rails

I want to use before_destroy callback in my rails app. I want to use it because I use it on notification. But how can I use before_destroy?
I have my code here for before_save and after_save but I don't know what will I do for before_destroy.
class Record < ActiveRecord::Base
# Callbacks
before_save {
#is_new_record = self.new_record? if self.new_record?
}
after_save {
action = #is_new_record ? 'created' : 'updated'
Notification.publish_notification(self, action)
}
...
#Some stuffs here
UPDATE
Actually, I want to save logs for my table Notification just like when creating new record.
Like,
User One created new Record.
So I want to save on notification before I destroy a record. Just like.
User One destroy a record.
Just like that.
Please help me.
I prefer to use methods instead of the anonymous blocks. I would handle this as follows:
class Record < ActiveRecord::Base
before_save :remember_new_record
after_save :write_save_notification
before_destroy :write_destroy_notification
# ... the rest of your class ...
private
def remember_new_record
#is_new_record = self.new_record?
end
def write_save_notification
action = #is_new_record ? 'created' : 'updated'
Notification.publish_notification(self, action)
end
def write_destroy_notification
Notification.publish_notification(self, 'destroy')
end
end
So it is simple as that, unless you ment something else?

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.

after_save callback to set the updated_by column to the current_user

I would like to use an after_save callback to set the updated_by column to the current_user. But the current_user isn't available in the model. How should I do this?
You need to handle it in the controller. First execute the save on the model, then if successful update the record field.
Example
class MyController < ActionController::Base
def index
if record.save
record.update_attribute :updated_by, current_user.id
end
end
end
Another alternative (I prefer this one) is to create a custom method in your model that wraps the logic. For example
class Record < ActiveRecord::Base
def save_by(user)
self.updated_by = user.id
self.save
end
end
class MyController < ActionController::Base
def index
...
record.save_by(current_user)
end
end
I have implemented this monkeypatch based on Simone Carletti's advice, as far as I could tell touch only does timestamps, not the users id. Is there anything wrong with this? This is designed to work with a devise current_user.
class ActiveRecord::Base
def save_with_user(user)
self.updated_by_user = user unless user.blank?
save
end
def update_attributes_with_user(attributes, user)
self.updated_by_user = user unless user.blank?
update_attributes(attributes)
end
end
And then the create and update methods call these like so:
#foo.save_with_user(current_user)
#foo.update_attributes_with_user(params[:foo], current_user)

How to get the current_user in a model observer?

Given the following models:
Room (id, title)
RoomMembers (id, room_id)
RoomFeed, also an observer
When a Room title is updated, I want to create a RoomFeed item, showing who the user is who made the update.
#room.update_attributes(:title => "This is my new title")
Problem is in my observer for RoomFeed:
def after_update(record)
# record is the Room object
end
The is no way for me to get the user.id of the person who just made the update. How do I go about doing that? is there a better way to do the update so I get the current_user?
I think what you are looking for is, room.updated_by inside your observer. If you don't want to persist the updated_by, just declare it as an attr_accessor. Before you push the update, make sure you assign the current_user to updated_by, may be from you controller.
This is a typical "separation of concern" issue.
The current_user lives in the controller and the Room model should know nothing about it. Maybe a RoomManager model could take care of who's changing the name on the doors...
Meanwhile a quick & dirty solution would be to throw a (non persistant) attribute at Room.rb to handle the current_user....
# room.rb
class Room
attr_accessor :room_tagger_id
end
and pass your current_user in the params when updating #room.
That way you've got the culprit! :
def after_update(record)
# record is the Room object
current_user = record.room_tagger_id
end
Create the following
class ApplicationController
before_filter :set_current_user
private
def set_current_user
User.current_user = #however you get the current user in your controllers
end
end
class User
...
def self.current_user
##current_user
end
def self.current_user= c
##current_user = c
end
...
end
Then use...
User.current_user wherever you need to know who is logged in.
Remember that the value isn't guaranteed to be set when your class is called from non-web requests, like rake tasks, so you should check for .nil?
I guess this is a better approach
http://rails-bestpractices.com/posts/47-fetch-current-user-in-models
Update user.rb
class User < ActiveRecord::Base
cattr_accessor :current
end
Update application_controller.rb
class ApplicationController
before_filter :set_current_user
private
def set_current_user
User.current = current_user
end
end
Then you can get logged user by User.current anywhere. I'm using this approach to access user exactly in observers.

How can I disallow updates except for on one field?

I've been preventing updates to certain models by using this in the model:
def update
self.errors.add_to_base( "Cannot update a #{ self.to_s }" )
end
I'm now writing a plugin that delivers some extra functionality to the model, and I need to update one field in the model. If I weren't using a plugin I would do this directly in the model...
def update
if self.changed == ['my_field']
super
else
self.errors.add_to_base( "Cannot update a #{ self.to_s }" )
end
end
I can't do the same from my plugin since I don't know if the update behaviour is the ActiveRecord default, or has been overridden to prevent updates. Is there another way to prevent record updates while allowing me to override for a specific field (and only in the instance where my plugin is applied to this model).
First, you should be using a before_update callback for that sort of thing rather than overriding update. Second, you can store the updatable attributes on the model, and then update them with the plugin. I just wrote this in the browser, so it could be wrong.
attr_accessor :updatable_attributes
before_update :prevent_update
private
def prevent_update
return true if self.changed == self.updatable_attributes
self.errors.add_to_base "Cannot update a #{ self.to_s }"
false
end
end
Late to the game here, but for people viewing this question, you can use attr_readonly to allow writing to a field on create, but not allowing updates.
See http://api.rubyonrails.org/classes/ActiveRecord/ReadonlyAttributes/ClassMethods.html
I think it has been available since Rails 2.0
The tricky part is, if you have any attributes that are attr_accessible you have to list your read only attributes there also (or you get a mass assignment error on create):
class Post < ActiveRecord::Base
attr_readonly :original_title
attr_accessible :latest_title, :original_title
end
Is this to prevent mass assignment? Would attr_accessible / attr_protected not do what you need?
Edit, just to illustrate the general point about the callback.
module MyModule
def MyModule.included(base)
base.send :alias_method_chain, :prevent_update, :exceptions
end
def prevent_update_with_exceptions
end
end
class MyModel < ActiveRecord::Base
before_validation :prevent_update
def prevent_update
end
include MyModule
end
I just use the rails params.require method to whitelist attributes that you want to allow.
def update
if #model.update(update_model_params)
render json: #model, status: :ok
else
render json: #model.errors, status: :unprocessable_entity
end
end
private
def update_prediction_params
params.require(:model).permit(:editable_attribute)
end

Resources