So, I have a namespace for a part of the project.
class Namespace::Product < ActiveRecord::Base
belongs_to: :namespace_category, class_name: 'Namespace::Category'
...
end
class Namespace::Category < ActiveRecord::Base
has_many :products, :class_name => 'Namespace::Product'
...
end
The migration for Product looks like
create_table :namespace_products do |t|
t.belongs_to :namespace_category, index: true
...
end
So, when I do this
p = Namespace::Product.create(some_params)
c = Namespace::Category.find(id)
c.products << p
it throws me an error, saying ActiveModel::MissingAttributeError: can't write unknown attribute 'category_id', however I have an attribute
t.integer "namespace_category_id"
in my schema.rb, which was created by the migration.
For anyone on Rails 5+ looking back at this, you can also specify the namespace on the migration level if you don't want them on the associations:
def change
create_table :namespace_products do |t|
t.references :category, foreign_key: { to_table: :namespace_categories }
end
end
# or
def change
add_reference :namespace_products, :category, foreign_key: { to_table: :namespace_categories }
end
class Namespace::Product < ActiveRecord::Base
belongs_to :category
end
class Namespace::Category < ActiveRecord::Base
has_many :products
end
https://api.rubyonrails.org/v5.0.0/classes/ActiveRecord/ConnectionAdapters/SchemaStatements.html#method-i-add_reference
I would just omit the namespaces from the associations:
class Namespace::Product < ActiveRecord::Base
belongs_to :category, class_name: 'Namespace::Category'
...
end
class Namespace::Category < ActiveRecord::Base
has_many :products, class_name: 'Namespace::Product'
...
end
class FixColumnName < ActiveRecord::Migration
def change
rename_column :namespace_products, :namespace_category_id, :category_id
end
end
Okay, I just had to specify foreign_key while defining has_many :products association in Namespace::Category, thanks to Max
Related
I want to use a join_table between two many to many relations.
student -> join_table <- teacher
MODEL definition :
class Student < ApplicationRecord
has_and_belongs_to_many :teachers, join_table: map_student_teacher
end
class Teacher < ApplicationRecord
has_and_belongs_to_many :students, join_table: map_student_teacher
end
Migration Definition :
class CreateStudents < ActiveRecord::Migration[6.0]
def change
create_table :students do |t|
t.string : student_name
end
end
end
class CreateTeachers < ActiveRecord::Migration[6.0]
def change
create_table :teachers do |t|
t.string : teacher_name
end
end
end
class CreateStudentsTeachersJoinTable < ActiveRecord::Migration[6.0]
def change
create_table :map_student_teacher, id: false do |t|
t.belongs_to :student, index: true
t.belongs_to :teacher, index: true
end
end
end
Now I have a seeds.rb file to initialize the students names.
students_defaults = ["hans","otto","paul","elsa"]
students_defaults.each do |name|
Student.create(student_name: name)
end
When I load the seed by rails db:seed
I got this error message:
rails aborted!
NameError: undefined local variable or method `map_student_teacher' for Student (call 'Student.connection' to establish a connection):Class
/home/sven/.rvm/gems/ruby-2.5.3/gems/activerecord-6.0.2.2/lib/active_record/dynamic_matchers.rb:22:in `method_missing'
What is going wrong ?
In your model classes it should say join_table: :map_student_teacher. Note the extra colon to make map_student_teacher into a symbol. Without that Ruby is trying to look inside a local called map_student_teacher, which is undefined.
class Student < ApplicationRecord
has_and_belongs_to_many :teachers, join_table: :map_student_teacher
end
class Teacher < ApplicationRecord
has_and_belongs_to_many :students, join_table: :map_student_teacher
end
If this is ugly to you, you can also use the older :join_table => :map_student_teacher syntax.
Here are two models:
class Team < ApplicationRecord
has_many :matches
end
# Model
class Match < ApplicationRecord
belongs_to :home_team, class_name: 'Team', foreign_key: 'home_team_id'
belongs_to :away_team, class_name: 'Team', foreign_key: 'away_team_id'
end
# Migration
class CreateMatches < ActiveRecord::Migration[5.2]
def change
create_table :matches do |t|
t.references :home_team, references: :team, foreign_key: { to_table: :teams}
t.references :away_team, references: :team, foreign_key: { to_table: :teams}
t.timestamps
end
end
end
Now I want to access to matches of a certain team, so I do it like this:
Team.first.matches
But obviously, it doesn't work because there is no 'team_id' column in matches table. So, how to get access to all matches of a certain team?
You can always just have your own method on the model.
class Team < ApplicationRecord
def matches
Match.where('home_team_id = :team_id OR away_team_id = :team_id', team_id: self.id)
end
end
You will of course miss out on some goodies has_many provides, but you can use this easily in your views/controllers, with extra where or order as needed.
= Team.find(1).matches.where(...).order(...)
here are the additions that I have made to my models:
class Event < ApplicationRecord
has_one :lineup
has_many :artists, :through => :lineup
belongs_to :venue
end
class Lineup < ApplicationRecord
belongs_to :artist
belongs_to :event
end
class Artist < ApplicationRecord
has_many :events, :through => :lineups
end
class Venue < ApplicationRecord
has_many :events
end
not asking for help with generating migrations for all of these associations, but could you at least show me how to do it for Event?
Please find below migrations for Event & Lineup (that have the keys to enable your model associations):
class CreateEvents < ActiveRecord::Migration
def change
create_table :events do |t|
t.references :venue, index: true, foreign_key: true
t.timestamps null: false
end
end
end
class CreateLineups < ActiveRecord::Migration
def change
create_table :lineups do |t|
t.references :artist, index: true, foreign_key: true
t.references :event, index: true, foreign_key: true
t.timestamps null: false
end
end
end
To generate them, you can use:
rails g migration create_events venue:references
rails g migration create_lineups artist:references event:references
If Event & Lineup already exist, you can generate the migrations as follows:
rails g migration add_reference_to_events venue:references
rails g migration add_references_to_lineups artist:references event:references
Migrations generated should be as follows:
class AddReferenceToEvents < ActiveRecord::Migration
def change
add_reference :events, :venue, index: true, foreign_key: true
end
end
class AddReferencesToLineups < ActiveRecord::Migration
def change
add_reference :lineups, :artist, index: true, foreign_key: true
add_reference :lineups, :event, index: true, foreign_key: true
end
end
belongs_to will place the foreign key in the declaring model whereas has_one will place it in the other model. There are good resources in this out there that I would recommend taking a look at. Here's one.
So for the event model I would do the following:
$ rails g migration AddVenueToEvents
Then fill it in with:
class AddVenueToEvents < ActiveRecord::Migration
def change
add_reference :events, :venue, index: true, foreign_key: true
end
end
I would strongly recommend making use of the something like the Shoulda gem in combination with RSpec as it provides extremely valuable feedback about what you should be doing. Which would allow you to write some specs:
RSpec.describe Events, type: :model do
#Associations
it { should belong_to(:venue) }
it { should have_one(:lineup) }
it { should have_many(:artists).through(:lineup) }
The awesome thing is that once you run the your specs shoulda/rspec will give you extremely useful feedback in the terminal essentially telling you where a required Foreign Key may be missing. The message might look something like this:
Region should have a city
Failure/Error: should belong_to(:city)
Expected Region to have a belongs_to association called city (Region does not have a city_id foreign key.)
# ./spec/models/region_spec.rb:5:in `block (2 levels) in <top (required)>'
as shown in this other SO post which is somewhat related.
Please check this migration for events.
class CreateEvents < ActiveRecord::Migration
def change
create_table :events do |t|
t.belongs_to :venue, index: true
t.timestamps null: false
end
end
end
I am doing self association in rails. I have Request model which should reference itself. Here is what i have:
class Request < ActiveRecord::Base
has_many :sub_requests, class_name: 'Request',
foreign_key: 'parent_request_id'
belongs_to :parent_request, class_name: 'Request'
end
and my migration:
class CreateRequests < ActiveRecord::Migration
def change
create_table :requests do |t|
t.references :parent_request, index: true
end
end
end
But I get an error as follows:
PG::UndefinedColumn: ERROR: column requests.parent_request_id does not exist
Have you run rake db:migrate? If you did, than try this one:
class Request < ActiveRecord::Base
has_many :sub_requests, class_name: 'Request', foreign_key: 'parent_request_id', primary_key: 'id'
belongs_to :parent_request, class_name: 'Request', foreign_key: 'parent_request_id', primary_key: 'id'
end
class CreateRequests < ActiveRecord::Migration
def change
create_table :requests do |t|
t.integer :parent_request_id, index: true
end
end
end
I have two separate models: "page" and "user". I want to have a "comment" model that can comment on either a "page" or a "user", but not both at the same time. I want to do something like this:
class Comment < ActiveRecord::Base
belongs_to :page
belongs_to :user
end
but I'm not sure if that's the correct approach. What's the best way to handle this case?
It seems that what you need is Polymorphic Associations.
class Comment < ActiveRecord::Base
belongs_to :commentable, polymorphic: true
end
class User < ActiveRecord::Base
has_many :comments, as: :commentable
end
class Page < ActiveRecord::Base
has_many :comments, as: :commentable
end
And the migrations:
class CreateUsers < ActiveRecord::Migration # and similar for Pages
def change
create_table :users do |t|
...
t.references :commentable, polymorphic: true
...
end
end
end
class CreateComments < ActiveRecord::Migration
def change
create_table :comments do |t|
...
t.integer :commentable_id
t.string :commentable_type
...
end
end
end