Error creating DB entry in model with nested has_many association - ruby-on-rails

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.

Related

Rails - Validate presence of at least one relation of certain type

Here's a has_many :through relationship:
# app/models/user.rb
class User < ApplicationRecord
enum role: {
intern: 0,
associate: 1,
partner: 2,
admin: 3
}
has_many :assignments, dependent: :destroy
has_many :projects, through: :assignments
end
# app/models/project.rb
class Assignment < ApplicationRecord
belongs_to :project
belongs_to :user
end
# app/models/project.rb
class Project < ApplicationRecord
has_many :assignments, dependent: :destroy
has_many :assignees, through: :assignments, source: :user
end
In other words, users can have many projects; projects can have many users ('assignees' in this case).
These assignments can be qualified by their nature: each assignee is either a project owner, or not (that would be the is_owner boolean).
create_table "assignments", force: :cascade do |t|
t.boolean "is_owner"
t.integer "project_id"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["project_id"], name: "index_assignments_on_project_id"
t.index ["user_id"], name: "index_assignments_on_user_id"
end
How can I enforce that there's always 1) at lease one owner and 2) never more than one owner?
I have tried numerous variants, but my money would be on a validation rule on the project model. Any suggestions as to how to achieve this?

Rails - Polymorphic has_many :through with model not building on create

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!

How to implement multiple associations between models?

I have 4 models.
UserModel
SkillModel
UserSkillModel
PreferenceSkillModel
Association I used:
UserModel
has_many :skills, through: :user_skills
has_many :user_skills
has_many :skills, :through: :preference_skills
has_many :preference_skills
SkillModel
has_many :users, through: :user_skills
has_many :users_skills
has_many :users, :through: :preference_skills
has_many :preference_skills
UserSkillModel
belongs_to :user
belongs_to :skill
PreferenceSkillModel
belongs_to :user
belongs_to :skill
Schema:
create_table "preference_skills", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.integer "user_id", default: 0
t.integer "skill_id", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "user_skills", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.integer "user_id"
t.integer "skill_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "skills", options: "ENGINE=InnoDB DEFAULT CHARSET=utf8", force: :cascade do |t|
t.string "name", default: ""
t.integer "skill_count", default: 0
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
How can I implement this association in a correct way?
How can I implement this association in a correct way?
What is correct may depend on what you are trying to do. The following is correct, by naming separate associations for users-skills and users-preference_skills.
class User
has_many :user_skills
has_many :my_skills, through: :user_skills, class_name: 'Skill'
has_many :preference_skills
has_many :my_preference_skills, :through: :preference_skills, class_name: 'Skill'
...
end
class SkillModel
has_many :users_skills
has_many :direct_users, through: :user_skills, class_name: 'User'
has_many :preference_skills
has_many :preference_users, :through: :preference_skills, class_name: 'User'
...
end
If you want one collection with all the skills you need to use STI or polymorphism. Read the Rails Guides for more information.
I don't think there's a need for separate user_skills & preference_skills models.
You can use STI here like -
skill.rb
Skill < ApplicationRecord
end
user_skill.rb
UserSkill < Skill
end
preference_skill.rb
PreferenceSkill < Skill
end
Note: The Skill model that I've used is different from what you are assuming.
You might want to rename your current Skills model to more explainatory name like SkillDetail.
After this you can have your associations like -
class User < AR
has_many :skills
has_many :skill_details, through: :skills
end
class Skill < AR
belongs_to :user
belongs_to :skill_detail
end
class SkillDetail < AR
has_many :skills
has_many :users, through: :skills
end
P.S. Do some research on Single Table Inheritance (STI)
Hope this helps.
Also note that you got a downvote because this is a very vague question & you are directly asking to implement a business logic.

Rails self-referential has_many association

Using Ruby on Rails, I think I need to create a self-referential has_many association to model words in Chinese.
Background:
Each word is a composite of multiple component words.
For example, if I have three words, 'ni', 'hao', and 'nihao', I want to be able to do:
nihao.components = ['ni', 'hao']
and
'ni'.composites = ['nihao']
'hao'.composites =['nihao']
I don't believe this should be a hierarchical association (I've seen several gems... ) because a word doesn't have 1 or 2 "parents", it has 0, 1, or hundreds of "composites". Likewise a word has 0, 1, or several "components".
I've tried:
class Word < ActiveRecord::Base
has_many :relationships
has_many :components, through: :relationships, foreign_key: :component_id
has_many :composites, through: :relationships, foreign_key: :composite_id
end
class Relationship < ActiveRecord::Base
belongs_to :component, class_name: "Word"
belongs_to :composite, class_name: "Word"
end
This isn't quite correct as I am unable to add components:
nihao.components << ni
(0.2ms) BEGIN
(0.2ms) ROLLBACK
ActiveModel::UnknownAttributeError: unknown attribute 'word_id' for Relationship.
from (irb):5
Database schema:
create_table "relationships", force: :cascade do |t|
t.integer "component_id"
t.integer "composite_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "words", force: :cascade do |t|
t.string "characters"
t.string "pinyin"
t.string "opinyin"
t.string "tpinyin"
t.string "english"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Try this, you were not associating your models properly for this kind of use case.
class Word < ActiveRecord::Base
has_many :component_relationships, class_name: 'Relationship', foreign_key: :composite_id
has_many :composite_relationships, class_name: 'Relationship', foreign_key: :component_id
has_many :components, through: :component_relationships
has_many :composites, through: :composite_relationships
end
class Relationship < ActiveRecord::Base
belongs_to :component, class_name: "Word", foreign_key: :component_id
belongs_to :composite, class_name: "Word", foreign_key: :composite_id
end
I have not tried this, but this should work.

how to update schema after adding "accepts_nested_attributes_for" to a model

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"

Resources