Rails3 Nested Attribute Validation and Control - ruby-on-rails

I'm rolling with a legacy database unfortunately and am trying to build my rails3 app around it.
Thanks to this previous post, I've figured out where I'm going but still think I'm approaching incorrectly.
My basic problem is that main my main data is stored in a table with multiple rows, each with a different attribute value:
+-----+----------+----------------+----+---------------+------------+
| id | username | attribute_name | op | value | raduser_id |
+-----+----------+----------------+----+---------------+------------+
| 173 | jenny | User-Password | := | March 25 2011 | 33 |
| 172 | jenny | User-Password | := | 1234 | 33 |
+-----+----------+----------------+----+---------------+------------+
2 rows in set (0.00 sec)
I was using a nested form to enter this information but it's not really doing what I need. I can add the nested attributes and set a field thanks to the previous question now.
The issue I have is that I need some more control over my user's inputs. For instance, I need to restrict them to three distinct attributes:
User-Password, Expiration, Simultaneous-Use
I also need to validate the fields. I can't do so with the nested form.
My plan was to get the user to enter these in the parent model and propagate down but I do not have a clue how to do this and save out to separate rows, like I do with my nested atributes.
Can anyone shed any light on this?
--UPDATE--
raduser.rb
class Raduser < ActiveRecord::Base
has_many :radcheck, :dependent => :destroy
accepts_nested_attributes_for :radcheck, :reject_if => lambda { |a| a[:value].blank? }, :allow_destroy => true
end
radcheck.rb
class Radcheck < ActiveRecord::Base
set_table_name 'radcheck'
attr_accessible :attribute_name, :username, :value, :op, :groupname
belongs_to :raduser
has_many :radusergroup, :dependent => :destroy, :primary_key => :username, :foreign_key => :groupname
has_many :radgroupcheck, :through => :radusergroup
before_save :sync_usernames
private
def sync_usernames
self.username = self.raduser.username
end
end

Did you try placing the validations in the radcheck.rb model? Try this code:
radcheck.rb
class Radcheck < ActiveRecord::Base
set_table_name 'radcheck'
attr_accessible :attribute_name, :username, :value, :op, :groupname
belongs_to :raduser
validates :attribute_name, :inclusion => { :in => %w(User-Password Expiration Simultaneous-Use) }
before_save :sync_usernames
private
def sync_usernames
self.username = self.raduser.username
end
end
raduser.rb
class Raduser < ActiveRecord::Base
has_many :radcheck, :dependent => :destroy
accepts_nested_attributes_for :radcheck, :reject_if => lambda { |a| a[:value].blank? }, :allow_destroy => true
end
radusers_controller.rb
def new
#raduser = Raduser.new
#raduser.radcheck.build
end
def create
#raduser = Raduser.new(params[:raduser])
if #raduser.save
redirect_to(#raduser, :notice => 'Raduser was successfully created.')
else
render :action => "new"
end
end
and finally the form
<% if #raduser.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#raduser.errors.count, "error") %> prohibited this raduser from being saved:</h2>
<ul>
<% #raduser.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= form_for #raduser do |f| %>
<p>
<%= f.label :username %><br />
<%= f.text_field :username %>
</p>
<%= f.fields_for :radcheck do |builder| %>
<li>
<%= builder.label :attribute_name %>
<%= builder.text_field :attribute_name %>
</li>
<% end %>
<p><%= f.submit "Submit" %></p>
<% end %>
When I tried to save with attribute name other than User-Password, Expiration, Simultaneous-Use, it is giving
1 error prohibited this raduser from being saved:
- Attribute name is not included in the list
If you want to change the message, you can add :message to the validations. You can add other validations like this in the Radcheck model.
See these links RailsCasts, Complex form codes

Related

Rails: Passing nested attribute parameters

So im working through the Odin Project's "Flight Booker" project. https://www.theodinproject.com/courses/ruby-on-rails/lessons/building-advanced-forms. Which essentially is what it sounds like and im running into a problem with passing nested attributes.
First and foremost the Relevant Models:
class Booking < ApplicationRecord
belongs_to :passenger
belongs_to :flight
accepts_nested_attributes_for :passenger
end
class Flight < ApplicationRecord
has_many :bookings, dependent: :destroy
has_many :passengers, through: :bookings
belongs_to :to_airport, class_name: 'Airport', foreign_key: 'origin_id'
belongs_to :from_airport, class_name: 'Airport', foreign_key: 'destination_id'
end
class Passenger < ApplicationRecord
has_many :bookings, dependent: :destroy
has_many :flights, through: :bookings
end
The passenger schema just contains an email and name for right now. But the problem is when I pass the information to the "booking" controller. Here is my "New" form for booking.
<%= form_for #booking do |f| %>
<%= f.hidden_field :flight_id, value: params[:booking][:flight_num] %>
<%= f.hidden_field :passengers_num, value: params[:booking][:passengers_num] %>
<% params[:booking][:passengers_num].to_i.times do |passenger| %>
<%= fields_for :passenger do |passenger| %>
<%= passenger.label :name, 'Name', class: "Label" %>
<%= passenger.text_field :name %>
<%= passenger.label :email, 'email', class: "Label" %>
<%= passenger.email_field :email %>
<% end %>
<% end %>
<%= f.submit "Book Flight" %>
<% end %>
(Ignore the hidden fields for now, they are passed from the "Flights" search page and Im getting those just fine.)
So I am getting the multiple forms (name and email fields) but when I "Submit" I am only getting parameters for the last field sets. (So if there are 3 sets of name/email fields, I only get parameters for the last one).
It's possible im not understanding the fields_for however as I can't find a ton of good examples.
Thanks!
There could be many issues with your implementation...I'll layout a few...
Move <% params[:booking][:passengers_num].to_i.times do |passenger| %> logic into the new action of your bookings controller...ie
def new
#booking = Booking.new
3.times { #booking.passengers.new } # or whatever your logic is to display x amount of passenger fields
end
Make sure that in your bookings controller you are permitting the nested attributes like this...
params.require(:booking).permit(passengers_attributes: [:name, :email])
As far as the form, you'll need to treat it like a form within a form (makes sense...nested attributes created from a nested form!) and use the block variable...like this
<ul>
<%= f.fields_for :passengers do |passenger_form| %>
<li>
<%= passenger_form.label :name
<%= passenger_form.text_field :name %>
</li>
<!-- other permitted fields -->
<% end %>
</ul>

Rails polymorphic association won't save _type

I have a few models in my project : Request, Work, Car and Employee. Work is an intermediate model between Request and Car/Employee.
Here are the associations:
Request
has_many :works, dependent: :destroy
def performers
works.map {|x| x.performer}
end
Work
belongs_to :request
belongs_to :performer, polymorphic: true
Car
has_many :works, as: :performer
has_many :requests, through: :works, as: :performer
Employee
has_many :works, as: :performer
has_many :requests, through: :works, as: :performer
View used to create works:
<%= form_for([#request, #work]) do |f| %>
<%= (f.collection_select :performer_id, Employee.all, :id, :name) if #request.type == "StaffRequest" %>
<%= (f.collection_select :performer_id, Car.all, :id, :select_info) if #request.type == "CarRequest" %>
<%= f.submit 'OK' %>
<% end %>
Work controller
def new
#work = #request.works.new
end
def create
#work = #request.works.new(work_params)
end
def work_params
params.require(:work).permit(:performer_id, :request_id)
end
The problem is that my performer_type column is always empty, it does not save the class name. What can be the problem? Any ideas?
It's empty because you did't pass it, you should add a hidden field for you form:
<%= form_for([#request, #work]) do |f| %>
<% if #request.type == "StaffRequest" %>
<%= (f.hidden_field :performer_type, value: "Employee") %>
<%= (f.collection_select :performer_id, Employee.all, :id, :name) %>
<% elsif #request.type == "CarRequest" %>
<%= (f.hidden_field :performer_type, value: "Car") %>
<%= (f.collection_select :performer_id, Car.all, :id, :select_info) %>
<% end %>
<%= f.submit 'OK' %>
<% end %>
Beside :performer_id, you have to pass the :performer_type also, one way to do this is via the form select_tag :
def create
#work = #request.works.new(work_params)
end
def work_params
# use :performer if you use :performer as in the select form field
params.require(:work).permit(:performer, :request_id)
# OR
# use :performer_id & :performer_type if you also use the hidden field
params.require(:work).permit(:performer_id, :performer_type, :request_id)
end
There is a good example (for Rails 4.2) of using a single select form field for polymorphic so you can write like:
<%= f.grouped_collection_select :global_performer, [ Car, Employee ], :all, :model_name, :to_global_id, :name %>
How to create grouped select box in Rails for polymorphic association using Global ID?

Nested checkboxes in Rails

I'm trying to create an event app where each event has multiple tables and each table has multiple people sitting at a table the event has multiple tickets which map the people to the tables that they are sitting at -> in order to achieve this I have created a checkbox nested in the fields_for :tables (which is in turn in the event form) I presume something is wrong with either the strong parameters or the form itself but I have not been able to find any information that provides a solution to the problem.After checking the checkboxes in the form indicating which people are going to be sitting at this table and submitting the form and returning to the form I find that the checkboxes are no longer checked???
here are the contents of my model files
# models
class Event < ActiveRecord::Base
has_many :tables, dependent: :destroy
has_many :people , through: :tickets
has_many :tickets
accepts_nested_attributes_for :tickets, allow_destroy: true
accepts_nested_attributes_for :tables, allow_destroy: true
end
class Table < ActiveRecord::Base
belongs_to :event
has_many :tickets
has_many :people, through: :tickets
end
class Ticket < ActiveRecord::Base
belongs_to :table
belongs_to :person
end
class Person < ActiveRecord::Base
has_many :tickets
has_many :tables, through: :tickets
end
Here is the form with parts omitted for brevity.
<%= form_for(#event) do |f| %>
...
<%= f.fields_for :tables do |builder| %>
<%= render 'table_field', f: builder %>
<% end %>
<%= link_to_add_fields "Add Table", f, :tables %>
...
<% end %>
And here is the checkbox list I have implemented within the table_field.
<% Person.all.each do |person| %>
<div class="field">
<%= check_box_tag "table[people_ids][]", person.id, f.object.people.include?(person) %> <%= f.label [person.first_name, person.last_name].join(" ") %>
</div>
<% end %>
this is the event_params
def event_params
params.require(:event).permit(:name, :description, :start, :end, :latitude, :longitude, :address, :data, :people_ids => [], tables_attributes: [:id, :number, :size, :people_ids => []]).tap do |whitelisted|
whitelisted[:data] = params[:event][:data]
end
How do I get the checkboxes to be persistently checked in this form?
You can use http://apidock.com/rails/v4.0.2/ActionView/Helpers/FormOptionsHelper/collection_check_boxes
<%= f.collection_check_boxes(:people_ids, Person.all, :id, :name) do |person| %>
<%= person.label { person.check_box } %>
<% end %>
It will persist data as well.

foreign key not updating

I'm developing an app for college where a user can log on & upload details of a hiking trail.
So far everything is working & I have also implemented a nested form for photos in each hiking trail. A user can log-on & create a hike.
I would like to display all the hikes which the user created in there show/profile page but when I've set up the relationships in my database & the has_many & belongs_to options in my model. I've also tried to do this with nested accepts_nested_attributes_for :hikingtrails it does none of this works.
I've checked my database when a hikingtrail is created by a user it is not updating the user_id field in the table.
I'm not sure if I'm approaching this entirely the wrong way, should I be looking at polymorphic associations?
class User < ActiveRecord::Base
attr_accessible :user_name, :email, :password, :password_confirmation, :photos_attributes, :hikingtrails_attributes
has_many :hikingtrails
accepts_nested_attributes_for :hikingtrails, :allow_destroy => :true, :reject_if => :all_blank
class Hikingtrail < ActiveRecord::Base
attr_accessible :description, :name, :looped, :photos_attributes,:directions_attributes, :user_id
has_many :photos
has_many :trails
has_many :directions
belongs_to :user
users/show.html.erb
<div class="page-header">
<h1>Your Profile</h1>
</div>
<p>
<b>username:</b>
<%= #user.user_name %>
</p>
<p>
<b>email:</b>
<%= #user.email %>
</p>
<h4>Small Photos</h4>
<% #user.photos.each do |photo| %>
<%= image_tag photo.image_url(:thumb).to_s %>
<% end %>
<h4>Hiking Trails</h4>
<% #user.hikingtrails.each do |hk| %>
<%= hk.name %>
<% end %>
<%= link_to "Edit your Profile", edit_user_path(current_user), :class => 'btn btn-mini' %>
You didn't add :user_id to your accessible attributes in the Hikingtrail model. Try the following:
attr_accessible :description,
:duration_hours,
:duration_mins,
:name,
:looped,
:addr_1,
:addr_2,
:addr_3,
:country,
:latitude,
:longitude,
:photos_attributes,
:trails_attributes,
:directions_attributes,
:user_id
UPDATE:
After seeing the form code, I think it's probably not necessary to do the above and could potentially also be unsafe. Instead, don't set the user_id through mass assignment, but handle user assignment in your controller like so:
class HikingtrailsController < ApplicationController
# ...
def create
#hikingtrail = Hikingtrail.new(params[:hikingtrail])
#hikingtrail.user = current_user
if #hikingtrail.save
# ...
else
# ...
end
end
end
Hope this helps :)

Cannot get simple_nested_form to submit data

I am trying to build a simple_nested_form in my Ruby on Rails app. When I submit my form I am getting some unknown error because it is just redirecting back to the form to input again. Here is the output in the rails server console for when I submit the form. It looks like there is some random "0" => thrown in there.
Parameters: {"machine"=>{"name"=>"2134", "ip_adress"=>"2", "machine_employees_attributes"=>{"0"=>{"machine_id"=>"1", "employee_id"=>"2"}}}, "commit"=>"Create Machine"}
I have a machine model which has_many :machine_employees
and a machineemployee model which belongs_to :machine
Do you have any idea why this 0 => could be appearing because I think it is what is giving me the issues.
Here is the code for my models.
Machine
class Machine < ActiveRecord::Base
# Relationships
has_many :machine_employees
has_many :employees, :through => :machine_employees
accepts_nested_attributes_for :machine_employees, :reject_if => lambda{ |me| me[:employee_id].blank? }
attr_accessible :ip_adress, :name, :machine_employees_attributes
# Validations
validates_presence_of :name, :ip_adress
end
MachineEmployee
class MachineEmployee < ActiveRecord::Base
before_validation :set_default
# Relationships
belongs_to :machine
belongs_to :employee
attr_accessible :employee_id, :machine_id, :end_date, :start_date
# Validations
validates_presence_of :employee_id, :machine_id, :start_date
private
# Callback Methods
def set_default
self.start_date = Date.today
self.end_date = nil
end
end
New Machine Form
<div class="row-fluid">
<div class="span3">
<h1>Add a Machine</h1>
<br />
<%= simple_nested_form_for #machine do |f| %>
<%= render "machine_fields", :f => f %>
<%= f.button :submit %>
<%= link_to 'Back', machines_path %>
</div>
<div class="span4">
<h4>Assign an Employee to This Machine</h4>
<%= f.simple_fields_for :machine_employees do |me_form| %>
<!-- render nested machine_employee fields-->
<%= render "machine_employee_fields", :f => me_form %>
<% end %>
</div>
<% end %>
</div>
Machine Employee Fields Partial
<%= f.input :machine_id, :as => :hidden, :input_html => { :value => #machine.id } %>
<%= f.input :employee_id, collection: #employees, :id => :name, :prompt => "Select ..." %>
The 0 is thrown in there because the machine model has_many machine_employees. When you use nested forms, it passes a pseudo-array for has_many relations. So, if you tried to submit 2 machine employees, your hash would look like this:
Parameters: {"machine"=>{"name"=>"2134", "ip_adress"=>"2", "machine_employees_attributes"=>{
"0"=>{"machine_id"=>"1", "employee_id"=>"2"},
"1"=>{"machine_id"=>"1", "employee_id"=>"3"}
}
}, "commit"=>"Create Machine"}
This way you can access the machine_employees passed from the form by doing params[:machine][:machine_employees_attributes][0] or params[:machine][:machine_employees_attributes][1]. Note that if this was a has_one relationship, then the machine_employees_attributes key would be changed to machine_employee_attributes and there would be no numerical index.
I suspect the problem is that your machine model must accept_nested_attributes_for :machine_employees and must also have attr_accessible :machine_employees_attributes.

Resources