I have these models in my Rails app. My use-case is multilingual data. First I want to store the default language version, then it will be possible to add other language data.
class Category < ApplicationRecord
has_many :category_versions
accepts_nested_attributes_for :category_versions, allow_destroy: true
end
class CategoriesVersion < ApplicationRecord
belongs_to :categories
has_many :categories_contents
has_one :langs
end
class CategoriesContent < ApplicationRecord
belongs_to :categories_versions
end
And I would like to save the record with one toplevel record with associated record in CategoriesVersion and CategoriesContent.
Using data from params and generated inside create method in the controller.
I got first and second table, but I don't know how to add attributes for third table, nested in second one, if it is possible. So the association from CategoriesVersion to CategoriesContent
category_data = { category: {
kind: "user",
featured_image: "test",
featured_image_title: "test",
editorial_id: nil,
category_versions_attributes: [
{lang_id: Lang.find_by_lang("francais").id},
{published: false},
{featured: false} #data["featured"] || false
]
}}
test = Category.create(category_data["category"])
Please any suggestions?
If I understand your issue, I think accepts_nested_attributes_for is the configuration that you need in your models to save the nested data just saving the category.
Related
Given:
class Group < ApplicationRecord
has_many :customers, inverse_of: :group
accepts_nested_attributes_for :customers, allow_destroy: true
end
class Customer < ApplicationRecord
belongs_to :group, inverse_of: :customers
end
I want to create/update a group and assign existing customers to the group e.g.:
Group.new(customers_attributes: [{ id: 1 }, { id: 2 }])
This does not work though because Rails will just throw ActiveRecord::RecordNotFound: Couldn't find Customer with ID=1 for Group with ID= (or ID=the_group_id if I'm updating a Group). Only way I've found to fix it is just extract customers_attributes and then do a separate Customer.where(id: [1,2]).update_all(group_id: 'groups_id') after the Group save! call.
Anyone else come across this? I feel like a way to fix it would be to have a key like _existing: true inside customers_attributes (much like _destroy: true is used to nullify the foreign key) could work. Or does something like this violate a Rails principle that I'm not seeing?
Actually, you don't need to use nested attributes for this, you can instead set the association_ids attribute directly:
Group.new(customer_ids: [1, 2])
This will automatically update the group_id on each referenced Customer when the record is saved.
I have some models in my database:
- customer
has_many documents
- charts
has_many documents
- pages
has_many documents
Any of models above can have many documents.
How can I do this in the Document model? Is there any relationship can accept different models?
Yes, it is possible. This concept is called polymorphic association and can be done like this using Ruby on Rails:
class Document < ActiveRecord::Base
belongs_to :owner, polymorphic: true
class Customer < ActiveRecord::Base
has_many :documents, as: :owner
It uses 2 columns to work: one column to save the owner's type, and a second column to save th owner's id:
Document.create(owner_type: 'Customer', owner_id: customer.id)
Then, you can call the method .owner on the document object:
doc = Document.first
doc.owner # => Can either return a Customer, Chart or Page record
You might want to add some security around this, something to prevent from creating documents for a owner that is not supposed to have this relation:
class Document < ActiveRecord::Base
belongs_to :owner, polymorphic: true
validates :owner_type, inclusion: { in: %w( Customer Chart Page ) }
This will prevent from creating documents like this:
Document.create(owner_type: 'kittyCat', owner_id: 77) # won't work
I have a complex nested attributes system that I'll slim down to two models:
I have 2 models:
class Product < ActiveRecord::Base
has_many :products_colors
accepts_nested_attributes_for :products_colors, :reject_if => :all_blank, :allow_destroy => true
class ProductsColor < ActiveRecord::Base
belongs_to :product
belongs_to :color
What method would I have to overwrite so that when I'm on the products edit form with a nested products_colors form and I create a product + products_colors it first looks to see if a products_color with a certain color_id and product_id exists and returns that instead of a new ProductsColor instance?
The reason is that if there is a ProductsColor of Product A and Color A that already exists, I want to just update that one instead of creating a new ProductsColor.
I'm thinking of something like this:
class ProductsColor < ActiveRecord::Base
...
def self.new(args)
pc = ProductsColor.where(color_id: args[:color_id], product_id: args[:product_id]).first_or_initialize
pc.assign_attributes(args)
end
end
Here's an example params submitted through the edit products form:
{"product"=>{"name"=>"zzzz", "products_colors_attributes"=>{"0"=>{"color_id"=>"1"}}, "commit"=>"Update Product", "id"=>"24"}
I know it's a bit confusing... let me know if you need any more info.
You must create a new method in Product something like update_or_create_colors that updates or creates new ProductColor, and in your controller just call that method, I don't thing there is a method in rails for your especific case.
This is a situation where you could submit a "color_ids" attribute (this is a dynamic attribute created by has_many
class Product < ActiveRecord::Base
has_many :products_colors
has_many :colors, :through => :products_colors
This will build the join relationships for product and colors.
I am having models like
// Contains the details of Parties (Users)
class Party < ActiveRecord::Base
has_many :party_races
has_many :races, :through=>:party_races
end
// Contains the party_id and race_id mappings
class PartyRace < ActiveRecord::Base
belongs_to :party
belongs_to :race
end
// Contains list of races like Asian,American,etc..
class Race < ActiveRecord::Base
has_many :party_races
has_many :parties, :through => :party_races
end
Now, lets say I'm creating an instance of Party
party_instance = Party.new
How am I supposed to add multiple Races to party_instance and save to database ?
You could use nested attributes to make one form that allows children. There are many examples on this site. Read up on the following first:
Accepts nested attributes
Fields for
A railscast about your use case
You also can create new PartyRace for each Races that you can add:
def addRace( party_instance, new_race )
party_race = PartyRace.new( party: party_instance, race: new_race )
party_race.save
end
I have a typical has_many relationship between two models (lets say Article has_many Authors.)
My Article form lets the user:
Create new Authors and associate them with the Article,
Select existing Authors to associate with the Article,
Remove the association with an Author (without deleting the Author record.)
I am using accepts_nested_attributes_for and this handles #1 perfectly. However, I am yet to find the best way of implementing #2 and #3 while still using accepts_nested_attributes_for.
I actually had this all working with Rails 3.0.0. ActiveRecord would automatically create a new association when given an Author id that it had not seen before. But it turned out I was accidentally exploiting the security bug that was then fixed in Rails 3.0.1.
I've tried a bunch of different approaches, but nothing works completely and I can't find much information on best practices in this case.
Any advice would be appreciate.
Thanks,
Russell.
Assuming you probably need to use a join table. Give this a go:
class Article < ActiveRecord::Base
has_many :article_authors
accepts_nested_attributes_for :article_authors, allow_delete: true
end
class Author < ActiveRecord::Base
has_many :article_authors
end
class ArticleAuthor < ActiveRecord::Base
belongs_to :article
belongs_to :author
accepts.nested_attributes_for :author
end
# PUT /articles/:id
params = {
id: 10,
article_authors_attributes: {
[
# Case 1, create and associate new author, since no ID is provided
{
# A new ArticleAuthor row will be created since no ID is supplied
author_attributes: {
# A new Author will be created since no ID is supplied
name: "New Author"
}
}
],
[
# Case 2, associate Author#100
{
# A new ArticleAuthor row will be created since no ID is supplied
author_attributes: {
# Referencing the existing Author#100
id: 100
}
}
],
[
# Case 3, delete ArticleAuthor#101
# Note that in this case you must provide the ID to the join table to delete
{
id: 1000,
_destroy: 1
}
]
}
}
Look at this: http://ryandaigle.com/articles/2009/2/1/what-s-new-in-edge-rails-nested-attributes
its for rails 2.3, but most of the syntax is the same with rails3... It mentions all things you look for..
For completeness, the way I'm doing this now is this:
class Article < ActiveRecord::Base
belongs_to :author, validate: false
accepts_nested_attributes_for :author
# This is called automatically when we save the article
def autosave_associated_records_for_author
if author.try(:name)
self.author = Author.find_or_create_by_name(author.name)
else
self.author = nil # Remove the association if we send an empty text field
end
end
end
class Author < ActiveRecord::Base
has_many :articles
end
I haven't found a way to validate the associated model (Author) with it's validations..