Rails: Save data in two tables - ruby-on-rails

I have a user table and a setting table with 1-1 relationship. I want to insert some default setting for the newly created user. I am thinking to use after_create callback of user. However, I am not sure if this will be transactional. What is the best approach for this condition?

You may find after_initialize callback useful for building the setting object for user and assigning default setting. Example:
class User < ActiveRecord::Base
has_one :setting
after_initialize :init_user_setting
private
def init_user_setting
# Assign default setting or build
self.setting = ...
end
end
With this you'll have your complete parent user including child setting. When you call user.save both user and setting are saved and both happen inside a transaction.

You can use after_create callback or observer both. Both will be okay. But You should also set your default values for settings in a initialize method for attribute assignment. Or you can also use user's create method to do same after save call. But it's not a good way. So prefer either call_back or observer.

--UPDATE--
As you start registering new callbacks for your models, they will be queued for execution. This queue will include all your model's validations, the registered callbacks, and the database operation to be executed.
The whole callback chain is wrapped in a transaction. If any before callback method returns exactly false or raises an exception, the execution chain gets halted and a ROLLBACK is issued; after callbacks can only accomplish that by raising an exception.
After setting an attribute to false at the end of a before_save callback, I could not for the life of me figure out why the object would never save! Before callbacks must return truthy values or it will rollback.
--Previous--
You need to do something like this. This is my default approach for anything short of dealing with currency.
class User < ActiveRecord::Base
has_one :setting
after_create :setup_user
private
def setup_user
user_settings = self.setting.new
user_settings.attr1 = foo1
user_settings.attr2 = foo2
user_settings.save
end
end
class Setting < ActiveRecord::Base
belongs_to :user
end
Ideally you would put validations on the user and make it so that if a user is valid a setting is also valid. But if you don't do that, you need to use
if !user_settings.save
self.destroy
end
For where to put the default values,if the setting default values depend on the user, stick them in the setup_user method. If the setting default values doesn't care about the user, stick them in a before_save or before_validation method on the setting. With regards to the user, you need to use an after_create method for the case where a user doesn't validate, you want those callbacks to only fire if a user is successfully created. You don't want to user after_validation because the user would not have been created and if the setting contains a foreign id to the user that is not yet created, for an instant your database will be inconsistent.
Transaction based approach
class User < ActiveRecord::Base
has_one :setting
after_commit :setup_user, on: [:create]
after_rollback :undo_user, on: [:create]
private
def setup_user
user_settings = self.setting.new
user_settings.attr1 = foo1
user_settings.attr2 = foo2
user_settings.save!
end
def undo_user
#The users settings didn't save so roll back the user
self.destroy
end
end
class Setting < ActiveRecord::Base
belongs_to :user
end
To put it in the same transaction you need to use after_commit and after_rollback. Using .save! will throw an exception and trigger after_rollback. You will not have a user without the setting.

Related

callback on associated has_many not working

Seems like i’ve gone back to basics and am missing something… ..
I have two models User and Lists. User has many lists and lists belongs to User… When we remove the user from the list, i.e. the lists user_id == nil, we can’t seem to catch the update through a callback like after_update or after_commit.
class List < ApplicationRecord
after_update :check_user
belongs_to :user
private
def check_user
binding.pry
if user_id.blank?
end
end
end
we even tried before_save but it doesn’t seem like the model sees the change. Are we missing something obvious??
What method are you using to update the record? Because there are methods that DO NOT trigger callbacks like update_column, update_attribute and update_all

What is the difference between `before_create` and `after_create` and when to use which?

