Rails model relationship, has many and belongs to many?
Well, you're going to run into some issues with the fact that you want a double many-to-many relationship here. Ie. groups have and belong to many items, and items have and belong to many users.
So, I would setup the relationship this way, assuming you want a group to be able to have many items, and items may belong to more than one group:
User has_many :groups
User has_and_belongs_to_many :items
User has_many :own_items, :class_name => 'Item'
Group belongs_to :user
Group has_and_belongs_to_many :items
Item has_and_belongs_to_many :groups
Item has_and_belongs_to_many :users
Item belongs_to :owner, :class_name => 'User'
Your migrations will need to look like so:
# Group
:user_id, :integer
# Item
:owner_id, :integer
# GroupsItems
:group_id
:item_id
#ItemsUsers
:item_id
:user_id
Now, the structure you're looking at isn't the cleanest in the universe, but it will behave as you expect as long as you're careful about the user association.
For instance, to create a user's item:
@user = User.first
@user.own_items.create(...)
To assign users as able to view an item...
@item = Item.find(...) #or @user.own_items.find(...)
@item.users = [user1,user2,user3]
Now, this sets up the relationships you want, but you'll have to also write your own controller / view logic to limit access, or use a library like CanCan.
For instance:
# View
- if @item.users.include?(current_user)
...show item...
# Items Controller:
def show
@item = Item.find(params[:id])
if @item.users.include?(current_user)
...continue...
else
redirect_to :back, :alert => 'You are not authorized to view this item.'
end
end
I hope those examples point you in the right direction. You'll have a number of issues to deal with relating to access control, but trying to think of them and solve each one I can think of is beyond the scope of this question.
Also, note that this is the simplest setup I could think of. If you have more complex logic in the associations you might want to make a full-fledged join model and use has_many :through associations instead of HABTM.
Good luck!
Rails ActiveRecord relationships - has many and belongs to associations
It depends whether you want a join model or not. A join model lets you hold extra information against the association between two other models. For example, perhaps you want to record a timestamp of when the article was tagged. That information would be recorded against the join model.
If you don't want a join model, then you could use a simple has_and_belongs_to_many
association:
class Article < ActiveRecord::Base
has_and_belongs_to_many :tags
end
class Tag < ActiveRecord::Base
has_and_belongs_to_many :articles
end
With a Tagging
join model (which is a better name than ArticleTag
), it would look like this:
class Article < ActiveRecord::Base
has_many :taggings
has_many :tags, :through => :taggings
end
class Tag < ActiveRecord::Base
has_many :taggings
has_many :articles, :through => :taggings
end
class Tagging < ActiveRecord::Base
belongs_to :article
belongs_to :tag
end
- A Guide to Active Record Associations
has_many and belongs_to of the same model
You'll need 2 separate relationships between the User and Song models. Namely, you'll need an 'owner' relationship and a 'favorite' relationship. The 'owner' relationship can be a simple has_many/belongs_to as you have it now. The 'favorite' relationship is many-to-many and will need a join table used either as a habtm
table or a first class model with a has_many through
relationship as explained here.
The generallly recommended approach is to use has_many through
as it gives you better control:
class User
has_many :songs # these are songs 'owned' by the user
has_many :user_favorite_songs
has_many :favorite_songs, :through => :user_favorite_songs # these are the favorites
end
class Song
belongs_to :user
has_many :user_favorite_songs
end
class UserFavoriteSong
belongs_to :user
belongs_to :favorite_song, :class_name => 'Song', :foreign_key => :song_id
end
Rails Model, belongs to many
Many-to-many relationships in rails don't use belongs_to
. Instead, you want to use one of a couple options. The first is has_and_belongs_to_many
:
# app/models/category.rb
class Category < ActiveRecord::Base
has_and_belongs_to_many :items
end
# app/models/item.rb
class Item < ActiveRecord::Base
has_and_belongs_to_many :categories
end
And you'll need to add an extra join table in your database, with a migration like this:
class AddCategoriesItems < ActiveRecord::Migration
def self.up
create_table :categories_items, :id => false do |t|
t.integer :category_id
t.integer :item_id
end
end
def self.down
drop_table :categories_items
end
end
You can see that the join table's name is the combination of the two other tables' names. The tables must be mentioned in alphabetical order as above, and the :id => false
needs to be there, since we don't want a primary key on this table. It will break the rails association.
There's also another, more complex method known as has_many :through
if you need to store info about the relationship itself. I've written a whole article detailing how to do both methods, and when to use each:
Basic many-to-many Associations in Rails
I hope this helps, and contact me if you have any other questions!
How do I do two has_many/belongs_to relationships between two models?
Its similar to the way belongs_to
is defined in the other class.
So Basically
class Contact < ActiveRecord::Base
has_many :projects_owned, :class_name => "Project", :foreign_key => "owner_id"
has_many :projects_as_client, :class_name => "Project", :foreign_key => "client_id"
end
names of associations could be better. The Single Table inheritance approach described before me is also a neat way, but go for it if you have a lot of different behaviour for each of the OwnerContact and ClientContact class, otherwise it might be just a useless overhead.
How do I specific belongs_to and has_many relationships when generating new models
When passed as a generator argument belongs_to
in is just an alias of references
which tells rails to create a column named blog_id
which is a foreign key:
# rails generate model Post blog:belongs_to
class CreatePosts < ActiveRecord::Migration[5.0]
def change
create_table :posts do |t|
t.belongs_to :blog, foreign_key: true
t.timestamps
end
end
end
This is the actual database column that defines the relation between two tables.
It also adds the association to the model:
class Post < ApplicationRecord
belongs_to :blog
end
Why does'nt the same work for has_many
?
The arguments for the model generators are attributes of the model. blog_id
is an actual attribute backed by a database column.has_many
is not an attribute. It's a metaprogramming method which adds a posts
method to your Blog instances. You need to add it manually to the model.
If you run rails g model Blog posts:has_many foo:bar
Rails will actually create a migration with those attributes:
class CreateBlogs < ActiveRecord::Migration[5.0]
def change
create_table :blogs do |t|
t.has_many :posts
t.bar :foo
t.timestamps
end
end
end
Rails does not type check the arguments. Of course the migration won't actually run:
undefined method `has_many' for #<ActiveRecord::ConnectionAdapters::PostgreSQL::TableDefinition:0x007fd12d9b8bc8>
If you have already generated the migration just remove the line t.has_many :posts
and add has_many :posts
to app/models/blog.rb
.
Selecting one of a has_many relation in Rails
You can add an additional one-to-one relationship.
# create by running:
# rails g migration AddPrimaryPictureToUser
class AddPrimaryPictureToUser < ActiveRecord::Migration[5.0]
def change
add_column :users, :primary_picture_id, :integer
add_index :users, :primary_picture_id
add_foreign_key :users, :pictures, column: :primary_picture_id
end
end
class User < ApplicationRecord
has_many :pictures, as: :imageable
# This should be belongs_to and not has_one as the foreign key column is
# on the users table
belongs_to :primary_picture,
class_name: 'Picture',
optional: true
end
class Picture < ApplicationRecord
belongs_to :imageable, polymorphic: true
has_one :primary_user,
foreign_key: 'primary_picture_id',
class_name: 'User'
# ...
end
The main reason to do it this way vs for example a boolean flag on the pictures
table is that having a separate relationship makes it easy to join which is important for avoiding N+1 queries which is an issue if you are listing a bunch of users together with their primary image.
@users = User.includes(:primary_picture).all
Accessing Rails model information across belongs_to / has_many relationships?
The variables you set in your controller prepended by an @ are called instance variables and are available in your view.
@provider = Provider.find(params[:id])
returns a single record
@lunches = @provider.lunches
returns an ActiveRecord::Relation
When you call @provider.lunches.lunchscore
, you are calling an instance method that is to be called on a single Lunch record but right now you are dealing with a Relation of Lunch records.
For example, to select single lunch records, you could loop over the lunches
@lunches.each do |lunch|
lunch.lunchscore
end
or you could only select the first record in the relation
@lunches.first.lunchscore
I hope this clarifies the difference between a record and a relation.
Related Topics
Hash['Key'] to Hash.Key in Ruby
Ruby - Using Class_Eval to Define Methods
God VS. Monit for Process Monitoring
Message Queues in Ruby on Rails
How to Convert Array of Activerecord Models to CSV
Where to Place/Access Config File in Gem
Ruby Working on Array Elements in Groups of Four
Find Out If an Ip Is Within a Range of Ips
Rubocop Line Length: How to Ignore Lines with Comments
Tutorials or Screencasts on Building a Rest Web Service on Rails
How to Capitalize the First Letter in a String in Ruby
Disable a Group of Tests in Rspec
Rails Mapping Array of Hashes Onto Single Hash
Ruby - Elegantly Convert Variable to an Array If Not an Array Already
Indentation Sensitive Parser Using Parslet in Ruby