I have 3 text_fields in my view in which I enter students name. Of course you can enter one student or three students but I want to make sure that at least one student was provided because a project must have a student assigned to it.
Here is my view:
<%= form_for #project, url: projects_path do |f| %>
<p>
<%= f.label :name, "Name" %>
<%= f.text_field :name %>
</p>
<p>
<%= f.fields_for :students do |s| %>
<%= s.label :name %>
<%= s.text_field :name %>
<% end %>
</p>
<p>
<%= f.submit "Submit" %>
</p>
<% end %>
And new method from Projects controller:
def new
#project = Project.new()
3.times do
student = #project.students.build
end
end
What I want to achieve is to check if at least one student was provided and if not just show alert or disable submiting.
Edit
Models used in this project:
class Student < ActiveRecord::Base
belongs_to :project
end
class Project < ActiveRecord::Base
has_many :students
accepts_nested_attributes_for :students
validate :validate_student_count
def validate_student_count
errors.add(:students, "at least one is required") if students.count < 1
end
end
Lots of very similar questions on the internet. Here's some examples: Validate the number of has_many items in Ruby on Rails and Validate that an object has one or more associated objects
Just add a custom validation rule as:
validate :validate_student_count
def validate_student_count
errors.add(:students, "at least one is required") if students.count < 1
end
Related
I have 2 models (and resources) - Institute and Admin.
I want to have a view with 1 submit button that creates 2 types of resources. Would I need to have 2 separate forms? An example would be great!
Also, what naming convention should this view use (given that it creates 2 types resources).
There is a "has-many through" association between Institute and Admin.
What you want is a design pattern called Form Object.
https://robots.thoughtbot.com/activemodel-form-objects
With a Form Object, you can create a class that represents the form, validate the data and then persist to the resource (or resources) that you need.
There's also a gem called Virtus for that. For me, it's a overkill if what you want is simple. You could just create a ActiveModel model and do your stuff.
Would I need to have 2 separate forms?
Answer is Non. you can make one form nested.
So example : Gessing your "has many through" association like this: One institue has many admins throuth mettings
Models :
class Institute < ActiveRecord::Base
has_many :mettings
has_many :admins, :through => :mettings
accepts_nested_attributes_for :mettings
end
class Admin < ActiveRecord::Base
has_many :mettings
has_many :institues, :through => :mettings
accepts_nested_attributes_for :mettings
end
class Metting < ActiveRecord::Base
belongs_to :institue
belongs_to :admin
accepts_nested_attributes_for :institues
end
Controller :
def new
#institue= Institue.new
#metting= #institue.mettings.build
#admin = #metting.build_admin
end
def create
Institue.new(institue_params)
end
def institue_params
params.require(:institue).permit(:id, mettings_attributes: [:id, :metting_time, admin_attributes: [:id ] )
end
Views can be called _form.erb.rb included in edit.erb.rb:
<% form_for(#institue) do |institue_form| %>
<%= institue_form.error_messages %>
<p>
<%= institue_form.label :name, "Institue Name" %>
<%= institue_form.text_field :name %>
</p>
<% institue_form.fields_for :mettings do |metting_form| %>
<p>
<%= metting_form.label :metting_date, "Metting Date" %>
<%= metting_form.date_field :metting_date %>
</p>
<% metting_form.fields_for :admin do |admin_form| %>
<p>
<%= admin_form.label :name, "Admin Name" %>
<%= admin_form.text_field :name %>
</p>
<% end %>
<% end %>
<p>
<%= institue_form.submit 'Create' %>
</p>
<% end %>
<%= link_to 'Back', institues_path %>
Is there a way I can avoid the hidden_field method of passing values in the view to a controller? I would prefer a controller method for security reasons. Unfortunately value pairing #variables is not supported in strong_parameters.
EDIT 6/18 1:00 PM EST
I've renamed my garages controller to appointments
cars_controller no longer creates a new appointment (formally garages). A new appointment is created in the
appointments_controller
My current structure
routes
Rails.application.routes.draw do
resources :techs, only: [:index, :show], shallow: true do
resources :cars, only: [:new, :create]
end
resources :appointments
#For searchkick
resources :cars, only: [:show] do
collection do
get 'search'
end
end
root "home#index"
end
models
tech.rb
class Tech < ActiveRecord::Base
searchkick
has_many :appointments
has_many :customers, :through => :appointments
has_many :service_menus
has_many :services
has_many :cars
end
service.rb
class Service < ActiveRecord::Base
belongs_to :tech
belongs_to :service_menu
has_many :cars, dependent: :destroy
accepts_nested_attributes_for :cars, :reject_if => :all_blank, :allow_destroy => true
end
car.rb
class Car < ActiveRecord::Base
belongs_to :service
belongs_to :tech
has_many :appointments
end
appointment.rb
class Garage < ActiveRecord::Base
belongs_to :customer
belongs_to :tech
belongs_to :car
end
controllers
cars_controller
def new
#car = Car.find(params[:id])
#tech = Tech.find(params[:tech_id])
#appointment = Garage.new
end
appointments_controller
def create
#appointment = current_customer.appointments.build(appointment_params)
if #appointment.save
redirect_to appointments_path, notice: "You car has been added to this appointment."
else
redirect_to appointments_path, notice: "Uh oh, an error has occured."
end
end
private
def appointment_params
params.require(:appointment).permit(:tech_id, :service_id, :car_id, ...and a bunch of other keys here)
end
views
cars.new.html
Please note this form passes hidden values to the appointment_controller.
Value from #car.name and other alike are not from a text_field but rather a pre-defined value based on selections from a previous page which is store in the cars db.
<%= simple_form_for(#appointment, { class: 'form-horizontal' }) do |f| %>
<%= f.hidden_field :tech_id, value: #tech.id %>
<%= f.hidden_field :car_id, value: #car.id %>
<%= f.hidden_field :service_id, value: #car.service.id %>
<%= f.hidden_field :customer_car, value: current_customer.car %>
<%= f.hidden_field :customer_street_address, value: current_customer.street_address %>
<%= f.hidden_field :customer_city, value: current_customer.city %>
<%= f.hidden_field :customer_state, value: current_customer.state %>
<%= f.hidden_field :customer_zip_code, value: current_customer.zip_code %>
<%= f.hidden_field :service_name, value: #car.service.service_menu.name %>
<%= f.hidden_field :car_name, value: #car.name %>
<%= **And a bunch of other hidden values here which are too long to list** %>
<%= f.submit "Add to appointment", class: 'btn btn-default' %>
<% end %>
service.html
<%= render 'form' %>
_form.html
<%= simple_form_for #service do |f| %>
<div class="field">
<%= f.label "Select service category" %>
<br>
<%= collection_select(:service, :service_menu_id, ServiceMenu.all, :id, :name, {:prompt => true }) %>
<%= f.fields_for :cars do |task| %>
<%= render 'car_fields', :f => task %>
<% end %>
</div>
<div class="links">
<%= link_to_add_association 'Add New Car', f, :cars, class: 'btn btn-default' %>
</div><br>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
_car_fields.html
<div class="nested-fields">
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %><br>
<%= f.label :hours %>
<%= f.select :hours, '0'..'8' %>
<%= f.label :minutes %>
<%= f.select :minutes, options_for_select( (0..45).step(15), selected: f.object.minutes) %><br>
<%= f.label :price %><br>
<%= f.text_field :price, :value => (number_with_precision(f.object.price, :precision => 2) || 0) %> <br>
<%= f.label :details %><br>
<%= f.text_area :details %></div>
<%= link_to_remove_association "Remove Car", f, class: 'btn btn-default' %>
<%= f.hidden_field :tech_id, value: current_tech.id %>
<br>
<hr>
</div>
> Edit 7/14 1:30 pm EST
Brief Synopsis on this specific function of the application
A customer clicks through a list of services a tech has to offer
The customer selects a service for example brakes which is a service a tech has listed in his profile.
The attributes for brakes are listed in the cars db
cars belongs_to to techs
The customer can save brakes which is an attribribute of a techs car to a appointment
A good number of predefined values from tech, the customer's street address, etc..., and the car are pre-loaded in the form for storing in the appointments table.
appointment acts as a histories table. So if the tech decides to modify any one of his services in this example brakes, the appointments tables will remain untouched for the brakes entry.
Once the customer selects the Add to appointment button, it will save all of the predefined values from tech, customer, and car attributes (in this example brakes) to the appointments db.
Another approach to this would be to get rid of the strong parameters altogether and do the following:
def create
#appointment = Garage.create(tech_id: #car.service.tech.id,
customer_id: current_customer.id,
customer_street_address: current_customer.street_address,
customer_city: current_customer.city,
customer_state: current_customer.state,
customer_zip_code: current_customer.zip_code,
customer_phone_number: current_customer.phone_number,
customer_location_type: "WILL ADD LATER",
customer_latitude: current_customer.latitude,
customer_longitude: current_customer.longitude,
service_id: #car.service.id,
service_name: #car.service.name,
car_id: #car.id,
car_name: #car.name,
car_time_duration: #car.time_duration,
price: #car.price,
car_details: #car.details)
if #appointment.save
redirect_to techs_path, notice: "This service has been saved."
elsif
redirect_to tech_path, notice: "Uh oh, an error has occurred."
end
end
Please let me know if you require further details.
I can think of some methods you could use to avoid this form bloated with hidden_fields:
Share data between controllers in the user's session, pretty much like a shopping cart in an e-commerce application.
If you prefer to preserve the statelessness of the application, create a model to temporarily store these informations; this way you'll only need to include one hidden_field in the form.
Use JavaScript to make the requests, storing the data in local objects and passing them as JSON when needed (this is trivial using AngularJS).
Whichever method you choose, keep in mind that storing a lot of state in a web application usually is a code smell. You can always rethink your application so you don't need to keep so much context.
To resolve my issue, my latest edit from my initial post stated the following:
EDIT 6/18 1:00 PM EST
I've renamed my garages_controller to appointments_controller
cars_controller no longer creates a new appointment (formally garages). A new appointment is created in the appointments_controller
Only hidden_field i'm passing is the car_id in the appointments view /new.html.erb <%= f.hidden_field :car_id, value: #car.id %>.
In the appointments_controller, I'm assigning all the car attributes doing the following.
def create
#appointment = current_customer.appointments.build(appointment_params)
#appointment.tech_id = #appointment.car.service.tech.id
#appointment.price = #appointment.car.price
#appointment.car_name = #appointment.car.name
#appointment.car_details = #appointment.car.details
if #appointment.save
redirect_to appointments_path, notice: "Thank you booking your appointment."
else
redirect_to appointments_path, notice: "Uh oh, an error has occurred. Please try again or contact us for further assistance"
end
end
Thank you all for your responses.
I should've known better. :(
You could move that stuff into a callback and only pass the customer_id and car_id with the form. This way garage instance will know about it's customer and car parents and you can do something like:
class Garage < ActiveRecord::Base
before_create :copy_stuff
private
def copy_stuff
self.customer_street_address = customer.street_address
self.car_name = car.name
# and so on
end
end
Is there a way I can avoid the hidden_field method of passing values
in the view to a controller?
You can disable those fields in the HTML/view by adding attribute disabled: true to the hidden input field tags to achieve what you asked for.
Not sure about the syntax exactly, but should be something like this for example
f.hidden_field :tech_id, value: #tech.id, disabled: true
I'm trying to write a nested form in ROR.
I have two tables Employee and EmployeeInfo and both table have a column named employeeID
these tables are connected with this key.
What i want to do is to create a form with some input fields which should update the values into both tables.
for eg i want a form which can create or update fields named employee_name, age, address and city But employee_name and age are present in table Employee and city and address are present in table EmployeeInfo.
So how should i write the form tag inorder to do this.
Please be sorry if question is a blunder. I'm realy new to this. Pls help
Extending #emu's answer
Models setup
#employee.rb
Class Employee < ActiveRecord::Base
has_one :employe_info
accepts_nested_attributes_for :employee_info
end
#employee_info.rb
Class EmployeeInfo < ActiveRecord::Base
belongs_to :employee
end
Controller
Class EmployeesController < ApplicationController
def new
#employee = Employee.new
#employee.build_employee_info
end
def create
#employee = Employee.new(employee_params)
if #employee.save
redirect_to #employee
else
render 'new'
end
end
private
def employee_params
params.require(:employee).permit(:employee_name, :age, employee_info_attributes: [:id, :city,:address])
end
end
In rails 4 you need to use
accepts_nested_attributes_for :employeeinfo
in your employee model. And also employee has the relation with emplyeeinfo is has_one.
in the form:
<%= form_for #employe, :html => { :multipart => true } do |f| %>
<% if #employe.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#employe.errors.count, "error") %> prohibited this employe from being saved:</h2>
<ul>
<% #employe.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :employee_name %><br>
<%= f.text_field :employee_name %>
</div>
<div class="field">
<%= f.label :age %><br>
<%= f.text_field :age %>
</div>
<%= f.fields_for :employeeinfo do |s| %>
<%= s.label :address %><br>
<%= s.text_field :address %>
<% end %>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
Please edit the form objects according to your model name.
Foriegn Key
Firstly the "connector" key you're referring to is called a foreign_key.
This is a standard relational database feature, allowing you to "join" two or more databases together with a single reference point. Whenever you use associations in Rails, you'll basically have to join the two datatables with a foreign_key
both table have a column named employeeID
Your employee_infos table only needs to have the foreign_key employee_id:
#app/models/employee.rb
Class Employee < ActiveRecord::Base
has_one :employee_info #-> foreign key = "employee_id"
accepts_nested_attributes_for :employee_info
end
#app/models/employee_info.rb
Class EmployeeInfo < ActiveRecord::Base
belongs_to :employee
end
Forms
As mentioned by Emu, you'll need to use accepts_nested_attributes_for
This allows you to pass data from a "parent" model to a child model, exactly what you have set up currently. The difference being you have to ensure you have it set up correctly.
Regardless of what you think, this is how you need to do it. You mention yourself that you're very new to Rails; which means your objections are likely based on your current DB setup. This might not be correct
You should use emu & Pavan's answers to fix this :)
I am having a problem with creating an object with an association.
I have a Message model that belongs_to a job, and a user or runner. Inside my jobs/index.html I want to show a list of jobs with their corresponding messages and a form to create a new message for that particular job.
The problem is whenever I create a message, job_id stays nil. I am new to ruby on rails, so I still dont fully understand this stuff.
Here is part of my jobs/index.html (NOTE: not my actual code, I am in class so I just typed this up, may contain syntax errors).
<% #jobs.each do |job| %>
<p> <%= job.body %> </p>
<%= form_for job.messages do |f| %>
<%= f.label :body %>
<%= f.text_field :body %>
<%= f.submit %>
<% end %>
<%if job.messages.present? %>
<ul>
<% job.messages.each do |message| %>
<li>
<p> message.description <p>
</li>
<% end %>
</ul>
<% else %>
<p> No messages <p>
<% end %>
<% end %>
Here is the create method in message controller (NOTE: current_login can be a runner or user, they both share the same attributes)
def create
#message = current_login.messages.new(params[:message])
#message.save
end
Job controller index action
def index
#jobs = Job.all
end
Message model
class Message < ActiveRecord::Base
attr_accessible :description
belongs_to :user
belongs_to :runner
belongs_to :job
end
User model
class User < ActiveRecord::Base
attr_accessible :username
has_many :jobs
end
Runner model
class Runner < ActiveRecord::Base
attr_accessible :username
has_many :jobs
end
Job model
class Job < ActiveRecord::Base
attr_accessible :body
has_many :messages
belongs_to :user
belongs_to :runner
end
Whenever I submit the message form inside the jobs/index.html view, it seems to successfully create a message with user_id or runner_id successfully filled out (depending on who posted the message), but I am getting nil for the job_id attribute.
Since your message belongs to job, i think you should be creating the nested resources within the jobs form.
Your new controller function inside the jobs model should build the association like so:
def new
#job = Job.new(params[:job])
#message = #job.build_message
end
your create model just needs to save the parent model:
def create
#job = Job.create(params[:job])
end
For lots of detailed information on how to do this, watch this railscast: http://railscasts.com/episodes/196-nested-model-form-part-1
I should also add, if you are simply trying to add a message to an existing job, just pass the parameter for the job_id correctly in your form, AND make sure the job you're referencing actually exists.
To solve this problem, I decided to manually create the tie between the message and the job it belongs to through a hidden field in the form.
<%= form_for(#message) do |f| %>
<%= f.label :body, "Description" %>
<%= f.text_area :body %>
<%= f.hidden_field :job_id, value: job.id %>
<%= f.submit 'Create message', class: 'button small secondary' %>
<% end %>
I have three models:
class Rate < ActiveRecord::Base
attr_accessible :user_id, :car_id, :rate
belongs_to :user
belongs_to :car
end
class User < ActiveRecord::Base
attr_accessible :name
has_many :rates
accepts_nested_attributes_for :rates
end
class Car < ActiveRecord::Base
attr_accessible :name
has_many :rates
accepts_nested_attributes_for :rates
end
And one controller:
class UsersController < ResourceController
def new
# Assume user is loaded
#user.rates.build
end
end
I'm trying to build a nested form that will associate a list of users/cars and their associated rates.
Something like:
<% form_for #user do |f| %>
<%= #user.name %><br />
<% Car.all.each do |car| %>
<%= car.name %><br />
<%= f.fields_for :rates do |r| %>
<%= r.number_field :rate %>
<% end %>
<% end %>
<% end %>
The problem is that I would like the Rate model to store data as follows:
USER_ID CAR_ID RATE
1 1 10
1 2 20
1 3 30
2 1 40
3 2 50
I cannot figure out how to properly build the fields_for helper to build the proper params for both the user_id and the car_id.
Something like:
user[car=1][rate]
user[car=2][rate]
I've tried being more explicit with the fields_for like this:
<%= r.fields_for 'user[car][rate]' %>
But it still doesn't build out the nested parameters properly. The car parameter is not correctly identified.
Any help would be appreciated! Thanks.
EDIT:
The controller action has to be under user. The example above has been shortened for brevity but other user-related attributes are available through the form so it has to use the users controller.
ANSWER:
I figured out a way to do it. I've added my own answer that explains it.
<% form_for #user do |f| %>
<%= #user.name %><br />
<%= f.fields_for :rates do |r| %>
<% Car.all.each do |car| %>
<%= car.name %><br />
<%= r.number_field :rate %>
<% end %>
<% end %>
<% end %>
This may be solution of your problem. Just check it.
The form is going to create a new rate instead of a new user, so the method should be in RatesController instead of UsersController.
With this logic the problem seems solved. You can write field_for rate[user] and field_for rate[car]
I think I've got it figured out.
In my controller, I've modified the build method as follows:
Car.all.each { |c| #user.rates.build(car_id: c.id) } if #user.rates.count == 0
Then, in my model, I need the following:
attr_accessible :rates_attributes
Finally, the fields_for block should look like this (remember, this is in the #user form object f):
<%= f.fields_for :rates do |r| %>
<%= r.hidden_field :car_id %>
<%= r.object.car.name %><br />
<%= r.number_field :rate %>
<% end %>
This builds the params hash properly and create the rate model entries when the form is submitted.
The check on existing user rates in the controller will ensure that the existing values are used in the form and new ones are not built (which I thought build took into consideration... ?).