I have same problem as : Rails Polymorphic Association with multiple associations on the same model
But the solutions on this question aren't working for me. I have a picture model and an event model. Event has many pictures and one cover pic. Here are both the models.
class Picture < ActiveRecord::Base
belongs_to :image, polymorphic: true
end
class Event < ActiveRecord::Base
has_many :pictures, as: :image, :dependent => :destroy
has_one :cover_picture, -> { where image_type: "CoverPicture"},
class_name: Picture, foreign_key: :image_id,
foreign_type: :image_type, dependent: :destroy
end
Issue here is that when I create a new picture and set it as cover_picture of an event, it doesn't set the image_type to "CoverPicture". When I try and save it after specifically setting the image_type to "CoverPicture", it errors out with "NameError: uninitialized constant CoverPicture"
image_type has a specific function in this polymorphic association... it identifies the associated model (just as image_id identifies the id).
You should not be changing image_type as that breaks the association.
Make a new boolean column in the picture model, say cover_picture, and you can do...
has_one :cover_picture, -> {where cover_picture: true} ...
The advantage of this is your cover picture is also included in your pictures association, but if you want that picture excluded from the has_many then you can apply a where clause to that as well...
has_many :pictures, -> {where.not cover_picture: true} ...
Related
A report_template has_many report_template_columns, which each have a
name and an index attribute.
class ReportTemplateColumn < ApplicationRecord
belongs_to :report_template
validates :name, presence: true
end
class ReportTemplate < ApplicationRecord
has_many :report_template_columns, -> { order(index: :asc) }, dependent: :destroy
accepts_nested_attributes_for :report_template_columns, allow_destroy: true
end
The report_template_columns need to be ordered by the index column. I'm applying this with a scope on the has_many association, however doing so causes the following error:
> ReportTemplate.create!(report_template_columns: [ReportTemplateColumn.new(name: 'id', index: '1')])
ActiveRecord::RecordInvalid: Validation failed: Report template columns report template must exist
from /usr/local/bundle/gems/activerecord-5.1.4/lib/active_record/validations.rb:78:in `raise_validation_error'
If I remove the scope the same command succeeds.
If I replace the order scope with where scope that command fails in the same way, so it seems to be the presence of the scope rather than the use of order specifically.
How can I apply a scope to the has_many without breaking the nested creation?
I believe you need the :inverse_of option added to the has_many association.
class ReportTemplate < ApplicationRecord
has_many :report_template_columns, -> { order(index: :asc) },
dependent: :destroy, inverse_of: :report_template
end
The api states that :inverse_of:
Specifies the name of the belongs_to association on the associated object that is the inverse of this has_many association. Does not work in combination with :through or :as options. See ActiveRecord::Associations::ClassMethods's overview on Bi-directional associations for more detail.
I also like how the cocoon gem words their reason for using it:
Rails 5 Note: since rails 5 a belongs_to relation is by default required. While this absolutely makes sense, this also means associations have to be declared more explicitly. When saving nested items, theoretically the parent is not yet saved on validation, so rails needs help to know the link between relations. There are two ways: either declare the belongs_to as optional: false, but the cleanest way is to specify the inverse_of: on the has_many. That is why we write: has_many :tasks, inverse_of: :project
I've used dependent: :destroy on models before with out any problem, but in rails 4.2 I'm stuck. The past uses were mainly for classic has_many belongs_to models. It almost seems that #<ActiveRecord::Associations::CollectionProxy is causing my problems.
Models
class Subject < ActiveRecord::Base
has_many :properties
has_many :values, :through => :properties
has_many :tags, :through => :properties
class Property < ActiveRecord::Base
belongs_to :subject
belongs_to :tag
belongs_to :value
class Value < ActiveRecord::Base
has_one :property
has_one :subject, :through => :property
has_one :tag, :through => :property
class Tag < ActiveRecord::Base
has_many :properties
has_many :subjects, :through => :properties
My goals were to
Deleting a Subject would delete all associated Properties and Values
Deleting a Property would delete the associated Value, leaving Subject intact
or, Deleting a Value would delete the associated Property, leaving Subject intact
I tried adding dependent destroy on the values line in Subject and the property line in Value. It would delete the properties, but not the values. I tried putting it on the values properties line and value line in Property and got the same results - it would not delete the Values.
I then tried before_destroy filter and ran into the same type of problem or a ActiveRecord::InvalidForeignKey: PG::ForeignKeyViolation: ERROR when I tried the the model associations. I then hacked it and got it to work:
# In Subject model
before_destroy :destroy_values
def destroy_values
# relations does not seem to work got the Values using a new query
#values.destroy_all
pids = values.pluck(:id)
Value.where(id:pids).destroy_all
end
# in Value model
before_destroy :destroy_property
def destroy_property
property.destroy
end
Not sure what is going on, read up as much as I could on dependent and tried delete_all, and every other thing I saw with no joy!
Yes, it is a strange model, just playing around and tried to replicate the "Whatit?" Apple II database in rails for grins.
Sometimes, when your stuck going around circles, you just have to ask a question. When you don't get an answer, you've had time to rethink the problem. I guess I didn't try all the options.
I think the problem is my not understanding default strategy of nullify. According to my goals, you can't have a value without a property and vice versa. Trying to destroy using a through association would raise the pg error. The simple solution that I apparently didn't try was to dependent destroy properties in the Subject model and dependent destroy value in the Property model. The warning in the guide not to use dependent destroy on a belongs_to association may have started my circle trip. I'm still not sure I understand the warning. My guess is that when subject.properties is destroyed, the subject_id is set to null before the property.value destroy call is made, avoiding the pg error. My cleaned up models:
class Subject < ActiveRecord::Base
has_many :properties, dependent: :destroy
has_many :values, :through => :properties
has_many :tags, :through => :properties
class Property < ActiveRecord::Base
belongs_to :subject
belongs_to :tag
belongs_to :value, dependent: :destroy
class Value < ActiveRecord::Base
has_one :property
has_one :subject, :through => :property
has_one :tag, :through => :property
I at one point had a simple model where a recipe had many photos, yet I was only creating Recipe objects by setting one photo through carrierwave on the recipe.photo attribute rather than through recipe.photos.
This was in part because I didn't realize I had specified has_many. I was creating Recipe objects through rails_admin gem by assigning to the recipe.photo attribute rather than recipe.photos.
class Recipe < ActiveRecord::Base
validates :title, :content, presence: true
belongs_to :category
has_many :photos, class_name: 'RecipePhoto', dependent: :destroy
mount_uploader :photo, RecipePhotoUploader
accepts_nested_attributes_for :photos, allow_destroy: :true
end
class RecipePhoto < ActiveRecord::Base
belongs_to :recipe
mount_uploader :photo, RecipePhotoUploader
end
So my question is when I began assigning to recipe.photos and then were to access a recipe instances photos via recipe.photos
I would only see photos that were created onto recipe.photos, and not recipe.photo.
Why is this? Shouldn't ActiveRecord access ALL associated photos to the recipe with notation recipe.photos?
Why does it treat it as recipe.photo were assigned via a has_one relationship even though it was has_many the whole time?
Shouldn't ActiveRecord be robust to changing relationships? Like if it at a later date and wanted to change it to has_one, etc.
I have a polymorphic PLACE model like this:
class Place < ActiveRecord::Base
belongs_to :placeable, polymorphic: :true
...
So, for example, in my Lodging model I have something like:
class Lodging < ActiveRecord::Base
has_one :place, as: :placeable, dependent: :destroy
...
And they work as expected.
The point is that now I want to create a CarRental model and this model has TWO places, on is the pick-up place and the other one is the drop-off place.
So I wrote:
class Transportation < ActiveRecord::Base
has_one :start_place, as: :placeable, dependent: :destroy
has_one :end_place, as: :placeable, dependent: :destroy
And of course that does not work. Any insights on how to do that?
EDIT 1: IF I DO LIKE
class Transportation < ActiveRecord::Base
has_one :start_place, class_name: 'Place', as: :placeable, dependent: :destroy
has_one :end_place, class_name: 'Place', as: :placeable, dependent: :destroy
It works! But why? Where is the start or end information saved?
** EDIT 2: NOPE, IT DOES NOT WORK **
It does not work... =(
I guess, associations spitted out errors before because of the unknown classes. has_one :start_place by default assumes you mean a class StartPlace that doesn't exist. It singularizes the term (as best as it can) and converts it to CamelCase. Once you've specified that you mean Place, it's clear.
You should be adding a new column anyways.Let's try single table inheritance (STI). Add a column type of type string to your places table:
rails generate migration AddTypeToPlaces type:string
...make sure it does what it says, then migrate and create new models like so:
rails generate model StartPlace --no-migration --parent=Place
rails generate model EndPlace --no-migration --parent=Place
Note: they don't get a dedicated table and inherit from Place, not ActiveRecord::Base. It should generate two empty classes, that's fine, they inherit things from Place.
...then revert your associations to what didn't work a while ago:
has_one :start_place, as: :placeable, dependent: :destroy
has_one :end_place, as: :placeable, dependent: :destroy
...and they should work now, because StartPlace is defined as
Place with type equal to "StartPlace"
...same with EndPlace with corresponding type.
I described this quite some time ago for a similar case.
Your schema doesn't make sense to me - that places belong to lodgings etc. Surely there's a fixed number of places, and then they can have a number of different things in them? In your schema the same place could be in the database lots of times which seems wrong. I don't think that polymorphic associations are the way to go here either.
I would model this like so:
class Place
has_many :lodgings
has_many :starting_transportations, :class_name => "Transportation", :as => :start_place
has_many :ending_transportations, :class_name => "Transportation", :as => :end_place
class Lodging
belongs_to :place #using lodgings.place_id
class Transportation
belongs_to :start_place, :class_name => "Place" #via transportations.start_place_id
belongs_to :end_place, :class_name => "Place" #via transportations.end_place_id
btw, i think "Location" is a better name than "Place" for physical locations in the real world. "Place" sounds too vague.
Very new to Rails... I'm building out functionality that lets people compare photos, and I can't decide exactly how I should structure it. Ideally what I'd like is to have a "comparisons" table which keeps a record of the IDs of the photos compared as well as the user that compared them, but I'm not quite sure whether this warrants use of the "belongs_to" function or not. If so, how do I specify that each comparison belongs to TWO separate photos?
The following has_many, :through => Model structure will let you have additonal properties on the join table, e.g. 'comparing_user_id'.
class Photo < ActiveRecord::Base
has_many :appearances
has_many :users, :through => :appearances
end
class Appearance < ActiveRecord::Base
belongs_to :photo
belongs_to :user
end
class User < ActiveRecord::Base
has_many :appearances
has_many :photos, :through => :appearances
end