I'm creating a single associated record in a has_many association via nested_attributes like so
#Parent Model
has_many :child_models
#Parent Controller
def create
parentModel.update_attributes(parent_model_params_with_nested_child_attributes)
end
Pretty basic, but after the update created a new ChildModel I want to add some other data to it, so I need to get a reference to the ChildModel that was just created.
parentModel.childModels.order("id DESC").first
since that SHOULD contain the most recently created association, but I'm wondering if A) that's true and B) there isn't a better way.
Related
I have a rails API that injects primary key IDs from the client, as opposed to autogenerating through Rails. Specifically:
class ParentModel < ApplicationRecord
accepts_nested_attributes_for: child_models
has_many :child_models
end
class ChildModel < ApplicationRecord
belongs_to :parent_model
end
Nested data is created via:
#parent_object = ParentModel.find_or_initialize_by(id: parent_model_params[:id])
#parent_object.assign_attributes(parent_model_params)
#parent_object.save
If the ID for a child_object already exists in the database, the operation updates the child object as expected. However, if the child object is new, I get:
Couldnt find ChildModel with ID=b35e8f02... for ParentModel with ID=0c9b60f3...
In short: I'm trying to ensure rails creates child records (with the given IDs) when they don't exist, and continues to update existing child records if they do. Any thoughts on how to do that through nested attributes?
For those wondering, I couldn't find an elegant solution. Instead, I manually created a new child object for each new child before calling .assign_attributes. Eg:
parent_model_params[:child_model_attributes].each do |child_params|
next if #parent_object.child_ids.include?(child_params[:id])
#parent_object.child_objects << ChildModel.new(child_params)
end
#parent_object.assign_attributes(parent_model_params) # This no longer raises a RecordNotFound error
In my app there is recipes and ingredients, so a recipe can have many ingredients and a ingredient can be used in many recipes, everything is ok and when I create recipes there is a table called Has_ingredient where is saved every ingredient per recipe.
The thing is, now when I try to destroy a recipe there is a error, because I need to destroy the record in Has_ingredient associated with that recipe before delete the recipe.
So in my model I created something like this
class Recipe < ActiveRecord::Base
before_destroy :destroy_ingredents
....
....
....
def destroy_ingredents
HasIngredent.destroy(recipe_id: self.id)
end
Well, now I get this error: OCIERROR: ORA-00904.
So now the problem is not Rails, but the database (Im using Oracle), but im pretty sure the problem is how im using destroy method but I cant figure how to use it properly to delete every record in has_ingredent table associated with certain recipe
The best way to destroy the children would be using the dependent
So in your model, you would have:
class Recipe < ActiveRecord::Base
has_many :ingredients, dependent: :destroy
end
As #pauloancheta suggested, is the better way to leave these with rails. However if you want to know the correct syntax,
HasIngredent.find_by(recipe_id: self.id).destroy
I want to preview what the model will look like when saved without currently saving to the database.
I am using #event.attributes = because that assigns but does not save attributes for #event to the database.
However, when I also try to assign the audiences association, Rails inserts new records into the audiences_events join table. Not cool. Is there a way to preview what these new associations will look like without inserting into the join table?
Model
class Event < ActiveRecord::Base
has_and_belongs_to_many :audiences # And vice versa for the Audience model.
end
Controller
class EventsController < ApplicationController
def preview
#event = Event.find(params[:id])
#event.attributes = event_params
end
private
def event_params
params[:event].permit(:name, :start_time, :audiences => [:id, :name]
end
end
Possible Solutions?
Possible solutions that I thought of, but don't know how to do:
Using some sort of method that assigns associations, but does not persist them.
disabling all database writes for this one action (I dont know how to do that).
Rolling back all database changes at the end of this action
Any help with these would be great!
UPDATE:
After the reading the great answers below, I ended up writing this service class that assigns the non-nested attributes to the Event model, then calls collection.build on each of the nested params. I made a little gist. Happy to receive comments/suggestions.
https://gist.github.com/jameskerr/69cedb2f30c95342f64a
In these docs you have:
When are Objects Saved?
When you assign an object to a has_and_belongs_to_many association, that object is automatically saved (in order to update the join table). If you assign multiple objects in one statement, then they are all saved.
If you want to assign an object to a has_and_belongs_to_many association without saving the object, use the collection.build method.
Here is a good answer for Rails 3 that goes over some of the same issues
Rails 3 has_and_belongs_to_many association: how to assign related objects without saving them to the database
Transactions
Creating transactions is pretty straight forward:
Event.transaction do
#event.audiences.create!
#event.audiences.first.destroy!
end
Or
#event.transaction do
#event.audiences.create!
#event.audiences.first.destroy!
end
Notice the use of the "bang" methods create! and destroy!, unlike create which returns false create! will raise an exception if it fails and cause the transaction to rollback.
You can also manually trigger a rollback anywhere in the a transaction by raising ActiveRecord::Rollback.
Build
build instantiates a new related object without saving.
event = Event.new(name: 'Party').audiences.build(name: 'Party People')
event.save # saves both event and audiences
I know that this is a pretty old question, but I found a solution that works perfectly for me and hope it could save time to someone else:
class A
has_many :bs, class_name 'B'
end
class B
belongs_to :a, class_name: 'A'
end
a.bs.target.clear
new_bs.each {|new_b| a.bs.build new_b.attributes.except('created_at', 'updated_at', 'id') }
you will avoid autosave that Rails does when you do a.bs = new_bs
When creating objects through a has_many association like User.first.books.create!(...), the new book gets the user_id automatically from the association.
Is there any way to get that user_id if I call my own create method? i.e. User.first.books.own_create_method
def self.own_create_method
# how to get the user object?
end
Thanks!
To define User.first.books.own_create_method you would use:
def self.own_create_method
book = build
# something custom you want to do with book
book.save
end
self. allows you to define class methods in Ruby.
Digging into ActiveRecord new method, I found that you can call scope_attributes and you'll get a hash with all the attributes that are scoped.
def self.own_create_method
attributes = scope_attributes
# attributes["user_id"] would be the user_id that is scoped by
...
end
Not sure if this is a good practice though...
I have two applications, App1 and App2. App1 posts a JSON payload to App2 that includes data for a parent and child object. If the parent object already exists in App2, then we update the parent record if anything has changed and create the child record in App2. If the parent object does not exist in App2, we need to first create it, then create the child object and associate the two. Right now I'm doing it like this:
class ChildController
def create
#child = Child.find_or_initialize_by_some_id(params[:child][:some_id])
#child.parent = Parent.create_or_update(params[:parent])
if #child.update_attributes(params[:child])
do_something
else
render :json => #child.errors, :status => 500
end
end
end
Something feels dirty about creating/updating the parent like that. Is there a better way to go about this? Thanks!
As a starting point, you'll want to create the association in your model, then include accepts_nested_attributes_for on your Parent.
With the associations created in your model, you should be able to manipulate the relationship pretty easily, because you automatically get a host of methods intended to manage the relationship. For example, your Parent/Child model might look something like this:
In your Parent model:
class Parent < ActiveRecord::Base
has_many :children
accepts_nested_attributes_for :children
In your Child model:
class Child < ActiveRecord::Base
belongs_to :parent
Then, you should be able to build associations in your controller like this:
def new
#parent = Parent.children.build
end
def create
#parent = Parent.children.build(params[:parent])
end
The nested_attributes property will then allow you to update attributes of the Child by manipulating the Parent.
Here is the Rails API on the topic: http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
Use accept_nested_attributes_for to handle parent children relationship .here's a blog post to help you out http://currentricity.wordpress.com/2011/09/04/the-definitive-guide-to-accepts_nested_attributes_for-a-model-in-rails-3/