User is a nested attribute of Announcement. When a new announcement is created, it will create a new user if its email is not found. Otherwise it should just post announcement into the existing user's record.
I am not sure which callback to use before_create or before_save. The following code still does not allow new announcement to be posted into the existing user record. A complete newbie, please help.
class Announcement < ActiveRecord::Base
attr_accessible :content, :users_attributes
has_many :users, :through => :awards
accepts_nested_attributes_for :users, :reject_if => lambda { |a| a[:email].blank? }, :allow_destroy => true
before_save :find_user
private
def find_user(user)
If User.find(params[:email]).nil?
#user = User.new
#user.save
else
#user = User.find(params[:email])
end
end
You do not need to use any callbacks at all. Rails handles creating and deleting of nested attributes automatically which makes the accepts_nested_attributes_for macro so great. You should not have to manually save, create, or delete except in your controller when you call new and update_attributes. You may be having problems because your awards association should be defined before your users association. In your code sample I don't see any definition for an awards association even though your users association is through awards. Otherwise I would check your controller and view code. A common problem I have is passing the wrong parameters from the view (make sure you use the fields_for helper in the view).
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Related
I'm trying to use an AR callback (after_save) to check, if an association was added on the latest 'update' or 'create'. However, I can't seem to find the correct way to do it.
class Submission < ActiveRecord
has_many :notes, inverse_of: :submission, dependent: :destroy
accepts_nested_attributes_for :notes, :reject_if => proc { |attributes| attributes['message'].blank? }, allow_destroy: true
end
Here is my after_save
after_save :build_conversation
in that method I want to see, if a new note table was added on update or create...
def build_conversation
if self.notes.any?
binding.pry
end
end
This logic does not make sense, because if notes can exist, which is fine. Nevertheless, I only want to enter this block, if there is a new note added on update or create...
Check out this post. Basically, you add include ActiveModel::Dirty in the model, then in the after_change callback you check if_notes_changed. This method is defined using method_missing so for example if you have a name column you can use if_name_changed and so on. If you need to compare the old vs new values, you can use previous_changes.
Alternatively, you can use around_save like so:
around_save :build_conversation
def build_conversation
old_notes = notes.to_a # the self in self.notes is unnecessary
yield # important - runs the save
new_notes = notes.to_a
# binding.pry
end
I am building a very simple application managing users and keys. Keys are nested attributes of a user. I am inspired by RailsCast #196.
The models are defined as:
class User < ApplicationRecord
#Validations
---
#Relations
has_many :keys, :dependent => :destroy
accepts_nested_attributes_for :keys, :reject_if => :all_blank, :allow_destroy => true
end
class Key < ApplicationRecord
#Validations
---
#Relations
belongs_to :user
end
The users controller includes strong parameters:
def user_params
params.require(:user).permit(:nom, :prenom, :section, :email, :password, keys_attributes: [:id, :secteur, :clef])
end
And I wish to initialize 1 key for each new user (users controller):
# GET /users/new
def new
#user = User.new
key = #user.keys.build({secteur: "Tous", clef: "ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678"})
end
I tried many ways to initialize the user's key, but I can't figure out how to pass parameters to it. User is always created, but the first key is not. No error message is issued.
Remove the '{' tags inside the build method parameters. Should be:
key = #user.keys.new(secteur: "Tous", clef: "ABCDEFGHIJKLMNOPQRSTUVWXYZ12345678")
Also, the build method is just an alias for 'new' and used to behave differently on rails 3 apps so I've always strayed away from it and just use the more familiar 'new' method. Obviously don't forget to then save the object at some point in your controller.
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
I'm working on a sort of project management app with Rails (my Rails skills is kinda rusty). I have two model objects, in this case User and Account, which have a many-to-many relationship (Company could maybe be a better name for Account). When a user signs up a new Account is created (with .build) with help form a nested form. The Account model have two fields name and account_admin. When the the initial user creates it's Account I want to set account_admin to the users id. But I can't get this to work.
The models is set up like this:
class Account < ActiveRecord::Base
attr_accessible :name, :account_admin
validates_presence_of :name
has_many :projects, dependent: :destroy
has_many :collaborators
has_many :users, through: :collaborators
end
class User < ActiveRecord::Base
has_secure_password
attr_accessible :email, :name, :password, :password_confirmation, :accounts_attributes
has_many :collaborators
has_many :accounts, through: :collaborators
accepts_nested_attributes_for :accounts
[...]
The UserController looks like this:
def new
if signed_in?
redirect_to root_path
else
#user = User.new
# Here I'm currently trying to set the account_admin value, but it seems to be nil.
account = #user.accounts.build(:account_admin => #user.id)
end
end
I have also tried to move account = #user.accounts.build(:account_admin => #user.id) to the create action, but the the field disappears from the form.
What would be the appropriate way to accomplish what I want (set account_admin to the users id when it is getting created)? Or is there a better approach to find out which user created the account (ie. do something with the relationship table)?
Update
With help from #joelparkerhenderson I think I got it to work. I made a method in my User model that looks like this:
def set_account_admin
account = self.accounts.last
if account.account_admin == nil
account.account_admin = self.id
account.save
end
end
Which I call with after_create :set_account_admin. This works, but is there a more "Rails way" to do the same?
Thanks.
When you call #new, the user doesn't have an id yet (it is nil).
When you #save the user, Rails automatically gives the user a new id.
You can then use the after_create Active Record callback to set the new Account's account_admin
class Party < ActiveRecord::Base
belongs_to :hostess, class_name: 'Person', foreign_key: 'hostess_id'
validates_presence_of :hostess
end
class Person < ActiveRecord::Base
has_many :parties, foreign_key: :hostess_id
end
When creating a new Party, the view lets the user select an existing Hostess, or enter a new one. (This is done with jQuery autocomplete to look up existing records.) If an existing record is chosen, params[:party][:hostess_id] will have the correct value. Otherwise, params[:party][:hostess_id] is 0 and params[:party][:hostess] has the data to create a new Hostess (e.g., params[:party][:hostess][:first_name], etc.)
In the Parties controller:
def create
if params[:party][:hostess_id] == 0
# create new hostess record
if #hostess = Person.create!(params[:party][:hostess])
params[:party][:hostess_id] = #hostess.id
end
end
#party = Party.new(params[:party])
if #party.save
redirect_to #party, :notice => "Successfully created party."
else
#hostess = #party.build_hostess(params[:party][:hostess])
render :action => 'new'
end
end
This is working fine when I pass in an existing Hostess, but it's not working when trying to create the new Hostess (fails to create the new Hostess/Person and thus fails on creating the new Party). Any suggestions?
Given the models you provided, you can have this setup in a cleaner way using a few rails tools like inverse_of, accepts_nested_attributes_for, attr_accessor, and callbacks.
# Model
class Party < ActiveRecord::Base
belongs_to :hostess, class_name: 'Person', foreign_key: 'hostess_id', inverse_of: :parties
validates_presence_of :hostess
# Use f.fields_for :hostess in your form
accepts_nested_attributes_for :hostess
attr_accessor :hostess_id
before_validation :set_selected_hostess
private
def set_selected_hostess
if hostess_id && hostess_id != '0'
self.hostess = Hostess.find(hostess_id)
end
end
end
# Controller
def create
#party = Party.new(params[:party])
if #party.save
redirect_to #party, :notice => "Successfully created party."
else
render :action => 'new'
end
end
We're doing quite a few things here.
First of all, we're using inverse_of in the belongs_to association, which allows you to validate presence of the parent model.
Second, we're using accepts_nested_attributes_for which allows you to pass params[:party][:hostess] into the party model and let it build the hostess for you.
Third, we're setting up an attr_accessor for :hostess_id, which cleans up controller logic quite a bit, allowing the model to decide what to do whether it receives hostess object or the hostess_id value.
Fourth, we're making sure to override hostess with an existing hostess in case we got a proper hostess_id value. We do this by assigning hostess in the before_validation callback.
I didn't actually check if this code works, but hopefully it reveals enough information to solve your problem and exposes more helpful tools lurking in rails.