简体   繁体   中英

Rails adding element to one-to-many association

I have one-to-many relation between albums and posts:

class Post < ActiveRecord::Base
    belongs_to :album

class Album < ActiveRecord::Base
    has_many :posts, dependent: :destroy
    accepts_nested_attributes_for :posts

In AlbumsController , I would like to create a method for adding post to this relation (to Album.posts ):

def add_post
    @album = Album.find(params[:album_id])
    params['album']['post_ids'].each do |post_id|
      @album.posts << Post.find(post_id)
    end
end

Why doesn't it work? What is the correct way of doing this? I've also tried assigning @album.id to the album_id column of the post, but it doesn't do anything either.

Form looks like this:

    <%= form_for :album, url: album_add_post_path(@album), method: :patch do |f| %>  
        <% @posts.each do |post| %>  
            <%= check_box_tag :post_ids, post.id, @album.posts.include?(post), :name => 'album[post_ids][]' %>  
       <% end %>  
        <%= f.submit class:"btn btn-success"%>  
    <% end %>  

If you are using nested attributes, like you are, You don't necesarily have to loop through the associated posts to create. ( that is what the nested attributes is for )

However, you have to ensure that you are permitting these post_id s in the parameters. This is one of the most common mistakes ( I have made this a few times myself )

So, in your controller:

def add_post
  @album = Album.find(params[:album_id])
  @album.update(album_params)
end

Where the album_params method is as follow:

def album_params
  params.require(:album).permit(..., ..., ..., posts_attributes: [:id, ..., ...])
end

The important things to note here:

  1. the posts_attributes comes in as an array of the attributes for all the associated attributes ( posts_attributes: [] )
  2. the id should be permitted for associated attributes, or else new associations will always be created each time there is an edit/update
  3. all attributes of the parent object ( album ) needed to be allowed are listed directly in the permit block ( params.require(:album).permit(..., ..., )
  4. all attributes of the child object ( post ) needed to be allowed are listed in the nested attributes block. ( posts_attributes: [:id, ..., ...] )

NOTE: The front-end view form has to be set right as well. An example can be found on this rails cast , And you can check the documentation for more information on this.

I just saw your update with the front-end view. You can leverage the nested attributes you are already using and that I've explained by extending the album's post as follow:

<%= f.fields_for :posts do |p| %>
  <%= p.collection_select :post_id, Post.order(:name),:id,:name, include_blank: true %>
<% end %>

The direct fix for your code is...

#config/routes.rb
resources :albums do
   match :posts, via: [:get, :patch], on: :member #-> url.com/albums/:id/posts
end

#app/controllers/albums_controller.rb
class AlbumsController < ApplicationController
   def posts
       @album = Album.find params[:id]
       @posts = Post.all if request.get?
       if request.patch?
         @album.update album_params
         @album.save
       end
   end 

   private

   def album_params
       params.require(:album).permit(post_ids: [])
   end
end

This can be accompanied with a collection_select :

#app/views/albums/posts.html.erb
<%= form_for @album do |f| %>
   <%= f.collection_select :post_ids, @posts, :id, :name %>
   <%= f.submit %>
<% end %>

However , this will not work (I presume) as you'd hope.

When using belongs_to / has_many , you can only have matching "pairs" of relations. This means that adding to the singular_collection_ids method will override any existing belongs_to reference in the Post records.

In short, if you want to change (not add) your associated objects, it will work. If you want to add existing posts to an album , you'll have to use the code below.


You'd be best using has_and_belongs_to_many :

#config/routes.rb
resources :album do
   resources :posts #-> url.com/albums/:album_id/posts/new 
end

#app/models/album.rb
class Album < ActiveRecord::Base
   has_and_belongs_to_many :posts
end 

#app/models/post.rb
class Post < ActiveRecord::Base
   has_and_belongs_to_many :albums
end

#app/controllers/albums_controller.rb
class AlbumsController < ApplicationController
    def edit
       @album = Album.find params[:id]
       @posts = Post.all
    end

    def update
       @album = Album.find params[:id]
       @album.update album_params
    end

    private

    def album_params
       params.require(:album).permit(post_ids: [])
    end
end

#app/views/albums/edit.html.erb
<%= form_for @album do |f| %>
   <%= f.collection_select :post_ids, @posts, :id, :name %>
   <%= f.submit %>
<% end %>

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM