Rails HABTM middle values - ruby-on-rails

I have to following relationship:
class Course < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :users
end
class User < ActiveRecord::Base
attr_accessible :name
has_and_belongs_to_many :courses
end
Then I have the following table:
create_table :courses_users, :force => true, :id => false do |t|
t.integer :user_id
t.integer :course_id
t.integer :middle_value
end
How can I access (edit/update) the middle value in the many to many record?

HABTM should be used to only store the relation. If you have any fields you want to store in the relation, you should create another model, eg. CourseSignup. You would then use this model to create a has_many :through => :course_signups relation, so your models would look like this:
class Course < ActiveRecord::Base
has_many :course_signups
has_many :users, :through => :course_signups
end
class CourseSingup < ActiveRecord::Base
belongs_to :course
belongs_to :user
end
class User < ActiveRecord::Base
has_many :course_signups
has_many :courses, :through => :course_signups
end
Then you can add your middle_value in the CourseSignup model.
You can find more details in the guide to ActiveRecord associations.

You want a has_many :though, not a HABTM.
A HABTM does not have a join model, but a has_many :through does. Something like:
class Course < ActiveRecord::Base
has_many :enrollments
has_many :users, :through => :enrollments
end
class Enrollment < ActiveRecord::Base
belongs_to :course
belongs_to :user
end
class User < ActiveRecord::Base
has_many :enrollments
has_many :courses, :through => :enrollments
end

Related

Trouble with model associations

The goal is for a shop to create rewards and associate each reward to a follower of his choice. This is my setup:
class Shop < ApplicationRecord
has_many :rewards
has_many :follows
has_many :users, through: :follows
end
class Reward < ApplicationRecord
belongs_to :shop
end
class Follow < ApplicationRecord
belongs_to :shop
belongs_to :user
has_many :reward_participant
end
class User < ApplicationRecord
has_many :follows
has_many :shops, through: :follows
end
I created this model in order to capture the reward and follower association.
class RewardParticipant < ApplicationRecord
belongs_to :reward
belongs_to :follow
end
And I have created the following migrations:
class CreateRewards < ActiveRecord::Migration[6.0]
def change
create_table :rewards do |t|
t.string :title
t.text :body
t.date :expires
t.integer :shope_id
t.timestamps
end
end
end
class CreateRewardParticipants < ActiveRecord::Migration[6.0]
def change
create_table :reward_participants do |t|
t.integer :reward_id
t.integer :follow_id
t.timestamps
end
end
end
I'm having trouble figuring out if this is the correct approach to the model associations and migrations. Thanks for the help in advance!
Generally you are right.
We want users to follow a shop, and a shop can create rewards and grant many rewards to many followers.
1. Visual schema:
2. Model associations (complete version)
user.rb
has_many :follows
has_many :reward_follows, through: :follows
has_many :rewards, through: :reward_follows # NOT through shops
has_many :shops, through: :follows
follow.rb
belongs_to :user
belongs_to :shop
has_many :reward_follows
shop.rb
has_many :rewards
has_many :reward_follows, through: :rewards # NOT through follows
has_many :follows
has_many :users, through: :follows
reward.rb
has_many :reward_follows
belongs_to :shop
has_many :follows, through: :reward_follows
has_many :users, through: :follows
3. Do not use date field. Use datetime field.
Justification: https://www.ruby-forum.com/t/time-without-date/194146
This personally saved me hours of work long-term.

Ruby on Rails Nested Form

I have three Models: Deal, Zipcode, DealIncludeZipcode.
Now, the association looks like below:-
Deal Model:
class Deal < ActiveRecord::Base
has_many :deal_include_zipcodes, dependent: :destroy
has_and_belongs_to_many :zipcodes, dependent: :destroy
accepts_nested_attributes_for :deal_include_zipcodes,:reject_if => :reject_include_zipcodes, allow_destroy: true
private
def reject_include_zipcodes(attributes)
if attributes[:deal_id].blank? || attributes[:zipcode_id].blank?
if attributes[:id].present?
attributes.merge!({:_destroy => 1}) && false
else
true
end
end
end
end
class Zipcode < ActiveRecord::Base
has_and_belongs_to_many :deals
end
class DealIncludeZipcode < ActiveRecord::Base
belongs_to :deal
belongs_to :zipcode
end
Now in view I have a checkbox,on unchecking it I can select multiple zipcode to select from DealIncludeZipcode.But when I save the data it is not saving.
I have used migration for joining Zipcode and Deal Model in which my exclude zipcode functionality is working correctly.
Please provide a soloution.I have tried various method but didn't got succeed.
The whole point of has_and_belongs_to_many is that you don't have a model which joins the two parts.
class Deal < ActiveRecord::Base
has_and_belongs_to_many :zipcodes
end
class Zipcode < ActiveRecord::Base
has_and_belongs_to_many :deals
end
Would join through a "headless" table called deals_zipcodes. If you want to have a join model you need to use has_many :through instead.
class Deal < ActiveRecord::Base
has_many :deal_zipcodes, dependent: :destroy
has_many :zipcodes, through: :deal_zipcodes
end
class DealZipcode < ActiveRecord::Base
belongs_to :deal
belongs_to :zipcode
end
class Zipcode < ActiveRecord::Base
has_many :deal_zipcodes, dependent: :destroy
has_many :deals, through: :deal_zipcodes
end
I think Max's right. So your migration should be
create_table :deals do |t|
t.string :name
...
end
create_table :zipcodes do |t|
t.string :zipcode
...
end
create_table :deals_zipcodes do |t|
t.belongs_to :deal, index: true
t.belongs_to :zipcode, index: true
end
And your models should be
class Deal < ActiveRecord::Base
has_and_belongs_to_many :zipcodes
end
class Zipcode < ActiveRecord::Base
has_and_belongs_to_many :deals
end
You should probably take a look at the ActiveRecord guide, where you'll find more explanation.

