My has_many :through model (Posting) is not building an object on creation of the primary associated object (Post). How can I build the relationship on creation?
Models:
class Post
has_many :postings, dependent: :destroy
has_many :products, through: :postings, source: :postable, source_type: "Product"
has_many :Items, through: :postings, source: :postable, source_type: "Item"
end
class Posting
belongs_to :postable, polymorphic: true
belongs_to :post
end
class Product
has_many :postings, as: :postable
has_many :posts, through: :postings
end
class Item
has_many :postings, as: :postable
has_many :posts, through: :postings
end
Schema:
create_table "postings", force: :cascade do |t|
t.bigint "postable_id"
t.string "postable_type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.bigint "post_id"
t.index ["post_id"], name: "index_postings_on_post_id"
end
create_table "posts", force: :cascade do |t|
t.string "title"
t.string "slug"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Console:
Post.create(title: "Foo", product_id:3)
Traceback (most recent call last):
1: from (irb):12
ActiveModel::UnknownAttributeError (unknown attribute 'product_id' for Post.)
Appreciate any tips!
Related
I created a double "Synonym" association with the same Word model as following:
class Synonym < ApplicationRecord
belongs_to :word_origin, :class_name => "Word"
belongs_to :word_synonym, :class_name => "Word"
end
Here is my Word model:
class Word < ApplicationRecord
has_many :word_synonyms, :class_name => 'Word', :foreign_key => 'word_synonym_id', dependent: :destroy
has_many :word_origins, :class_name => 'Word', :foreign_key => 'word_origin_id', dependent: :destroy
end
However, when I try to delete a word, I get the following error:
ActiveRecord::StatementInvalid (PG::UndefinedColumn: ERROR: column
words.word_synonym_id does not exist)
Any idea what's wrong with my double association? Thanks.
EDIT: here is the schema of Word and Synonym
create_table "words", force: :cascade do |t|
t.string "content"
t.string "slug"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["content"], name: "index_words_on_content", opclass: :gin_trgm_ops, using: :gin
end
create_table "synonyms", force: :cascade do |t|
t.bigint "word_origin_id"
t.bigint "word_synonym_id"
t.index ["word_origin_id"], name: "index_synonyms_on_word_origin_id"
t.index ["word_synonym_id"], name: "index_synonyms_on_word_synonym_id"
end
Word is referencing itself without the needed columns. You have to change the line to be
has_many :word_synonyms, class_name: 'Synonym',
foreign_key: 'word_synonym_id', dependent: :destroy
has_many :word_origins, class_name: 'Synonym',
foreign_key: 'word_origin_id', dependent: :destroy
I tried to multiple ways to get the count of all job applications for the job model, that an employer has. When it comes to one job in particular (job.job_applications.count), it works. You can actually count the applications for that job in question. When you try to sum all job applications for all jobs. I may have overlooked something while setting up the relationships. I receive the error below:
line of code that breaks: <%= current_employer.jobs.job_applications.count %>
undefined method job_applications' for #<Job::ActiveRecord_Associations_CollectionProxy:0x0000000011066998>
The code that I wrote beforehand, is below:
schema
job
create_table "jobs", force: :cascade do |t|
t.string "job_title"
t.text "required_exp"
t.text "job_description"
t.text "job_requirements"
t.bigint "employer_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "slug"
t.bigint "visit_id"
t.index ["employer_id"], name: "index_jobs_on_employer_id"
t.index ["slug"], name: "index_jobs_on_slug", unique: true
end
job applications
create_table "job_applications", force: :cascade do |t|
t.bigint "user_id"
t.string "user_resume_link"
t.string "user_contact_time"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "user_first_name"
t.string "user_last_name"
t.bigint "job_id"
t.index ["job_id"], name: "index_job_applications_on_job_id"
t.index ["user_id"], name: "index_job_applications_on_user_id"
end
job.rb
class Job < ApplicationRecord
belongs_to :employer
has_many :job_applications, dependent: :destroy
has_one :job_category
end
job_application.rb
class JobApplication < ApplicationRecord
belongs_to :user
belongs_to :job
end
employer.rb
class Employer < ApplicationRecord
has_many :jobs, :dependent => :destroy
end
user.rb
class User < ApplicationRecord
has_many :job_applications, dependent: :destroy
has_many :jobs, :through => :job_applications
end
class Employer < ApplicationRecord
has_many :jobs, :dependent => :destroy
has_many :job_applications, through: :jobs
end
And then just call current_employer.job_applications.count
I have three models user.rb, vote.rb, and event.rb
User
has_many :votes, foreign_key: 'voter_id', dependent: :destroy
has_many :voting, through: :votes, source: :voted
Vote
belongs_to :voter, class_name: "User"
belongs_to :voted, class_name: "Event"
Event
has_many :votes, foreign_key: 'voted_id', dependent: :destroy
has_many :voters, through: :votes, source: :voter
For some reason, if I call User.first.voting I get: NoMethodError: undefined method 'voted' for #<User:0x00007fbc56d069d8>
Anybody know why? I have googled a few other questions regarding this topic and I don't see where I am going wrong.
If you're on rails 5, I made a working code:
User
has_many :votes, foreign_key: 'voter_id', dependent: :destroy
has_many :voting, through: :votes, source: :voted
Event
has_many :votes, foreign_key: 'voted_id', dependent: :destroy
has_many :voters, through: :votes, source: :voter
Vote
belongs_to :voter, class_name: "User", optional: true
belongs_to :voted, class_name: "Event", optional: true
Schema
create_table "events", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "users", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "votes", force: :cascade do |t|
t.integer "voter_id"
t.integer "voted_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
To test (rails console)
> User.create
> Event.create
> Vote.create(:voter_id => 1, :voted_id => 1)
> User.first.voting
=> #<ActiveRecord::Associations::CollectionProxy
> User.first.voting.first # to get event instance
=> #<Event ...
Hope this helps!
Setup:
Reservations can be assigned multiple Resources. A reservation-resource combo can have multiple SetUps.
I tried to set up the model like this:
class SetUp < ApplicationRecord
has_many :reservation_resource_set_ups, dependent: :destroy
has_many :reservations, through: :reservation_resource_set_ups
has_many :resources, through: :reservation_resource_set_ups
end
class Resource < ApplicationRecord
has_many :reservation_resources, dependent: :destroy
has_many :reservation_resource_set_ups, dependent: :destroy
has_many :reservations, through: :reservation_resources
has_many :set_ups, through: :reservation_resource_set_ups
end
class Reservation < ApplicationRecord
has_many :reservation_resources, dependent: :destroy
has_many :reservation_resource_set_ups, dependent: :destroy
has_many :resources, through: :reservation_resources
has_many :set_ups, through: :reservation_resource_set_ups
end
class ReservationResource < ApplicationRecord
belongs_to :reservation
belongs_to :resource
has_many :reservation_resource_set_ups
has_many :set_ups, through: :reservation_resource_set_ups
end
class ReservationResourceSetUp < ApplicationRecord
belongs_to :reservation
belongs_to :resource
belongs_to :set_up
end
Steps:
Create a reservation, assigning a resource, works:
res1 = Reservation.create(name:"res name")
res1.resources << Resource.find(1) # resource with id = 1 exists
The reservations and reservation_resources tables are updated correctly.
Assign a setup to the reservation_resource, fails:
res1.resources.first.set_ups << SetUp.find(1) # set_ups with id = 1 exists
This fails with error ActiveRecord::RecordInvalid (Validation failed: Reservation must exist)
Can you help point me in the right direction? Thanks!
(Here's the schema, if helpful...)
create_table "reservation_resource_set_ups", force: :cascade do |t|
t.integer "reservation_id"
t.integer "resource_id"
t.integer "set_up_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["reservation_id"], name: "index_reservation_resource_set_ups_on_reservation_id"
t.index ["resource_id"], name: "index_reservation_resource_set_ups_on_resource_id"
t.index ["set_up_id"], name: "index_reservation_resource_set_ups_on_set_up_id"
end
create_table "reservation_resources", force: :cascade do |t|
t.integer "reservation_id"
t.integer "resource_id"
t.text "comments"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["reservation_id"], name: "index_reservation_resources_on_reservation_id"
t.index ["resource_id"], name: "index_reservation_resources_on_resource_id"
end
create_table "reservations", force: :cascade do |t|
t.string "name"
...
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["end_date"], name: "index_reservations_on_end_date"
t.index ["repeat_end_date"], name: "index_reservations_on_repeat_end_date"
t.index ["start_date"], name: "index_reservations_on_start_date"
end
create_table "resources", force: :cascade do |t|
t.string "name"
t.text "description"
t.string "resource_type"
t.text "location"
t.integer "quantity", default: 1
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "set_ups", force: :cascade do |t|
t.string "name"
t.text "instructions"
t.string "image"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
<< method useless when you need to setup more than one foreign key.
The collection<< method adds one or more objects to the collection by setting their foreign keys to the primary key of the calling model.
So you cat treat the code
res1.resources.first.set_ups << SetUp.find(1) # set_ups with id = 1 exists
as
ReservationResourceSetUp.create(
set_up: SetUp.find(1), # from << SetUp.find(1)
resource: res1.resources.first, # from left side
reservation: nil # raises the error
)
To create the entry, just specify all the keys:
ReservationResourceSetUp.create(
set_up: SetUp.find(1),
resource: res1.resources.first,
reservation: res1
)
You have some problems with your models. For example:
ReservationResource has_many :reservation_resource_set_ups. ActiveRecord assumes a reservation_resource_id in ReservationResourceSetUp
but
ReservationResourceSetUp does not belongs_to :reservation_resource
I suggest some changes in your models:
class Resource < ApplicationRecord
has_many :reservation_resources, dependent: :destroy
has_many :reservations, through: :reservation_resources
has_many :reservation_resource_set_ups, through: :reservation_resources
has_many :set_ups, through: :reservation_resource_set_ups
end
class Reservation < ApplicationRecord
has_many :reservation_resources, dependent: :destroy
has_many :resources, through: :reservation_resources
has_many :reservation_resource_set_ups, through: :reservation_resources
has_many :set_ups, through: :reservation_resource_set_ups
end
class ReservationResource < ApplicationRecord
belongs_to :reservation
belongs_to :resource
has_many :reservation_resource_set_ups
has_many :set_ups, through: :reservation_resource_set_ups
end
class SetUp < ApplicationRecord
has_many :reservation_resource_set_ups, dependent: :destroy
has_many :reservations_resources, through: :reservation_resource_set_ups
has_many :resources, through: :reservation_resources
has_many :reservations, through: :reservation_resources
end
class ReservationResourceSetUp < ApplicationRecord
belongs_to :reservation_resource
belongs_to :set_up
end
The main change is that ReservationResourceSetUp now belongs to ReservationResource and SetUp (instead of Reservation, Resource and SetUp). In practice it's the same, but I think it handles your situation in a better way. You first create a Reservation for a Resource. This ReservationResource is then assigned one or more SetUps. I think your code would work if you make these changes. You must of course change your migrations. Now your ReservationResourceSetUp will have a reservation_resource_id and a set_up_id.
here is my model
class Lineup < ApplicationRecord
has_many :artists
accepts_nested_attributes_for :artists
belongs_to :event
end
class Artist < ApplicationRecord
has_many :events, :through => :lineups
has_many :lineups
end
when running this in the console
Lineup.new(artists_attributes: [{artist_id: 1}, {artist_id: 2}])
the error is ActiveModel::UnknownAttributeError: unknown attribute 'artists_attributes' for Lineup. Obviously, I can't just drop something like that into a model and expect any changes to be from that alone. Do I need to run a migration for this? If so, what needs to be in it?
schema:
create_table "artists", force: :cascade do |t|
t.string "name"
t.text "bio"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "lineup_id"
t.integer "event_id"
end
add_index "artists", ["event_id"], name: "index_artists_on_event_id", using: :btree
add_index "artists", ["lineup_id"], name: "index_artists_on_lineup_id", using: :btree
create_table "lineups", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "artist_id"
t.integer "event_id"
end
add_index "lineups", ["artist_id"], name: "index_lineups_on_artist_id", using: :btree
add_index "lineups", ["event_id"], name: "index_lineups_on_event_id", using: :btree
I would set it up like this
schema
table Lineup
...
table Artist
...
table LineupArtists
lineup_id: Integer
artist_id: Integer
models
class Artist < ApplicationRecord
has_many :lineup_artists, dependent: :destroy
has_many :lineups, through: :lineup_artists
end
class LineupArtist < ApplicationRecord
belongs_to :lineup
belongs_to :artist
accepts_nested_attributes_for :lineup
end
class Lineup < ApplicationRecord
has_many :lineup_artists, inverse_of: :lineup, dependent: :destroy
has_many :artists, through: lineup_artists
accepts_nested_attributes_for :lineup_artists
end
The way you currently have it (with Artists having a lineup_id and Lineups having an artist_id) each model can only have one of the other (i.e. an Artist can have one lineup and vise versa). A join table gives the ability to have a many-to-many relationship.
Setting up nested attributes is a little trickier with a many-to-many but I believe what I posted should cover it. If not got some of the weirder bits from this post (https://robots.thoughtbot.com/accepts-nested-attributes-for-with-has-many-through). Hope this helps.
Try to replace line:
has_many :artists
With this one:
has_many :artists, foreign_key: "lineup_id"