I know that before_create is called before the object gets commuted to the database and after_create gets called after.
The only time when before_create will get called and after_create while not is if the object fails to meet data base constants (unique key, etc.). Other that that I can place all the logic from after_create in before_create
Am I missing something?
In order to understand these two callbacks, firstly you need to know when these two are invoked. Below is the ActiveRecord callback ordering:
(-) save
(-) valid
(1) before_validation
(-) validate
(2) after_validation
(3) before_save
(4) before_create
(-) create
(5) after_create
(6) after_save
(7) after_commit
you can see that before_create is called after after_validation, to put it in simple context, this callback is called after your ActiveRecord has met validation. This before_create is normally used to set some extra attributes after validation.
now move on to after_create, you can see this is created after the record is stored persistently onto DB. People normally use this to do things like sending notification, logging.
And for the question, when should you use it? The answer is 'you should not use it at all'. ActiveRecord callbacks are anti-pattern and seasoned Rails developer consider it code-smell, you can achieve all of that by using Service object to wrap around. Here is one simple example:
class Car < ActiveRecord::Base
before_create :set_mileage_to_zero
after_create :send_quality_report_to_qa_team
end
can be rewritten in
# app/services/car_creation.rb
class CarCreation
attr_reader :car
def initialize(params = {})
#car = Car.new(params)
#car.mileage = 0
end
def create_car
if car.save
send_report_to_qa_team
end
end
private
def send_report_to_qa_team
end
end
If you have simple app, then callback is okay, but as your app grows, you will be scratching your head not sure what has set this or that attribute and testing will be very hard.
On second thought, I still think you should extensively use callback and experience the pain refactoring it then you'll learn to avoid it ;) goodluck
The before_create callback can be used to set attributes on the object before it is saved to the database. For example, generating a unique identifier for a record. Putting this in an after_create would require another database call.
before_create:
will be called before saving new object in db. When this method will return false it will prevent the creation by rolling back.
So when you need to do something like check something before saving which is not appropriate in validations you can use them in before_create.
For example: before creation of new Worker ask Master for permission.
before_create :notify_master
def notify_master
# notify_master via ipc and
# if response is true then return true and create this successfully
# else return false and rollback
end
Another use is as Trung Lê suggested you want to format some attribute before saving
like capitalizing name etc.
after_create:
Called after saving object in database for first time. Just when you don't want to interrupt creation and just take a note of creation or trigger something after creation this is useful.
for example: After creating new user with role mod we want to notify other mods
after_create :notify_mod, :is_mod?
def notify_mod
# send notification to all other mods
end
EDIT: for below comment
Q: What's the advantage of putting notify_mod in after_create instead of before_create?
A: Sometimes while saving the object in database it can rollback due to database side validations or due to other issues.
Now if you have written notify_mod in before create then it will be processed even if the creation is not done. No doubt it will rollback but it generates overhead. so it's time consuming
If you have placed it in after_create then notify_mod will only execute if the record is created successfully. Thus decreasing the overhead if the rollback takes places.
Another reason is that it's logical that notification must be sent after user is created not before.

Rails: set default record if none assigned or if relation removed

I've got a model called Brand, on which several things rely including in this example a model called User. If a Brand is deleted then a lot of things will fail. What's the best way to set a default Brand for all its relationships in the event that a Brand is deleted?
I thought writing stuff like this might work:
class User < ActiveRecord::Base
after_save :assign_to_default_brand, :if => :not_branded?
def not_branded?
!self.brand_id?
end
def assign_to_default_brand
self.brand_id = Brand.first
end
end
But it doesn't seem to behave the way I want it to. Is there a best-practice established here? Cheers.
UPDATED
I've thrown a default boolean onto Brand and written this but again it seems to have no effect. Am I missing something?
class Brand < ActiveRecord::Base
after_save :assign_users_to_default
def assign_users_to_default
self.users.all.each { |user| user.brand_id = Brand.where(:default => true).first.id if user.not_branded? }
end
end
It should be a before_save instead of after_save That way the value will be persisted to the database when the instance is saved.
For deletion on a brand you could use after_destroy
class Brand
after_destroy :switch_assigned_users
def switch_assigned_users
User.where(:brand_id => id).update_all(:brand_id => Brand.first)
end
end
This finds all users that assigned to that brand and switches them to the first one.

What is a call back method in rails?

