Rails Model Has_Many , Belongs_To Relations

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



Leave a reply



Submit