Add extra field in Rails habtm joins

I have this relationship between users, teams
class CreateTeamsUsers < ActiveRecord::Migration
def change
create_table :teams_users, :id => false do |t|
t.references :user
t.references :team
t.timestamps
end
end
end
class User < ActiveRecord::Base
has_and_belongs_to_many :teams
end
class Team < ActiveRecord::Base
has_and_belongs_to_many :users
end
The issue is that I want to add extra attribute in HABTM,attribute name is "user_name"
How to do this?
Instead of HABTM you'd use has_many and has_many :through.
class User < ActiveRecord::Base
has_many :memberships
has_many :team, through: :membership
end
class Membership < ActiveRecord::Base # This would be your old 'join table', now a full model
belongs_to :user
belongs_to :team
end
class Team < ActiveRecord::Base
has_many :memberships
has_many :users, through: :memberships
end
Short version, you can't do what your're trying to do without a little refactoring. Here is how I would do it (apologies if there's syntax issues, I'm doing this from memory I haven't tested the code but the principle is sound)
Create a new model to represent "membership" of a team (maybe call it "Membership") and the associated migration to create the table:
class Membership
belongs_to :team
belongs_to :user
end
Then change your team and user models to use this new model:
class User
has_many :memberships
has_many :teams, through: :memberships
end
class Team
has_many :memberships
has_many :users, through: :memberships
end
Once you've refactored this far, adding additional columns / attributes to "memberships" is easy because you can just treat it like any other model.

Named many to many relations in Rails

How should I create following model in Rails 3.2? Project can have 1+ owners and 1+ users. Both of them are instances of class Person. I've thought about has_and_belongs_to_many but I don't know how to handle two separate collections of Persons for each Project.
You'll need a join model to represent each has-and-belongs-to-many relationship, and you would access using has-many-through as described here:
class ProjectOwnerLink < ActiveRecord::Base
belongs_to :project
belongs_to :owner, class_name: 'Person'
end
class ProjectUserLink < ActiveRecord::Base
belongs_to :project
belongs_to :user, class_name: 'Person'
end
class Project < ActiveRecord::Base
has_many :project_owner_links
has_many :owners, :through => :project_owner_links
has_many :project_user_links
has_many :users, :through => :project_user_links
end
class Person < ActiveRecord::Base
has_many :project_owner_links
has_many :owned_projects, :through => :project_owner_links, :source => :project
has_many :project_user_links
has_many :used_projects, :through => :project_user_links, :source => :project
end
You could define another model Participation that holds the type of the relationship, i.e. the role of the user. (Untested) code:
class Project < ActiveRecord::Base
has_many :participations
has_many :users, :through => :participations
def with_role(role)
includes(:participations).where('participation.role = ?', role)
end
def owners
users.with_role('owner')
end
def participants
users.with_role('participant')
end
end
 
class User < ActiveRecord::Base
has_many :participations
has_many :projects, :through => :participations
def with_role(role)
includes(:participations).where('participation.role = ?', role)
end
def projects_owned
projects.with_role('owner')
end
def projects_participating_in
projects.with_role('participant')
end
end
 
class Participation < ActiveRecord::Base
# has an attribute 'role'
belongs_to :project
belongs_to :user
end
Below is the demo application.
https://github.com/diatmpravin/habtm-demo.git
Please have a look, Let me know if you have any question?

counter_cache with has_many :through

I just created a counter_cache field and the controller looks like this.
#users = User.where(:sex => 2).order('received_likes_count')
The association in User.rb is
has_many :received_likes, :through => :attachments, :source => :likes, :dependent => :destroy
Problem is that counter_cache is declared in the belong_to of Like.rb and I don't know how to tell it that is for the has_many :through association.
belongs_to :user, :counter_cache => :received_likes
You have previous
class Product
has_and_belongs_to_many :categories
end
class Category
has_and_belongs_to_many :products
end
and migration
class CreateCategoriesProducts < ActiveRecord::Migration
def change
create_table :categories_products, id: false do |t|
t.references :category
t.references :product
end
add_index :categories_products, [:category_id, :product_id]
end
end
now change all to
class Product
has_many :categories_products, dependent: :destroy
has_many :categories, through: :categories_products
end
class Category
has_many :categories_products, dependent: :destroy
has_many :products, through: :categories_products
end
and new one
class CategoriesProduct < ActiveRecord::Base
# this model uses table "categories_products" as it is
# column products_count is in the table "categories"
belongs_to :category, counter_cache: :products_count
belongs_to :product
end
According to this post (from last month) and this post (from 2008), it doesn't seem to be possible. However, the latter post does have code for a workaround (copy/paste'd from that link for your convenience, credit goes to DEfusion in the second link)
class C < ActiveRecord::Base
belongs_to :B
after_create :increment_A_counter_cache
after_destroy :decrement_A_counter_cache
private
def increment_A_counter_cache
A.increment_counter( 'c_count', self.B.A.id )
end
def decrement_A_counter_cache
A.decrement_counter( 'c_count', self.B.A.id )
end
end
(This is for a scheme where C belongs_to B, B belongs_to A, A has_many C :through => B
This basically does the same thing:
after_save :cache_post_count_on_tags
def cache_post_count_on_tags
tags.each {|t| tag.update_attribute(:posts_count, t.posts.size)}
end
And you need a posts_count column on tags, or whatever associations you have.

Resources