What the heck is a call back method in rails? I see this term being used everywhere while I learn about controllers and models. Can someone provide examples please?
Ref ActiveRecord::Callbacks for the Callbacks w.r.to Activerecord
Callbacks are hooks into the lifecycle of an Active Record object that allow you
to trigger logic before or after an alteration of the object state. This can be
used to make sure that associated and dependent objects are deleted when destroy
is called (by overwriting before_destroy) or to massage attributes before they‘re
validated (by overwriting before_validation). As an example of the callbacks
initiated, consider the Base#save call for a new record
Take an example you have a Subscription model and you have a column signed_up_on which will contains the date at which subscription is created. For this w/o Callbacks you can do something like following in your controller.
#subscription.save
#subscription.update_attribute('signed_up_on', Date.today)
Which will perfectly fine but if suppose you have 3-4 methods in your application where subscription is get create. So to achieve it you have repeat the code in all the places which is redundant.
To avoid this you can use Callbacks and before_create Callback here. So whenever your object of subscription is get create it will assign today's date to signed_up_on
class Subscription < ActiveRecord::Base
before_create :record_signup
private
def record_signup
self.signed_up_on = Date.today
end
end
Following is the list of all the Callbacks
after_create
after_destroy
after_save
after_update
after_validation
after_validation_on_create
after_validation_on_update
before_create
before_destroy
before_save
before_update
before_validation
before_validation_on_create
before_validation_on_update

Rails - Best-Practice: How to create dependent has_one relations

What's the best practice to create has_one relations?
For example, if I have a user model, and it must have a profile...
How could I accomplish that?
One solution would be:
# user.rb
class User << ActiveRecord::Base
after_create :set_default_association
def set_default_association
self.create_profile
end
end
But that doesn't seem very clean... Any suggestions?
Best practice to create has_one relation is to use the ActiveRecord callback before_create rather than after_create. Or use an even earlier callback and deal with the issues (if any) of the child not passing its own validation step.
Because:
with good coding, you have the opportunity for the child record's validations to be shown to the user if the validations fail
it's cleaner and explicitly supported by ActiveRecord -- AR automagically fills in the foreign key in the child record after it saves the parent record (on create). AR then saves the child record as part of creating the parent record.
How to do it:
# in your User model...
has_one :profile
before_create :build_default_profile
private
def build_default_profile
# build default profile instance. Will use default params.
# The foreign key to the owning User model is set automatically
build_profile
true # Always return true in callbacks as the normal 'continue' state
# Assumes that the default_profile can **always** be created.
# or
# Check the validation of the profile. If it is not valid, then
# return false from the callback. Best to use a before_validation
# if doing this. View code should check the errors of the child.
# Or add the child's errors to the User model's error array of the :base
# error item
end
Your solution is definitely a decent way to do it (at least until you outgrow it), but you can simplify it:
# user.rb
class User < ActiveRecord::Base
has_one :profile
after_create :create_profile
end
If this is a new association in an existing large database, I'll manage the transition like this:
class User < ActiveRecord::Base
has_one :profile
before_create :build_associations
def profile
super || build_profile(avatar: "anon.jpg")
end
private
def build_associations
profile || true
end
end
so that existing user records gain a profile when asked for it and new ones are created with it. This also places the default attributes in one place and works correctly with accepts_nested_attributes_for in Rails 4 onwards.
Probably not the cleanest solution, but we already had a database with half a million records, some of which already had the 'Profile' model created, and some of which didn't. We went with this approach, which guarantees a Profile model is present at any point, without needing to go through and retroactively generate all the Profile models.
alias_method :db_profile, :profile
def profile
self.profile = Profile.create(:user => self) if self.db_profile.nil?
self.db_profile
end
Here's how I do it. Not sure how standard this is, but it works very well and its lazy in that it doesn't create extra overhead unless it's necessary to build the new association (I'm happy to be corrected on this):
def profile_with_auto_build
build_profile unless profile_without_auto_build
profile_without_auto_build
end
alias_method_chain :profile, :auto_build
This also means that the association is there as soon as you need it. I guess the alternative is to hook into after_initialize but this seems to add quite a bit of overhead as it's run every time an object is initialized and there may be times where you don't care to access the association. It seems like a waste to check for its existence.
There is a gem for this:
https://github.com/jqr/has_one_autocreate
Looks like it is a bit old now. (not work with rails3)
I had an issue with this and accepts_nested_attributes_for because if nested attributes were passed in, the associated model was created there. I ended up doing
after_create :ensure_profile_exists
has_one :profile
accepts_nested_attributes_for :profile
def ensure_profile_exists
profile || create_profile
end
If you need the has_one association to exist before saving the object (when testing, for instance), you should use the after_initialize callback instead. Here is how it could be applied to your use case:
class User < ActiveRecord::Base
has_one :profile
after_initialize :build_profile, unless: :profile
end

Resources