Rails: How to Get Has_Many Associations of a Model

RAILS: How to get has_many associations of a model

You should be using ActiveRecord reflections.

Then you can type something like this:

A.reflect_on_all_associations.map { |assoc| assoc.name}

which will return your array

[:B, :C]

How to get all has_many associations from an active record collection

Given that @taskflows is an ActiveRecord::Relation, you can do this:

@datasets = Dataset.joins(:dataset_assignments).
where(dataset_assignments: {taskflow: @taskflows.joins(:datasets) })

or, in the other direction:

@taskflows = Taskflow.joins(:dataset_assignments).
where(dataset_assignments: {dataset: @datasets.joins(:taskflows) })

joins generates INNER JOINs through many-to-many table and having the receiver being an instance of ActiveRecord::Relation will preserve other conditions.

As @oreoluwa suggested, you can avoid N+1 queries when enumerating by using includes:

@taskflows = Taskflow.joins(...).includes(:datasets)

Rails Has Many Association

First ... a word of caution: That railscast is very old. There may be syntactical things in that episode that have been dated by new versions of rails.



Question 1

If you are using the has_many through method then you have to have an id column in the join model because you are using a full blown model. As Ryan mentions in the episode, you'll choose this method if you need to track additional information. If you use the has_and_belongs_to_many method, you will not have an id column in your table.


If you want to achieve a check where you do not allow duplicates in your many-to-many association (ie allow the pairing of item a with item b and again allowing another record of item a to item b), you can use a simple validates line with a scope:

validates_uniqueness_of :model_a_id, :scope => [:model_b_id]


Question 2

You can add indices in your migrations with this code

add_index :table_name, [ :join_a_id, :join_b_id ], :unique => true, :name => 'by_a_and_b'

This would be inserted into the change block below your create_table statement (but not in that create_table block). Check out this question for some more details: In a join table, what's the best workaround for Rails' absence of a composite key?



Question 3

I'm not completely clear on what you're looking to accomplish but if you want to take some action every time a new record is inserted into the join model I would use the after_create active record hook. That would look something like this.

class YourJoinModel < ActiveRecord::Base

after_create :do_something

def do_something
puts "hello world"
end
end

That function, do_something, will be called each time a new record is created.



Question 4

Using the has_many through method will give you access to the additional attributes that you defined in that model on both sides of the relationship. For example, if you have this setup:

class Factory < ActiveRecord::Base
has_many :widgets, :through => :showcases
end

class Widget < ActiveRecord::Base
has_many :factories, :through => :showcases
end

class Showcases < ActiveRecord::Base
belongs_to :factory
belongs_to :widget

attr_accessiable :factory_id, :widget_id, :visible
end

You could say something like

widget = Widget.first
shown = widget.showcases
shown.first.visible

or

shown = widget.showcases.where( :visible=> true )

You can also reach to the other association:

shown.first.factory

in rails, how to return results from two has_many associations under another has_many association or a scope

Unfortunely this is not really a case that ActiveRecord assocations really can handle that well. Assocations just link a single foreign key on one model to a primary key on another model and you can't just chain them together.

If you wanted to join both you would need the following JOIN clause:

JOINS matches ON matches.mentor_id = entities.id 
OR matches.mentee_id = entities.id

You just can't get that as assocations are used in so many ways and you can't just tinker with the join conditons. To create a single assocation that contains both categories you need a join table:

Database diagram

class Entity < ApplicationRecord

# Easy as pie
has_many :matchings
has_many :matches, through: :matchings

# this is where it gets crazy
has_many :matchings_as_mentor,
class_name: 'Matching',
->{ where(matchings: { role: :mentor }) }

has_many :matches_as_mentor,
class_name: 'Match',
through: :matchings_as_mentor

has_many :matchings_as_mentee,
class_name: 'Matching',
->{ where(matchings: { role: :mentee }) }

has_many :matches_as_mentee,
class_name: 'Match',
through: :matchings_as_mentor
end

class Matching < ApplicationRecord
enum role: [:mentor, :mentee]
belongs_to :entity
belongs_to :match
validates_uniqueness_of :entity_id, scope: :match_id
validates_uniqueness_of :match_id, scope: :role
end

class Match < ApplicationRecord
# Easy as pie
has_many :matchings
has_many :entities, through: :matching

# this is where it gets crazy
has_one :mentor_matching,
class_name: 'Matching',
->{ where(matchings: { role: :mentee }) }
has_one :mentor, through: :mentor_matching, source: :entity

has_one :mentee_matching,
class_name: 'Matching',
->{ where(matchings: { role: :mentor }) }
has_one :mentee, through: :mentor_matching, source: :entity
end

And presto you can reap the rewards of having a homogenous association:

entities = Entity.includes(:matches)
entities.each do |e|
puts e.matches.order(:name).inspect
end

It is a lot more complex though and you need validations and constraints to ensure that a match can only have one mentor and mentee. its up to you to evaluate if its worth it.

The alternative is doing somehing like:

Match.where(mentor_id: entity.id)
.or(Match.where(mentee_id: entity.id))

Which cannot does not allow eager loading so it would cause a N+1 query issue if you are displaying a list of entites and their matches.

Get first association from a has many through association

Assuming you are on Postgresql

Playlist.
select("DISTINCT ON(playlists.id) playlists.id,
songs.id song_id,
playlists.name,
songs.name first_song_name").
joins(:songs).
order("id, song_id").
map do |pl|
[pl.id, pl.name, pl.first_song_name]
end

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

Rails 4 - How to query a has_many model to find objects based on associations

Use

@posts = Post.includes(:votes).where('votes.user_id <> ? or votes.user_id is null', current_user.id)

I suppose that your query is not retrieving the 3 records as votes.user_id is null for them.



Related Topics



Leave a reply



Submit