I have three objects:
class Picture < ActiveRecord::Base
belongs_to :imageable, :polymorphic => true
end
class Employee < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
class Product < ActiveRecord::Base
has_many :pictures, :as => :imageable
end
How should I create test seed data, to associate an image with both a seed employee and seed product ?
to associate an image with both a seed employee and seed product
This would require a many-to-many relationship (either has_and_belongs_to_many or has_many :through):
#app/models/product.rb
class Product < ActiveRecord::Base
has_many :images, as: :imageable
has_many :pictures, through: :images
end
#app/models/employee.rb
class Employee < ActiveRecord::Base
has_many :images, as: :imageable
has_many :pictures, through: :images
end
#app/models/image.rb
class Image < ActiveRecord::Base
belongs_to :imageable, polymorphic: true
belongs_to :picture
end
#app/models/picture.rb
class Picture < ActiveRecord::Base
has_many :images
end
This would allow you to use:
#db/seed.rb
#employee = Employee.find_or_create_by x: "y"
#picture = #employee.pictures.find_or_create_by file: x
#product = Product.find_or_create_by x: "y"
#product.pictures << #picture
ActiveRecord, has_many :through, and Polymorphic Associations
Because you're using a polymorphic relationship, you won't be able to use has_and_belongs_to_many.
The above will set the polymorphism on the join table; each Picture being "naked" (without a "creator"). Some hacking would be required to define the original creator of the image.
Create them from the has_many end instead:
employee = Employee.create! fields: 'values'
employee.pictures.create! fields: 'values'
product = Product.create! fields: 'values'
product.pictures.create! fields: 'values'
Although just one quick note: when seeding, you may already have the data you want in the database, so I would use instance = Model.where(find_by: 'values').first_or_create(create_with: 'values') instead.
NB. I've just noticed: you're not trying to associate one image with multiple owners, are you? Because each image only belongs to one Imageable, and that is either an Employee or a Product. If you want to do that, you'll have to set up a many-to-many join.
Related
class Sample
has_many :pictures
end
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
belongs_to :sample
end
class Employee < ApplicationRecord
has_many :pictures, as: :imageable
end
class Product < ApplicationRecord
has_many :pictures, as: :imageable
end
What should be the association to get all product or employee of a given sample.
Sample.first.pictures.map(&:imageable). I want to get it as an activerecord association.
Workaround:
class Sample
has_many :pictures
has_many :imageable_employees, through: :pictures, source: :imageable, source_type: 'Employee'
has_many :imageable_products, through: :pictures, source: :imageable, source_type: 'Product'
end
Usage:
sample = Sample.first
employees = sample.imageable_employees
products = sample.imageable_products
...see docs
Explanation:
Sample.first.pictures.map(&:imageable). I want to get it as an activerecord association.
... is I don't think it's possible, but you can still get them all as an Array instead. The reason is that there is no table (model) that corresponds to the imageable association, but that it corresponds to ANY model instead, which complicates the SQL query, and thus I don't think it's possible.
As an example, consider the following query:
imageables_created_until_yesterday = Sample.first.something_that_returns_all_imageables.where('created_at < ?', Time.zone.now.beginning_of_day)
# what SQL from above should this generate? (without prior knowledge of what tables that the polymorphic association corresponds to)
# => SELECT "WHAT_TABLE".* FROM "WHAT_TABLE" WHERE (sample_id = 1 AND created_at < '2018-08-27 00:00:00.000000')
# furthermore, you'll notice that the SQL above only assumes one table, what if the polymorphic association can be at least two models / tables?
Alternative Solution:
Depending on the needs of your application and the "queries" that you are trying to do, you may or may not consider the following which implements an abstract_imageable (a real table) model for you to be able to perform queries on. You may also add more attributes here in this abstract_imageable model that you think are "shared" across all "imageable" records.
Feel free to rename abstract_imageable
class Sample
has_many :pictures
has_many :abstract_imageables, through: :pictures
end
class Picture
belongs_to :sample
has_many :abstract_imageables
end
# rails generate model abstract_imageable picture:belongs_to imageable:references{polymorphic}
class AbstractImageable
belongs_to :picture
belongs_to :imageable, polymorphic: true
end
class Employee < ApplicationRecord
has_many :abstract_imageables, as: :imageable
has_many :pictures, through: :abstract_imageables
end
class Product < ApplicationRecord
has_many :abstract_imageables, as: :imageable
has_many :pictures, through: :abstract_imageables
end
Usage:
sample = Sample.first
abstract_imageables = sample.abstract_imageables
puts abstract_imageables.first.class
# => AbstractImageable
puts abstract_imageables.first.imageable.class
# => can be either nil, or Employee, or Product, or whatever model
puts abstract_imageables.second.imageable.class
# => can be either nil, or Employee, or Product, or whatever model
# your query here, which I assumed you were trying to do because you said you wanted an `ActiveRecord::Relation` object
abstract_imageables.where(...)
I'm using Rails 4.
I have an Anomaly Records class called Ar that inherits from the following classes as follows:
class RecordBase < ActiveRecord::Base
self.abstract_class = true
end
class ArAndEcrBase < RecordBase
self.abstract_class = true
# Relations
belongs_to :originator, class_name: 'User', foreign_key: 'originator_id'
has_many :attachments
end
class Ar < ArAndEcrBase
end
I want to share some relations with a class that handles another type of records in the Ar subclass however the has_many relationship doesn't work.
The following works:
> Ar.last.originator
=> #<User id: 1, ...
The following crashes:
> Ar.last.attachments
Mysql2::Error: Unknown column 'attachments.ar_and_ecr_base_id'
For some reason the has_many relationship doesn't work well. It should look for column attachments.ar_id and not attachments.ar_and_ecr_base_id
Am I doing something wrong? Or is this a Rails bug?
Atm the only way to get the code working is to move the has_many relation to the Ar class:
class Ar < ArAndEcrBase
has_many :attachments
end
If you want several models to have association to the same other model you probably need a polymorphic association
class Picture < ActiveRecord::Base
belongs_to :imageable, polymorphic: true
end
class Employee < ActiveRecord::Base
has_many :pictures, as: :imageable
end
class Product < ActiveRecord::Base
has_many :pictures, as: :imageable
end
I want to insert multiple data in rails. I'm using postgresql, the scenario is when the form submit it passes client name, email and some personal info, then also pass the desire venue with the date and also the amenities they want (ex. swimming poll, billiard poll and etc.). In my backend I will query :
venue = Venue.find(theVenue_id)
book = venue.books.new(name: client_name, email: client_email and etc)
My question is how can I insert the data in my amenity_books if the had many amenities choosen?
I trie something like this.
ex. amenities_id_choosen = [1,3]
if book.save
amenities_id_choosen.each do |x|
amenity = Amenitiy.find(x)
amenity_book = amenity.amenity_books.create(venue_id: venue.id)
end
I know this is not a good idea to insert data but that was my last choice. Does any body knows how to insert multiple data in 2 model with different data.
Models
class Amenity < ActiveRecord::Base
has_many :categorizations
has_many :venues, through: :categorizations
has_many :amenity_books
has_many :books, through: :amenity_books
end
class Venue < ActiveRecord::Base
has_many :categorizations
has_many :amenities, through: :categorizations
end
class Categorization < ActiveRecord::Base
belongs_to :venue
belongs_to :amenity
end
class Book < ActiveRecord::Base
belongs_to :venue
end
class AmenityBook < ActiveRecord::Base
belongs_to :amenity
belongs_to :venue
belongs_to :book
end
Here's an improved version:
amenities_id_choosen = [1,3]
if book.save
Amenitiy.find(amenities_id_choosen).each do |amenity|
amenity.amenity_books.create(venue_id: venue.id)
end
end
This will result in one SELECT query to find all chosen amenities and one INSERT query for each selected amenity.
Another option is to change your data model, does AmenityBook really need to have a venue? Because it looks like the venue is defined through the Book model already.
Here's a suggestion:
class Book < ActiveRecord::Base
belongs_to :venue
has_many :amenity_books
has_many :amenities, through: :amenity_books
end
class AmenityBook < ActiveRecord::Base
belongs_to :amenity
belongs_to :book
has_one :venue, through: :book
end
The code to create a booking with many amenities:
amenities_id_choosen = [1,3]
book.amenity_ids = amenities_id_choosen
if book.save
# success !
end
I have the following associations:
class Venue < ActiveRecord::Base
has_many :sales
end
class Sale < ActiveRecord::Base
has_many :sale_lines
has_many :beverages, through: :sale_lines
end
class SaleLine < ActiveRecord::Base
belongs_to :sale
belongs_to :beverage
end
class Beverage < ActiveRecord::Base
has_many :sale_lines
has_many :sales, through: :sale_lines
has_many :recipes
has_many :products, through: :recipes
end
class Recipe < ActiveRecord::Base
belongs_to :beverage
belongs_to :product
end
class Product < ActiveRecord::Base
has_many :recipes
has_many :beverages, through: :recipes
end
I wan't to see the quantity of products sold by each venue, so basically I have to multiply the recipe.quantity by the sale_line.quantity of an specific product.
I would like to call #venue.calc_sales(product) to get the quantity sold of product.
Inside the class Venue I am trying to calculating it by:
class Venue < ActiveRecord::Base
has_many :sales
def calc_sales(product)
sales.joins(:sale_lines, :beverages, :recipes).where('recipes.product_id = ?', product.id).sum('sale_lines.quantity * recipe.quantity')
end
end
However, I can't access the recipes in that way.
Any idea on how to achieve it?
For the joins, you have to use a Hash to join a already-joined table. It's hard to explain, but here are some examples:
Venue.joins(:sales, :beverages) : This implies that the relations :sales and :beverages are declared on the Venue model.
Venue.joins(:sales => :beverages) : This implies that the relation :sales exists on the Venue model, and the relation :beverages exists on the Sale model.
Consider this:
Venue
has_one :sale
Venue.joins(:sales) : This would not work, you have to use the exact same name as the relation between the Venue model & Sale model.
Venue.joins(:sale) : This would work because you used the same name of the relation.
Attention: You have to use the pluralized name in the where clause:
Venue.joins(:sale).where(:sales => { :id => sale.id })
^^ ^^ # See the plural
In your case, you can do something like this:
sales.joins(:sale_lines => { :beverage => :recipes })
.where(:recipes => { :product_id => product.id })
.sum('sale_lines.quantity * recipes.quantity')
I am trying to figure out how to use ActiveRecord relationships to relate a model that can contain multiple instances of another model within one row. For example, having has_many :dogs in one model, and belongs_to :dog in the other means that in the one model, "dog_id" will refer to an instance of :dog. However, I want to be able to have multiple instances of :dog within my related (has_many) model, such as dog_id1, dog_id2, dog_id3, etc. See the code below to understand what I mean. How can I do this?
I have the following models:
--tp.rb
class Tp < ActiveRecord::Base
has_many :dogs
has_many :cats
has_many :stars
attr_accessible :dog_id1, :dog_id2, :dog_id3, :dog_id4, :cat_id1, :cat_id2, :cat_id3, :cat_id4, :star_id, :tp_id
end
--dog.rb
class Dog < ActiveRecord::
belongs_to :tp
attr_accessible :dog_id, :dog_name
end
--cat.rb
class Cat < ActiveRecord::Base
belongs_to :tp
attr_accessible :cat_id, :cat_name
end
--star.rb
class Star < ActiveRecord::Base
belongs_to :tp
attr_accessible :patient_id
end
I think you have your belongs_to/has_many a little backwards.
belongs_to and has_many
For your example,
class Tp < ActiveRecord::Base
has_many :dogs
has_many :cats
has_many :stars
end
Tp doesn't have any dog_id, cat_id, or star_id in its database row. When you set belongs_to in the other models,
class Dog < ActiveRecord::Base
belongs_to :tp
end
class Cat < ActiveRecord::Base
belongs_to :tp
end
class Star < ActiveRecord::Base
belongs_to :tp
end
You should add a tp_id to each model (dogs table, cats table, and stars table).
Then, calling
tp = Tp.find(123)
Finds Tp with id equal to 123
SELECT * FROM tps WHERE id = 123;
And calling
cats = tp.cats
dogs = tp.dogs
stars = tp.stars
Finds all Cat, Dog, and Star instances with tp_id equal to 123
SELECT * FROM cats WHERE tp_id = 123;
SELECT * FROM dogs WHERE tp_id = 123;
SELECT * FROM stars WHERE tp_id = 123;
If you need your Cat instances to belong to many Tp instances, and Tp instances to have many Cat instances, then you should look at Rails's has_and_belongs_to_many or has_many :through.
has_and_belongs_to_many
A has_and_belongs_to relationship would require new tables cats_tps, dogs_tps, and stars_tps. These tables would have a schema of
cats_tps
cat_id
tp_id
dogs_tps
dog_id
tp_id
stars_tps
star_id
tp_id
Then in your models
class Tp < ActiveRecord::Base
has_and_belongs_to_many :dogs
has_and_belongs_to_many :cats
has_and_belongs_to_many :stars
end
class Dog < ActiveRecord::Base
has_and_belongs_to_many :tps
end
class Cat < ActiveRecord::Base
has_and_belongs_to_many :tps
end
class Star < ActiveRecord::Base
has_and_belongs_to_many :tps
end
Now, running
tp = Tp.find(123)
cats = tp.cats
Generates the SQL
SELECT "cats".* FROM "cats" INNER JOIN "cats_tps" ON "cats"."id" = "cats_tps"."cat_id" WHERE "cats_tps"."tp_id" = 123;
Which is essentialy the query (get me a list of all the cat_ids that belong to Tp 123) and then (get me all the cats that match these cat ids).
Generating the cats_tps join table can be done with a migration like
class CreateCatsTps < ActiveRecord::Migration
def change
create_table :cats_tps, :id => false do |t|
t.belongs_to :cat
t.belongs_to :tp
end
end
end
This works great for simple joins, but you may want to look into using has_many :through. This is because the cats_tps table holds no information about when or why this Cat belongs to a Tp or this Tp belongs to the Cat. Likewise, if you add Bird, Horse, Frog, and Snake models, you will have to create birds_tps, horses_tps, frogs_tps, and snakes_tps tables. Yuck.
has_many :through
To create a has_many :through relationship, you create a new model that makes sense semantically that links a Tp to a Cat. For instance, let's say that a Tp walks cats. You could create an Walk model that links a Cat to a Tp.
class Walk < ActiveRecord::Base
belongs_to :cat
belongs_to :tp
attr_accessible :price, :duration, :interval # these attributes describe the Walk relationship
end
class Cat < ActiveRecord::Base
has_many :walks
has_many :tps, :through => :walks
end
class Tp < ActiveRecord::Base
has_many :walks
has_many :cats, :through => :walks
end
Now, the relationship is similar to a has_and_belongs_to_many, but you can include metadata about the walking relationship. Additionally, say that a Tp also walks dogs. You could convert the belongs_to :cat into a polymorphic belongs_to :animal relationship so that a Tp can walk a cat, dog, mouse, rabbit, horse, ... you name it.
class Walk < ActiveRecord::Base
belongs_to :animal, :polymorphic => true
belongs_to :tp
attr_accessible :price, :duration, :interval # these attributes describe the Walk relationship
end
class Cat < ActiveRecord::Base
has_many :walks, :as => :animal
has_many :tps, :through => :walks
end
class Dog < ActiveRecord::Base
has_many :walks, :as => :animal
has_many :tps, :through => :walks
end
class Tp < ActiveRecord::Base
has_many :walks
has_many :cats, :through => :walks, :source => :animal, :source_type => 'Cat'
has_many :dogs, :through => :walks, :source => :animal, :source_type => 'Dog'
end
This relationship is created with a migration like
class CreateWalks < ActiveRecord::Migration
def change
create_table :walks do |t|
t.belongs_to :animal, :polymorphic => true
t.belongs_to :tp
end
end
end
This is wrong
attr_accessible :dog_id1, :dog_id2, :dog_id3, :dog_id4, :cat_id1, :cat_id2, :cat_id3, :cat_id4, :star_id, :tp_id
Your Dog model has a tp_id and the has_many and belongs_to allow you to do tp_instance.dogs to get an array of Dog models that have a matching tp_id
remove the :dog_id(s) from the TP model and put :tp_id in your dog model. This will allow you to have a one-to-many relationship from TP to Dog