Rails not save association if association values are empty - ruby-on-rails

I have a problem.
I have a Content model that has a polymorphic association, Sponsorship.
In my content form I have a nested form with some sponsorship details. These sponsorship details aren't mandatory, but now every time that I edit a content the sponsorship table is filled with blank data.
I will simplify my code here:
class Content < ActiveRecord::Base
include Sponsorable
end
class Sponsorship < ActiveRecord::Base
belongs_to :sponsorable, polymorphic: true
end
module Sponsorable
extend ActiveSupport::Concern
included do
has_one :sponsorship, as: :sponsorable
accepts_nested_attributes_for :sponsorship
end
end
I need to clarify that I create an auto-saving feature on my content model, so in my content controller I put a Content.create in the "new" method.
def new
#content = Content.create(author: current_user)
redirect_to edit_admin_content_path(#content)
end
And to fill the sponsorship detail
def edit
content.build_sponsorship unless content.sponsorship
end
Every time I create and save a content the sponsorship details are created also if I leave the form fields empty. My content table has a boolean "sponsor": is there a way to save association only if sponsor == true?

You may want to look at the reject_if: option for accepts_nested_attributes_for:
accepts_nested_attributes_for :sponsorship, reject_if: proc { |attributes| attributes['title'].blank? }
Looking up other questions on this matter seem to confirm this.
--
There is also reject_if: :all_blank -
accepts_nested_attributes_for :sponsorship, reject_if: :all_blank

Related

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

Modify Nested attribute array before_save

I am trying to modify nested attributes before they are saved in my database. The idea is that if someone has already submitted a costume with a given cid in the costume database, we should pull that one and use it for the current agreement. However, through debugging, I've found that the nested attributes array doesn't change at all. Any thoughts?
Thanks!
Matt
app/models/agreement.rb
class Agreement < ActiveRecord::Base
before_save :get_costumes
has_and_belongs_to_many :costumes, join_table: :agreement_costumes
accepts_nested_attributes_for :costumes, reject_if: proc { |attributes| attributes['cid'].blank? }
has_many :drycleans
accepts_nested_attributes_for :drycleans, allow_destroy: true, reject_if: :all_blank
def get_costumes
self.costumes.map! { |costume|
unless Costume.where(cid: costume.cid).nil?
Costume.where(cid: costume.cid).first
end
}
end
end
Your unless-condition is never true. With a where-statement you get always an ActiveRecord. And also a empty ActiveRecord is not nil.
Try to change that condition to:
unless Costume.where(cid: costume.cid).count == 0
Or to:
unless Costume.find_by_cid(costume.cid).nil?

why edit action try to save nested attribute

I have a models like Routine and RoutineContent for localization
in Routine.rb
Class Routine < ActiveRecord::Base
has_many :routine_contents, dependent: :destroy
accepts_nested_attributes_for :routine_contents, reject_if: proc {|attributes| attributes['title'].empty?}
end
and in RoutinesContent
class RoutineContent < ActiveRecord::Base
belongs_to :routine
validates_presence_of :title
end
In the new Routine action I puts on RoutineConten fields for languages. If title in one object is emty then this object will rejected.
When I go to edit action, I do this
def set_routine_contents
contents = #routine.routine_contents.group_by {|content| content.lang}
if contents['ru'].nil?
#routine.routine_contents << RoutineContent.new(lang: 'ru')
end
if contents['en'].nil?
#routine.routine_contents << RoutineContent.new(lang: 'en')
end
end
end after this Rails INSERT INTO emty object in table, why? How I can disable it?
Thanks
Solution
def set_routine_contents
contents = #routine.routine_contents.group_by {|content| content.lang}
if contents['ru'].nil?
#routine.routine_contents.build(lang: 'ru')
end
if contents['en'].nil?
#routine.routine_contents.build(lang: 'en')
end
end
Use the build method. Add to Array via << it was bad idea
has_many association implemented with foreign key in routine_id in routine_contents table.
So adding new RoutineContent to your Routine requires determined primary key in Routine to write to routine_id, and causes Routine to save if not saved yet.

How to get a nested child record to know stuff about its parent during instantiation in Ruby on Rails?

I have a form for creating a new invoice with many items.
class Invoice < ActiveRecord::Base
attr_accessible :project_id, :number, :date, :recipient, :items_attributes
accepts_nested_attributes_for :items
end
Now when I instantiate a new invoice and a set of containing items, I want these items to know something about the invoice they belong to even before they are saved, so I can do something like this in my Item model:
class Item < ActiveRecord::Base
belongs_to :invoice
after_initialize :set_hourly_rate
private
def set_hourly_rate
if new_record?
self.price ||= invoice.project.hourly_rate
end
end
end
Right now, my code fails because the child (item) doesn't know anything about its parent (invoice) during instantiation. Only after saving the invoice (and thus its nested items), it all works out. But I want to set a default value on each new item even before it gets saved.
How can this be done?
Thanks for any help.
You can add a callback on the invoice association, as follows:
class Invoice < ActiveRecord::Base
# Code
belongs_to :project
has_many :items, :after_add => :set_item_price
private
def set_item_price(item)
item.price = project.hourly_rate
end
end
Once you have your invoice object, you can create children records with the .items.build method (docs here)
items created through this method should have a reference to the invoice
Though, I think they will have the reference only if the Invoice has been persisted (not really sure about that.)

Rails 3 - callback

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

Resources