Add record to a has_and_belongs_to_many relationship
Asked Answered
N

5

30

I have two models, users and promotions. The idea is that a promotion can have many users, and a user can have many promotions.

class User < ActiveRecord::Base
  has_and_belongs_to_many :promotions
end

class Promotion < ActiveRecord::Base
  has_and_belongs_to_many :users
end

I also have a promotions_users table/model, with no id of its own. It references user_id and promotions_id

class PromotionsUsers < ActiveRecord::Base
end

So, how do I add a user to a promotion? I've tried something like this:

user = User.find(params[:id])
promotion = Promotion.find(params[:promo_id])
promo = user.promotions.new(promo)

This results in the following error:

NoMethodError: undefined method `stringify_keys!' for #<Promotion:0x10514d420>

If I try this line instead: promo= user.promotions.new(promo.id)

I get this error:

TypeError: can't dup Fixnum

I'm sure that there is a very easy solution to my problem, and I'm just not searching for the solution the right way.

Nichani answered 29/4, 2010 at 19:10 Comment(0)
A
54
user = User.find(params[:id])
promotion = Promotion.find(params[:promo_id])
user.promotions << promotion

user.promotions is an array of the promotions tied to the user.

See the apidock for all the different functions you have available.

Amparoampelopsis answered 29/4, 2010 at 19:32 Comment(3)
Thanks for the quick response. I figured I was overcomplicating things.Nichani
does << also save the association?Crocodile
@Coolguy123 yes, collection<<(object, …) is an alias of collection.push and collection.concat. This instantly fires update sql without waiting for the save or update call on the parent object.Argon
B
11

You can do just

User.promotions = promotion #notice that this will delete any existing promotions

or

User.promotions << promotion

You can read about has_and_belongs_to_many relationship here.

Bearskin answered 29/4, 2010 at 19:32 Comment(4)
Be careful with User.promotions = promotion as that will delete any existing and add the one passed in.Amparoampelopsis
j., i haven't seen railsapi.com. This is awesome! So much better than api.rubyonrails.org.Hamish
@Tony: Yes, I know that :] Tks.Bearskin
pretty sure you mean user.promotions not User.promotions. The association's methods are on an instance not the class.Bodice
E
10

This is also useful

User.promotion.build(attr = {})

so, promotion object saves, when you save User object.

And this is

User.promotion.create(attr = {})

create promotion you not need to save it or User model

Etty answered 29/4, 2010 at 19:41 Comment(0)
S
1

If you want to add a User to a Promotion using a prototypical PromotionsController CRUD setup and you're not using Rails form helpers, you can format the params as:

params = {id: 1, promotion: { id: 1, user_ids: [2] }}

This allows you to keep the controller slim, e.g., you don't have to add anything special to the update method.

class PromotionsController < ApplicationController

  def update
    promotion.update(promotion_params)

    # simplified error handling
    if promotion.errors.none?
      render json: {message: 'Success'}, status: :ok
    else
      render json: post.errors.full_messages, status: :bad_request
    end
  end

  private

  def promotions_params
    params.require(:promotion).permit!
  end

  def promotion
    @promotion ||= Promotion.find(params[:id])
  end
end

The result would be:

irb(main)> Promotion.find(1).users
=> #<ActiveRecord::Associations::CollectionProxy [#<User id: 2 ...>]>
Shylashylock answered 29/4, 2020 at 22:16 Comment(0)
A
0

For all those in the current times, Rails does have built-in functions that are like simpler associations.

For building (i.e. Promotion.new), you can use

user.promotions.build(promotion_attributes)

For creating it's the same

user.promotions.create(promotion_attributes)

Just wanted to give a more familiar option. It's outline in the apidoc The other answers work as well.

Attar answered 16/2, 2023 at 12:32 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.