Welcome to the Treehouse Community

Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community!

Looking to learn something new?

Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.

Start your free trial

Ruby

Rails Nested Attributes?

So I am not sure if nested attributes is what I need to know/learn but i'll give my scenario.

I have a model that is called "Inventory" this has 3 references "tablet_id, vehicle_id, employee_id"

I have 3 models, "Tablet", "Vehicle", "Employee" and in each I have a status column amongst other columns. ie. alias, serial, etc.

Now, in my "Inventory" form, I have 3 dropdowns and each generate from the models above.

<div class="form-group">
    <%= f.label :tablet_id, "Tablet #", class: "col-md-2 control-label" %>
    <div class="col-md-4">
      <%= select("inventory", "tablet_id", Tablet.where("status == '1'").all.collect{ |t| [t.alias, t.id] }, {}, { class: "form-control"} ) %>
    </div>
  </div>

  <div class="form-group">
    <%= f.label :employee_id, "Employee #", class: "col-md-2 control-label" %>
    <div class="col-md-4">
      <%= select("inventory", "employee_id", Employee.where("status == '1'").all.collect{ |t| [t.employeeNum, t.id] }, {}, { class: "form-control"} ) %>
    </div>
  </div>

  <div class="form-group">
    <%= f.label :vehicle_id, "Vehicle #", class: "col-md-2 control-label" %>
    <div class="col-md-4">
      <%= select("inventory", "vehicle_id", Vehicle.where("status == '1'").all.collect{ |t| [t.vehNum, t.id] }, {}, { class: "form-control"} ) %>
    </div>
  </div>

Now, when I submit this form the selected fields will go into the "Inventory" tables and on the Index page I can see a table that shows what I submitted.

BUT!

I want to also change the status of each selected field in their own model.

Meaning, if I select Tablet = "Tablet01", Employee = "Employee23", Vehicle = "Vehicle23" in the dropdowns I want to change their "status" to say "2".

How would I go about doing that?

2 Answers

Kevin Korte
Kevin Korte
28,147 Points

It would be inside your inventory model, since the form submits to the inventory model it sounds like, that's the model that has to know and expect that it will be getting data for other models.

So inventory will look like

class Inventory < ActiveRecord::Base
  belongs_to :tablet
  belongs_to :employee
  belongs_to :vehicle

  validates :tablet_id, :employee_id, :vehicle_id, presence: true
  validates :tablet_id, :employee_id, :vehicle_id, uniqueness: { message: " is already checked out." }

  accepts_nested_attributes_for :tablet
  accepts_nested_attributes_for :employee
  accepts_nested_attributes_for :vehicle

end

Now when your form submits to inventory, your params will be something like

params = { inventory: { table_id: 'id', employee_id: 'id', vehicle_id: 'id',
tablet_attributes: [{  //table attributes would be here }],
employee_attributes: [{ //employee attributes would be here }],
vehicle_attributes: [{ //vehicle attributes would be here }]
}}

And than you'll have 3 new methods available to the model tablet_attributes(attributes), employee_attributes(attributes), and vehicle_attributes(attributes).

Just play with it, it takes a little bit to get the hang of if, but the error messages I find are really helpful, and you should be fine.

Thanks! This does help. I do have another question (sorry).

For the attributes, I want to specify a predefined value. So for each models (tablet,employee,vehicle) they have a status and I want to say, take that submitted "tablet_id" and change the "status" from say "1" to "2'.

I dont see how the attributes would know which id would be correct, or does it?

Again, thanks so much for the help!

Kevin Korte
Kevin Korte
28,147 Points

Yes, you need nested attributes. Anytime you want to modify one model from another model, you'll need the :accepts_nested_attributes_for and than Rails will bundle the form submit up into a hash, which you can use to update the models.

http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html

Would I place this inside my Inventory model or each one of my other models (Tablet, Employee, Vehicle)??

Inventory Model

class Inventory < ActiveRecord::Base
  belongs_to :tablet
  belongs_to :employee
  belongs_to :vehicle

  validates :tablet_id, :employee_id, :vehicle_id, presence: true
  validates :tablet_id, :employee_id, :vehicle_id, uniqueness: { message: " is already checked out." }
end

Tablet Model

class Tablet < ActiveRecord::Base
  has_many :notes
  has_one :inventory

  validates :alias, :email, presence: true
  validates :alias, uniqueness: { message: " is already taken." }
end

Can't seem to get this. I must be missing or not understanding.

Inventory Form

<%= form_for(@inventory, html: {class: "form-horizontal"}) do |f| %>
  <% if @inventory.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(@inventory.errors.count, "error") %> prohibited this inventory from being saved:</h2>

      <ul>
      <% @inventory.errors.full_messages.each do |message| %>
        <li><%= message %></li>
      <% end %>
      </ul>
    </div>
  <% end %>

  <div class="form-group">
    <%= f.label :tablet_id, "Tablet #", class: "col-md-2 control-label" %>
    <div class="col-md-4">
      <%= select("inventory", "tablet_id", Tablet.where("status == '1'").all.collect{ |t| [t.alias, t.id] }, {}, { class: "form-control"} ) %>
    </div>
  </div>

  <%= f.fields_for :tablets do |t| %>
    <%= t.hidden_field :status, value: 2 %>
  <% end %>

  <div class="form-group">
    <%= f.label :employee_id, "Employee #", class: "col-md-2 control-label" %>
    <div class="col-md-4">
      <%= select("inventory", "employee_id", Employee.where("status == '1'").all.collect{ |t| [t.employNum, t.id] }, {}, { class: "form-control"} ) %>
    </div>
  </div>

  <%= f.fields_for :employees do |t| %>
    <%= t.hidden_field :status, value: 2 %>
  <% end %>

  <div class="form-group">
    <%= f.label :vehicle_id, "Vehicle #", class: "col-md-2 control-label" %>
    <div class="col-md-4">
      <%= select("inventory", "vehicle_id", Vehicle.where("status == '1'").all.collect{ |t| [t.vehNum, t.id] }, {}, { class: "form-control"} ) %>
    </div>
  </div>

  <%= f.fields_for :vehicles do |t| %>
    <%= t.hidden_field :status, value: 2 %>
  <% end %>

  <div class="form-group">
    <div class="col-md-2 col-md-offset-2">
      <%= f.submit class: "btn btn-sm btn-success" %>
    </div>
  </div>
<% end %>

My Inventory params

def inventory_params
      params.require(:inventory).permit(:tablet_id, :chauffeur_id, :vehicle_id, tablets_attributes: [:status], chauffeurs_attributes: [:status], vehicles_attributes: [:status])
    end