RoR callback after_create - ruby-on-rails

I have 2 models User and Profile
class User < ApplicationRecord
has_one :profile
after_create :create_profile
end
class Profile < ApplicationRecord
validates :first_name, :last_name, presence: true
belongs_to :user
end
My problem is that
after_create: create_profile
doesn't work, because does not pass validation. Does rail have something after_create: create_profile! or after_create (validate: false): create_profile to skip validation?

After_create will work in controller not in model.
create a method in controller which gets called on after_create action.
in that method call the Model method to create a profile.
Its not good to skip the validations but if you want to, You can still do it.
In the profile model, you can add a flag to skip the validation on call from this method.

after_create :create_profile!
private
def create_profile!
profile = Profile.new(user_id: self.id)
profile.save(validate: false)
end
I tried to do this.
Another solution that has occurred is to set default values to the database.

Related

Association in a custom validation method

I made the following class, but it didn't work correctly.
class User < ActiveRecord::Base
validate :validate_department_code, on: :create
def validate_department_code
if self.department_code.present?
department = Department_code.find_by_name(self.department_code)
user.build_participation(department: department)
else
self.errors.add(:department_code, :not_found)
end
end
end
After validation, the user is saved in DB, but the participation is not saved. So user.participation becomes nil.
How can I solve the problem?
In User.rb, add an after_create on a condition.
It should look like:
after_create :create_participation, if: :department_code.present?

Rails 4 + Devise: After create user, create XYZ

I have a User model that gets created through Devise, and after it's creation, I would like to automatically create a new Client (another model in my app). The new Client's atrribute, :user_id, should be equal to the :id of the User that was just created. I believe I need to use something like:
class Users::RegistrationsController < Devise::RegistrationsController
after_create :create_client
def create_client
Client.create(:user_id, :id) # Not sure what should go here
end
end
Is this the correct way to accomplish this? Also, if associations are important Client belongs_to :user and User has_one :client
You can add an after_create callback in User model(user.rb), check here for more information on how to create has_one associations.
class User < ActiveRecord::Base
after_save :add_client
def add_client
self.create_client(client_attribute1: value, client_attribute2: value)
end
end

Prevent parent object to be saved when saving child object fails

I have two associated classes like this:
class Purchase < ActiveRecord::Base
has_many :actions
before_create do |p|
self.actions.build
end
end
class Action < ActiveRecord::Base
belongs_to :purchase
before_save do |a|
false
end
end
The block in the Action class prevents it from saving. I was thinking doing Purchase.create will fail because it cannot save the child object. But while it does not save the Action, it commits the Purchase. How can i prevent the parent object to be saved when there is an error in the child object?
It turns out you have to rollback the transaction explicitly, errors from the child objects does not propagate. So i ended up with:
class Purchase < ActiveRecord::Base
has_many :actions
after_create do |p|
a = Action.new(purchase: p)
if !a.save
raise ActiveRecord::Rollback
end
end
end
class Action < ActiveRecord::Base
belongs_to :purchase
before_save do |a|
false
end
end
Take note that i also changed the before_create callback to after_create. Otherwise, since belongs_to also causes the parent to be saved, you will get a SystemStackError: stack level too deep.
I ran into this problem when dealing with race conditions where the child objects would pass a uniqueness validation, but then fail the database constraint (when trying to save the parent object), leading to childless (invalid) parent objects in the database.
A slightly more general solution to the one suggested by #lunr:
class Purchase < ActiveRecord::Base
has_many :actions
after_save do
actions.each do |action|
raise ActiveRecord::Rollback unless action.save
end
end
end
class Action < ActiveRecord::Base
belongs_to :purchase
before_save do |a|
false
end
end
Try to use this code in Purchase class:
validate :all_children_are_valid
def all_children_are_valid
self.actions.each do |action|
unless action.valid?
self.errors.add(:actions, "aren't valid")
break
end
end
end
Or use validates_associated in Purchase class:
validates_associated :actions
If in your business logic you can't save purchase without any action, then add a presence validator on actions inside purchases
validates :actions, length: {minimum: 1}, presence: true

Create two models at same time with validation

