How to Assign a Has_Many/Belongs_To Relation Properly in Rails Activerecord

How to assign a has_many/belongs_to relation properly in Rails ActiveRecord?

There is not difference between the 3 following:

my_article.author_id = author_one.id
my_article.save
# same as
my_article.author = author_one
my_article.save
# same as
author_one.articles << my_article

To set the owner of a particular post, the most common and readable way would be:

post.author = author
post.save

OR shorter version:

post.update_attributes(author_id: author.id) # call an implicit save

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

Proper way to set up models and relationships for a rails app, has_many through

Try:

<p>
<strong>Participants:</strong>
<% @game.users.each do |user| %>
<br><%= user.name %>
<% end %>
</p>

Creating a has_and_belongs_to_many relationship in Rails

I would like the teacher to have many users but each user to have only one teacher

You only need has_many / belongs_to...

#app/models/user.rb
class User < ActiveRecord::Base
belongs_to :teacher
end

#app/models/teacher.rb
class Teacher < ActiveRecord::Base
has_many :users
end

You'll need to add a teacher_id column in your users table (the opposite of what you have now):

class UpdateUsers < ActiveRecord::Migration
def change
change_table :users do |t|
t.references :teacher, index: true, foreign_key: true #-> teacher_id
end
end
end

--

The error you have is that you're calling user_id on teacher; it should be teacher_id on user:

@teacher = Teacher.new(name: "Phred Willard",
email: "pwillard@test.com",
phone: "1234567890",
user_ids: [@user.id,
@user2.id,
@user3.id,
@user4.id])

This should associate @teacher with the defined @users you've listed.

You'll also want to look at collection_singular_ids for the has_many association, which is why your test is failing.

Rails Converting a has_many relationship into a has and belongs to many

I suggest you to always use has_many :through instead of HBTM.

To establish this kind of relation you'll need the following set up:

# region.rb
class Region
has_many :facility_regions
has_many :facilities, through: :facility_regions
end

# facility.rb
class Facility
has_many :facility_regions
has_many :regions, through: :facility_regions
end

# facility_region.rb
class FacilityRegion
belongs_to :facility
belongs_to :region
end

Also, of course, you'll need to create a migration:

rails g migration create_facility_regions facility_id:integer region_id:integer
# in this migration create a uniq index:
add_index :facility_regions, %I(facility_id region_id), name: :facility_region
rake db:migrate

UPD

As to migration from one database state to another one.

I think it should not be a problem.

1) Do not delete the relations you had before (leave has_many :facilities and belongs_to :region in models).

2) When new table is created and new associations added to the classes (which I showed) create a new migration:

rails g migration migrate_database_state

3) Write the script, which will create new records in db (to reflect the current state of things):

ActiveRecord::Base.transaction do
Facility.where.not(region_id: nil).find_each do |facility|
next if FacilityRegion.find_by(falicity_id: facility.id, region_id: facility.region_id)
FacilityRegion.create!(facility_id: facility.id, region_id: facility.region_id)
end
end

4) Put this script into last created migration and run it (or in console without migration, effect would be the same).

5) After script is successfully run, create new migration in which you delete region_id from facilities table and remove these associations definitions (has_many :facilities and belongs_to :region) from models.

It must be it. I might have made some typos or so, make sure I did not miss anything and

Get items with Rails relations

As things stand, your relations are incomplete and so Rails won't work properly. If User has_many Tickets then Tickets must belong_to (or at least has_one) User. Alternatively, User can have_many Tickets through Group, which seems more likely in this case.

However, even then, it's not clear what your Group model is doing. Particularly, it's not clear how you intend it to relate to User - this looks like quite a complex relationship.

To start with, though, try and set the models up like this:

class Ticket < ApplicationRecord
belongs_to :group
end

class Group < ApplicationRecord
belongs_to :user
has_many :tickets, dependent: :destroy
end

class User < ApplicationRecord
has_many :groups, dependent: :destroy
has_many :tickets, through: :groups
end

(You'll see that I've also inherited these models from ApplicationRecord, which is how I've always done it.)

If you set it up as above, you can get your ticket records with a simple @user.tickets.

If this works, you can then add the extra HABTM relationship for Groups and Users. But be aware that HABTM relationships can be complex and there are good and bad ways to use them.

(If the primary relationship you really want is Groups > Users > Tickets then let me know and I can adjust accordingly.)

How to force a has_many association using a different column as foreign key

You need to specify a different primary key for the relationship if you wish to achieve what you are looking to do.

To clarify, this is not the same as changing the primary_key of the model. This way is only changing the primary key used by the relationship. Please see the bottom of this post for examples.

I changed the keys from both using custom_id and changed one to foo_id. This way you have a better idea of what is going on between the models. You can use both custom_id if you wish, but I would suggest keeping the rails norm of foo_id for the belongs_to association.

If you want to use both of custom_id you'll have to add some specific foreign_keys


Here are the models:

Foo

class Foo < ApplicationRecord
has_many :bars,
primary_key: :custom_id,
foreign_key: :foo_id
end

Bar

class Bar < ApplicationRecord
belongs_to :foo,
primary_key: :custom_id
end

The Migrations

CreateFoos

class CreateFoos < ActiveRecord::Migration[5.2]
def change
create_table :foos do |t|

t.integer :custom_id, index: {unique: true}
t.timestamps
end
end
end

CreateBars

class CreateBars < ActiveRecord::Migration[5.2]
def change
create_table :bars do |t|

t.integer :foo_id, index: true
t.timestamps
end
end
end

Here is the updated Test that should now be passing:

Test

require 'test_helper'

class BarTest < ActiveSupport::TestCase
test "the truth" do
foo = Foo.new(id: 1, custom_id: 100)
bar = Bar.new(foo: foo)

assert bar.foo_id == foo.custom_id
# bar.foo_id = 100
# foo.custom_id = 100
end
end

Examples

Foo.find(1) #<Foo id: 1, custom_id: 100>
Bar.first #<Bar id: 1, foo_id: 100>

Bar.first.foo = #<Foo id: 1, custom_id: 100>
Bar.first.foo == Foo.find(1) # true

As you can see, this method does not change the primary key of Foo itself. It changes the primary key the relationship between Foo and Bar uses. Bar is realated to foo via custom_id: 100, but foo is still found with it's id: 1 key, not its custom_id key.

How do I set up a has many through for two different associations between the the same models

It doesn't sound like you need a has_many through association for users hosting events. Something like this should work sufficiently for that (in user.rb) if you have a hosted_by_id column on your events table:

has_many :hosted_events, class_name: "Event", foreign_key: "hosted_by_id"

For attending events assuming a join class with columns attendee_id and event_id:

class AttendeeEvent < ActiveRecord::Base

belongs_to :attendee, class_name: "User"
belongs_to :event

end

You can add the following association to user.rb:

has_many :attendee_events, foreign_key: "attendee_id"
has_many :attended_events, through: :attendee_events, source: :event

The source: :event option indicates that the target objects for this association are found from the event association on the joining object.

The associations in event.rb are then:

belongs_to :hosted_by, class_name: "User"
has_many :attendee_events
has_many :attendees, through: :attendee_events

Rails: has_many through between two models and also a has_many/belongs to on the same models

I think something like this may solve your problem:

# User
has_many :answered_questions
has_many :questions, :through => :answered_questions
has_many :created_questions, class_name: Question, foreign_key: :author_id

# Question
has_many :answered_questions
has_many :users, :through => :answered_questions
belongs_to :author, class_name: User

# Answered questions
belongs_to :question
belongs_to :user


Related Topics



Leave a reply



Submit