2

I have the three models set up like the below (incomplete for brevity):

class Client < ActiveRecord::Base
  has_many :roles
  has_many :cases, through: :roles
end

class Role < ActiveRecord::Base
  belongs_to :client
  belongs_to :case
end

class Case < ActiveRecord::Base
  has_many :roles
  has_many :clients, through: :roles

  accepts_nested_attributes_for :roles, :clients
end

It's a simple has_many through association. I've set up the models to accept nested attributes for the appropriate associations, and permitted the parameters properly in my controller. I'm sending the below request body JSON to POST /cases which hits this method:

def create
  @case = Case.new(case_params)

  if @case.save
    render json: @case, root: 'case', status: :created, location: @case, serializer: CaseShowSerializer
  else
    render json: @case.errors, status: :unprocessable_entity
  end
end

And the JSON:

{
    "case": {
        "state": "CA",
        "clients_attributes": [
            {
                "first_name": "John",
                "last_name": "Doe",
                "email": "johndoe@gmail.com"
            }
        ]
    }
}

The case is created and the nested clients array are also created. Rails automatically creates Role records for each Client in the array of JSON. However, it only sets case_id and client_id (which associates the two). I ALSO want to set other fields on the Role model that it creates.

How can this be done?

Edit:

Both answers below (Oct 1 2015, 12:50PM PST) will not work. Using those answer's logic, a client can not have multiple roles (which is needed and explicitly defined in the model).

Example:

The roles CAN NOT nested inside of the case --

{
  "case": {
    "name": "test",
    "roles_attributes": [
      {
        "type": "Type 1",
        "client_attributes": {
          "name": "Client 1"
        }
      },
      {
        "type": "Type 2",
        "client_attributes": {
          "name": "Client 1"
        }
      }
    ]
  }
}

The JSON above will create the SAME client twice. The Client with a name of "Client 1" has two roles, one of the roles type is "Type 1" the other is "Type 2". The snippet above as suggested by the answer would create two clients and two roles. It should create one client (since it's the same client but that client has multiple roles).

Noah
  • 874
  • 7
  • 11
  • Favourited because you improved my knowledge, thank you :) – Richard Peck Oct 01 '15 at 18:56
  • https://github.com/matisoffn/so32894063 here's an example. Rails magically creates the association between case and client, but I want to be able to set data on the association it creates. If I can't intervene and do that, then I want to be able to create them manually and have it set the case_id and client_id, and specify more fields that I want to set. – Noah Oct 01 '15 at 21:01

2 Answers2

0

Instead of posting the clients_attributes, post the roles_attributes with the desired data for each role.

Each role in the roles_attributes can contain the nested client object.

When you do this, you might have trouble with saving a new role that references an existing client. Check the answer to this question for the solution.

Community
  • 1
  • 1
Sunil D.
  • 17,983
  • 6
  • 53
  • 65
  • This won't work. The reason is because if I do this, and one client has *TWO* roles, and I specify that client in each role object, it will create it twice. I've went down this road. – Noah Oct 01 '15 at 19:48
0

The only way I've been able to do this before is to define the various models' attributes as they are required. I wrote an answer about it a while back.

Your way is more concise (and better) than mine. However, you should be able to pass the data you need through it like I did:

{
    "case": {
        "state": "CA",
        "roles_attributes": [
           "name": "Admin",
           "client_attributes": [
               {
                   "first_name": "John",
                   "last_name": "Doe",
                   "email": "johndoe@gmail.com"
               }
           ]
        ]
    }
}

If you didn't want to use JSON, and had to populate your data from an HTML form etc, you'd use the following:

#app/controllers/cases_controller.rb
class CasesController < ApplicationController
   def new
      @case = Case.new
      @case.roles.build.build_client
   end

   def create
      @case = Case.new case_params
      @case.save
   end

   private

   def cases_params
      params.require(:case).permit(:state, roles_attributes: [:name, clients_attributes:[:first_name, :last_name, :email])
   end
end

You'd have to back this up with the appropriate model nested attributes:

#app/models/role.rb
class Role < ActiveRecord::Base
  belongs_to :client
  belongs_to :case

  accepts_nested_attributes_for :client
end

class Case < ActiveRecord::Base
  has_many :roles
  has_many :clients, through: :roles

  accepts_nested_attributes_for :roles
end

This would allow you to use the fields_for helper:

#app/views/cases/new.html.erb
<%= form_for @case do |f| %>
   <%= f.text_field :state %>
   <%= f.fields_for :roles do |role| %>
      <%= role.text_field :name %>
      <%= role.fields_for :client do |client| %>
         <%= client.text_field :first_name %>
         <%= client.text_field :last_name %>
         <%= client.email_field :email %>
      <% end %>
   <% end %>
   <%= f.submit %>
<% end %>
Community
  • 1
  • 1
Richard Peck
  • 76,116
  • 9
  • 93
  • 147
  • 1
    This will not work! Read my comment on the other answer. – Noah Oct 01 '15 at 19:49
  • Your JSON would not work. Read your JSON then read the model associations. You specify on the role a client_attributes array. A role can only belong to one client, not multiple. – Noah Oct 01 '15 at 19:52
  • Trying this just throws an "association not found, are you sure clients is on Role?" something like that (because the association is *client*, not *clients*). The reason is because it `belongs_to :client` so I can't pass an array, hence why your solution doesn't work and the JSON wouldn't work. – Noah Oct 01 '15 at 20:05
  • Thanks for the candidness :) I've detailed how I'd approach it, whilst if it's wrong I don't expect you to accept the answer, I'll leave it up either as a discussion point or something to work from – Richard Peck Oct 01 '15 at 20:26
  • Sorry for the reiteration. I've spent many hours on this and already went down this road as well as other avenues still to no avail. – Noah Oct 01 '15 at 20:27
  • Do you have a github repo I could look at? I just took what I knew before and applied to this. If I see it in context maybe it will give me ability to test something out – Richard Peck Oct 01 '15 at 20:33
  • I'll create an example app. – Noah Oct 01 '15 at 20:36
  • Made the README as descriptive as possible. – Noah Oct 01 '15 at 20:55