I'm having a potluck where my friends are coming over and will be bringing one or more food items. I have a friend model and each friend has_many food_items. However I don't want any two friends to bring the same food_item so food_item has to have a validations of being unique. Also I don't want a friend to come (be created) unless they bring a food_item.
I figure the best place to conduct all of this will be in the friend model. Which looks like this:
has_many :food_items
before_create :make_food_item
def make_food_item
params = { "food_item" => food_item }
self.food_items.create(params)
end
And the only config I have in the food_item model is:
belongs_to :friend
validates_uniqueness_of :food_item
I forsee many problems with this but rails is telling me the following error: You cannot call create unless the parent is saved
So how do I create two models at the same time with validations being checked so that if the food_item isn't unique the error will report properly to the form view?
How about to use nested_attributes_for?
class Friend < ActiveRecord::Base
has_many :food_items
validates :food_items, :presence => true
accepts_nested_attributes_for :food_items, allow_destroy: true
end
You're getting the error because the Friend model hasn't been created yet since you're inside the before_create callback. Since the Friend model hasn't been created, you can't create the associated FoodItem model. So that's why you're getting the error.
Here are two suggestions of what you can do to achieve what you want:
1) Use a after_create call back (I wouldn't suggest this since you can't pass params to callbacks)
Instead of the before_create you can use the after_create callback instead. Here's an example of what you could do:
class Friend
after_create :make_food_item
def make_food_item
food_params = # callbacks can't really take parameters so you shouldn't really do this
food = FoodItem.create food_params
if food.valid?
food_items << food
else
destroy
end
end
end
2) Handle the logic creation in the controller's create route (probably best option)
In your controller's route do the same check for your food item, and if it's valid (meaning it passed the uniqueness test), then create the Friend model and associate the two. Here is what you might do:
def create
friend_params = params['friend']
food_params = params['food']
food = FoodItem.create food_params
if food.valid?
Friend.create(friend_params).food_items << food
end
end
Hope that helps.
As mentioned, you'll be be best using accepts_nested_attributes_for:
accepts_nested_attributes_for :food_items, allow_destroy: true, reject_if: reject_if: proc { |attributes| attributes['foot_item'].blank? }
This will create a friend, and not pass the foot_item unless one is defined. If you don't want a friend to be created, you should do something like this:
#app/models/food_item.rb
Class FootItem < ActiveRecord::Base
validates :[[attribute]], presence: { message: "Your Friend Needs To Bring Food Items!" }
end
On exception, this will not create the friend, and will show the error message instead

Rails Active Record Callbacks error in nested association

I'm having trouble with Active Record callbacks in a model which contains accepts_nested_attributes
I call build_associated_partiesusing an after_create callback, but these values are not being saved and I get <nil> errors. I've also tried using before_create & after_initialize callbacks without success.
What is causing the callbacks to fail?
connection.rb
class Connection < ActiveRecord::Base
attr_accessible :reason, :established, :connector, :connectee1,
:connectee2, :connectee1_attributes,
:connectee2_attributes, :connector_attributes
belongs_to :connector, class_name: "User"
belongs_to :connectee1, class_name: "User"
belongs_to :connectee2, class_name: "User"
accepts_nested_attributes_for :connector, :connectee1, :connectee2
belongs_to :permission
after_create :build_associated_parties
# builds connectee's, connector, permission objects
def build_associated_parties
build_connector
build_connectee1
build_connectee2
build_permission
end
connection_controller.rb
class ConnectionsController < ApplicationController
def new
#connection = Connection.new
end
def create
#connection = Connection.new params[:connection]
if #connection.save
flash[:notice] = "Connection created successfully!"
redirect_to #connection
else
render :new
end
end
end
However, if I instead build these attributes inside the controller as shown below, I don't get the error. This is nice, but it seems to go against keeping business logic code out of the controller.
class ConnectionsController < ApplicationController
def new
#connection = Connection.new
#connection.build_connectee1
#connection.build_connectee2
#connection.build_connector
end
end
How can I accomplish the same functionality with code in the model? Are there advantages to keeping it in the model?
You called your method build_associated_parties after connection is created, so how these methods:
build_connector
build_connectee1
build_connectee2
build_permission
know what params it will use? So they don't know what values are passed into method then they will get error. In controller, they didn't have error because they used values of params[:connection].
On your form, if you already have fields for connector, connectee1, connectee2, you should put code which initialize object in your new controller. When you save #connection, it's saved those object too. I think these codes aren't need to put into model. Your model only should put other logic code, like search or calculation...
after_create is a big no. Use after_initialize in your model and use self inside your build_associated_parties method. See if that works.
Moved logic out of controller and back into model. However, the build_* code was overwriting the values I was passing into the nested attributes.
By adding unless {attribute} to these build_ methods, I was able to properly pass in the values.
class Connection < ActiveRecord::Base
attr_accessible :reason, :established, :connector, :connectee1, :connectee2,
:connectee1_attributes, :connectee2_attributes, :connector_attributes
belongs_to :connector, class_name: "User"
belongs_to :connectee1, class_name: "User"
belongs_to :connectee2, class_name: "User"
accepts_nested_attributes_for :connector, :connectee1, :connectee2
belongs_to :permission
after_initialize :build_associated_parties
validates :reason, :presence => true
validates_length_of :reason, :maximum => 160
#builds connectee's, connector, permission objects
def build_associated_parties
build_connector unless connector
build_connectee1 unless connectee1
build_connectee2 unless connectee2
end

Resources