so please imagine a form for a car reservation, the form contains both some client information(name, age, city..) and also the reservation informations(start_date, end_date...).
So obviously, i need to create in the same form a client first, based on the informations and then the reservation which is related to the created client:
class Client < ActiveRecord::Base
has_many :reservations
end
.
class Reservation < ActiveRecord::Base
belongs_to :client
belongs_to :voiture
end
Here is what i did until now(bad way).
The reservation informations start with f.text_field, and the client informations only with text_field(very bad way yes :( )
<%= form_for([#voiture, #voiture.reservations.new]) do |f| %>
<div class="row">
<div class="col-md-12 price_tag">
<span><%= #voiture.price %>Dhs</span>
<span class="pull-right">Par jour</span>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label>Nom</label>
<%= text_field :nom, readonly: 'true', placeholder: 'Nom', class: 'form-control' %>
</div>
<div class="col-md-6">
<label>Prenom</label>
<%= text_field :prenom, readonly: 'true', placeholder: 'Prenom', class: 'form-control', disabled: 'true' %>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label>CIN</label>
<%= text_field :cin, readonly: 'true', placeholder: 'CIN', class: 'form-control' %>
</div>
<div class="col-md-6">
<label>Age</label>
<%= text_field :age, readonly: 'true', placeholder: 'Age', class: 'form-control', disabled: 'true' %>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label>Ville</label>
<%= text_field :ville, readonly: 'true', placeholder: 'Ville', class: 'form-control' %>
</div>
<div class="col-md-6">
<label>Télephone</label>
<%= text_field :telephone, readonly: 'true', placeholder: 'Telephone', class: 'form-control', disabled: 'true' %>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label>Email</label>
<%= text_field :email, readonly: 'true', placeholder: 'Email', class: 'form-control' %>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label>Check In</label>
<%= f.text_field :start_date, readonly: 'true', placeholder: 'Start Date', class: 'form-control' %>
</div>
<div class="col-md-6">
<label>Check Out</label>
<%= f.text_field :end_date, readonly: 'true', placeholder: 'End Date', class: 'form-control', disabled: 'true' %>
</div>
</div>
<%= f.hidden_field :voiture_id, value: #voiture.id %>
<%= f.hidden_field :prix, value: #voiture.prix %>
<%= f.hidden_field :total, id: 'reservation_total' %>
<%= f.submit "Book Now", id:"btn_book", class: "btn btn-primary wide", disabled: 'true' %>
Controller :
class ReservationsController < ApplicationController
before_action :authenticate_user!
def create
#client = Client.create(client_params)
#reservation = #client.reservations.create(reservation_params)
redirect_to #reservation.voiture, notice: "Votre reservation a bien ete cree"
end
def reservation_params
params.require(:reservation).permit(:start_date, :end_date, :prix, :total, :voiture_id)
end
def client_params
params.permit(:nom, :prenom, :cin, :age, :ville, :telephone, :email)
end
end
I'm sure there is a good and clean way to do that..
Thanks !
I would look at nested attributes for a good clean solution, here is the doc link http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html
There is also a gem that can simplify it for you
https://github.com/ryanb/nested_form
Related
I have 2 models:
class Page < ApplicationRecord
enum page_type: STATIC_PAGE_TYPES, _suffix: true
has_one :seo_setting
accepts_nested_attributes_for :seo_setting, update_only: true
validates :title, :subtitle, length: { maximum: 50 }
validates :page_type, uniqueness: true
def to_param
"#{id}-#{page_type}".parameterize
end
end
and
class SeoSetting < ApplicationRecord
mount_uploader :og_image, SeoSettingsOgImageUploader
belongs_to :page
validates :seo_title, :seo_description, :og_title, :og_description, :og_image, presence: true
end
My Page objects are created from the seeds.rb file, and when I want to edit them, I get an error: Failed to save the new associated seo_setting.
In the form I have this:
<div class="card-body">
<%= form_for([:admin, #page]) do |f| %>
<%= render 'shared/admin/error-messages', object: #page %>
<div class="form-group">
<%= f.label :title, t('admin.shared.title') %>
<%= f.text_field :title, class: 'form-control' %>
</div>
<div class="form-group">
<%= f.label :subtitle, t('admin.shared.subtitle') %>
<%= f.text_field :subtitle, class: 'form-control' %>
</div>
<h3>SEO Settings</h3>
<%= f.fields_for :seo_setting, f.object.seo_setting ||= f.object.build_seo_setting do |form| %>
<div class="form-group">
<%= form.label :seo_title, t('admin.shared.seo_title') %>
<%= form.text_field :seo_title, class: 'form-control' %>
</div>
<div class="form-group">
<%= form.label :seo_description, t('admin.shared.seo_description') %>
<%= form.text_area :seo_description, class: 'form-control' %>
</div>
<div class="form-group">
<%= form.label :og_title, t('admin.shared.og_title') %>
<%= form.text_field :og_title, class: 'form-control' %>
</div>
<div class="form-group">
<%= form.label :og_description, t('admin.shared.og_description') %>
<%= form.text_area :og_description, class: 'form-control' %>
</div>
<div class="form-group">
<%= form.label :og_image, t('admin.shared.og_image') %>
<div class="row">
<div class="col-lg-12">
<%= image_tag(form.object.og_image.url, style: 'width: 100px') if form.object.og_image? %>
</div>
</div>
<%= form.file_field :og_image %>
<%= form.hidden_field :og_image_cache %>
</div>
<% end %>
<div class="form-group">
<%= f.submit t('admin.actions.submit'), class: 'btn btn-success' %>
<%= link_to t('admin.actions.cancel'), admin_page_path(#page) , class: 'btn btn-default' %>
</div>
<% end %>
</div>
If I remove validations from my SeoSetting model, everything is working. It seems Rails doesn't like this part: f.object.build_seo_setting, because it creates a record in my database. Any ideas of how can I solve this issue? Thanks ahead.
Just had to change this line:
<%= f.fields_for :seo_setting, f.object.seo_setting ||= f.object.build_seo_setting do |form| %>
for this one:
<%= f.fields_for :seo_setting, #page.seo_setting.nil? ? #page.build_seo_setting : #page.seo_setting do |form| %>
Looks as if the problem lies here:
accepts_nested_attributes_for :seo_setting, update_only: true
in that you only allow the updating of the seo_setting on update.
Then, when you use this code:
f.object.seo_setting ||= f.object.build_seo_setting
You're falling back to a new seo_setting if the associated object is missing.
For this to work, you'll need to either remove the update_only: true, or only render the fields for the association if the seo_setting already exists.
Accepted answer, a conditional inside fields_for line, by OP, works also with polymorphic association and nested form (fields_for). Thnx Alex.
My profile role is created and when the user logs in the profile controller's edit action view will be displayed. I want to show the role that is assigned to user in the edit action and the user cannot change the role. My edit.html.erb file is given as:
<div class="row">
<div class="col-md-6 offset-md-3">
<h3>Profile</h3>
<%= form_for(#profile) do |f| %>
<div class="form-group">
<%= f.label :first_name %><br />
<%= f.email_field :first_name, autofocus: true, class: "form-control"%>
</div>
<div class="form-group">
<%= f.label :last_name %><br />
<%= f.password_field :last_name, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :role %><br />
<%= f.select(:role, [['User', 'user'], ['Vip', 'vip'], ['Admin', 'admin']]) %>
</div>
<div class="actions form-group">
<%= f.submit "Submit", class: 'btn btn-primary' %>
</div>
<% end %>
</div>
The simplest possible way is just to use String#humanize from ActiveSupport.
Capitalizes the first word, turns underscores into spaces, and (by
default)strips a trailing '_id' if present. Like titleize, this is
meant for creating pretty output.
irb(main):008:0> roles.roles.keys.map(&:humanize)
=> ["User", "Vip", "Admin"]
irb(main):009:0> Profile.new(role: :admin).role.humanize
=> "Admin"
Profile.roles gives us the hash mapping for the Enum.
You can use this to generate the select tag with:
<%= form.select :role, Profile.roles.keys.map{|k| [k.humanize, k] } %>
You can get "vip".humanize to return "VIP" by setting up an inflection:
# config/initializers/inflections.rb
ActiveSupport::Inflector.inflections(:en) do |inflect|
inflect.acronym 'VIP'
end
This may require restarting your Rails server before it kicks in.
Using the I18n module
But if you want a more flexible solution that lets you set the mappings yourself (and works with translations) use the I18n module:
# config/locales/en.yml
en:
activerecord:
attributes:
profile:
roles:
user: 'User'
vip: 'Very Important Person'
admin: 'Admin'
# app/helpers/users_helper.rb
module UsersHelper
def translate_role(role)
I18n.t("activerecord.attributes.user.roles.#{ role }", default: role.humanize)
end
def role_options
Profile.roles.keys.map{|k| [translate_role(k), k] }
end
end
You would then display the users role by:
<%= translate_role(#user.role) %>
And you can setup the form input as:
<%= form.select :role, role_options %>
You can use a helper method, For example in the application_helper.rb you can make a method like this:
class ApplicationHelper
def profile_role(profile)
# Perform your logic
# if you are using as_enum gem for example
Profile.roles.select{|symbol_role,integer_rep| integer_rep == profile.role}.keys.first.to_s
# of course there could be much better way to get the string.
end
end
Then in your view, If user should not be able to change it, there is no need to make it as drop-down list:
<div class="row">
<div class="col-md-6 offset-md-3">
<h3>Profile</h3>
<%= form_for(#profile) do |f| %>
<div class="form-group">
<%= f.label :first_name %><br />
<%= f.email_field :first_name, autofocus: true, class: "form-control"%>
</div>
<div class="form-group">
<%= f.label :last_name %><br />
<%= f.password_field :last_name, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :role %><br />
<%= text_field_tag 'role', profile_role(#profile), disabled: true %>
</div>
<div class="actions form-group">
<%= f.submit "Submit", class: 'btn btn-primary' %>
</div>
<% end %>
You can just show it as regular text, e.g.:
...
<div class="form-group">
<%= f.label :last_name %><br />
<%= f.password_field :last_name, class: "form-control" %>
</div>
<p>
Role:<br />
<%= #profile.role %>
</div>
<div class="actions form-group">
<%= f.submit "Submit", class: 'btn btn-primary' %>
</div>
...
I have a sign-up form with some nested fields, and inside this form I have added a checkbox for the terms of service.
I'm trying to validate when the checkbox is checked or not and return an error if not.
validates_acceptance_of :agreement, :allow_nil => true, :accept => true, :on => :create
I do have a boolean column that is set to default inside the accounts table.
add_column :accounts, :agreement, :boolean, default: false
Also, I have added the :agreement to the permitted controller params.
This is the form:
<%= form_for(resource, as: resource_name, url: registration_path(resource_name)) do |f| %>
<div class="centerList">
<div id="error_explanation">
<%= devise_error_messages! %></div>
</div>
<div class="form-group">
<span class="asterisk_input"></span>
<%= f.email_field :email, autofocus: true, placeholder: "email", :class=>"form-control" %>
</div>
<div class="form-group">
<span class="asterisk_input"></span>
<%= f.password_field :password, placeholder: "password", autocomplete: "off", :class=>"form-control" %>
</div>
<div class="form-group">
<span class="asterisk_input"></span>
<%= f.password_field :password_confirmation, placeholder: "confirm password", autocomplete: "off", :class=>"form-control" %>
</div>
<%= f.fields_for :account do |form| %>
<div class="form-group">
<span class="asterisk_input"></span>
<%= form.text_field :street, placeholder: "street", :class=>"form-control" %>
</div>
<div class="form-group">
<span class="asterisk_input"></span>
<%= form.text_field :city, placeholder: "city", :class=>"form-control" %>
</div>
<div class="form-group">
<span class="asterisk_input"></span>
<%= form.text_field :state, placeholder: "state", :class=>"form-control" %>
</div>
<div class="form-group">
<span class="asterisk_input"></span>
<%= form.text_field :zip_code, placeholder: "zip code", :class=>"form-control" %>
</div>
<div class="form-group">
<span class="asterisk_input"></span>
<%= form.country_select :country, ["US"], {}, { :class => "form-control", :id=>"sign-frm-input" } %>
</div>
<div class="form-group">
<%= form.check_box :agreement %> I agree to the <%= link_to 'Terms', term_path(:id=>1) %>.
</div>
<% end %>
<div class="form-group">
<%= f.submit "Sign up", :class=>'btn btn-primary' %>
<% end %>
But it returns the form error agreement must be accepted even if I do accept it. Any ideas what I might be missing ohere!
Try: validates :agreement, acceptance: {accept: true} , on: :create,
allow_nil: false
You don't want to :allow_nil => true if you're validating for acceptance.
You can change your model validations and test it.
validates :agreement, inclusion: {in: [true]}
or you can use other validation
validates :agreement, acceptance: { accept: true }
I'm trying to create an event, the event has a date and a time field. When creating the table for protest I didn't add a time field, so I had to run a migration to add a time field. But I am now having issues when creating the form. What is the elegant solution to create a time and date field?
class CreateProtests < ActiveRecord::Migration[5.1]
def change
create_table :protests do |t|
t.string :name
t.text :description
t.string :location
t.datetime :starts_at
t.references :creator, index: true, foreign_key: { to_table: :users }
end
end
end
I then added a field for the time of day:
class AddStartsAtTimeOfDayToProtests < ActiveRecord::Migration[5.1]
def change
add_column :protests, :starts_at_time_of_day, :datetime
end
end
protests/new.html.erb
<div class="col-md-12">
<div class="card">
<div class="card-header"><h2>Create A Protest</h2></div>
<div class="card-block">
<%= form_for(#protest, url: protests_path, html: {multipart: true}) do |f| %>
<div class="form-group">
<%= f.label :name %>
<%= f.text_field :name, autofocus: true, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :description %>
<%= f.text_area :description, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label :location %>
<%= f.text_field :location, class: "form-control" %>
</div>
<div class="form-group">
<%= f.label "Date:" %>
<%= f.text_field :starts_at, value: "02/16/13", id: "dp2"%>
</div>
<div class="form-group">
<%= f.label "Time:" %>
<%= f.time_select :starts_at, ignore_date: true, class: "form-control"%>
</div>
<div class="form-group">
<%= f.label :image %>
<%= f.file_field :image, as: :file, class: "form-control" %>
</div>
<div class="form-group">
<%= f.submit "Create", class: "btn btn-primary" %>
</div>
<% end %>
</div>
</div>
</div>
The Date and Time are both captured in the single column :starts_at so you don't need another column for the time of day.
Your form should just have:
<div class="form-group">
<%= f.label "Starts At:" %>
<%= f.datetime_select :starts_at, class: "form-control"%>
</div>
Here is a link to the docs:
https://apidock.com/rails/ActionView/Helpers/FormBuilder/datetime_select
add date_field for Date and time_field for the time to have the two fields as follows.
<div class="form-group">
<%= f.label "Date:" %>
<%= f.date_field :starts_at, class: "form-control"%>
</div>
<div class="form-group">
<%= f.label "Time:" %>
<%= f.time_field :starts_at, class: "form-control"%>
</div>
Forced to ask for help with saving nested models on polymorphic association. I'm missing something but can't figure out what.
Everithing pretty straightforward. There is Address which can have multiple Phones.
So models are
class Address < ActiveRecord::Base
has_many :phones, as: :phoneable
accepts_nested_attributes_for :phones, allow_destroy: true
validates :city, :street, :building, :name, presence: true
end
and
class Phone < ActiveRecord::Base
belongs_to :phoneable, polymorphic: true
validates :number, :extension, presence: true
end
addresses_controller.rb
def new
#address = Address.new
#phone = #address.phones.build
authorize #address
end
def create
#address = Address.create(address_params)
authorize #address
if #address.save
binding.pry
flash[:success] = "Address #{#address.name} created"
redirect_to address_path(#address)
else
flash.now[:danger] = 'Failed'
render :new
end
end
def address_params
params.require(:address).permit(:name, :street, :building, :city, phones_attributes: [:id, :number, :extension, :details] )
end
/app/views/address.html.erb
<div class="row">
<div class="col-md-12">
<%= form_for(#address, html: {class: 'form-horizontal', role: 'form'}) do |f| %>
<%= render 'shared/errors', obj: #address, model_name: 'addresses' %>
<div id="create-form">
<div class="form-group">
<div class="control-label col-md-4">
<%= f.label :city, 'Город' %>
</div>
<div class="col-md-4">
<%= f.select(:city, options_for_select(['Moscow', 'Samara']), {}, {class: "form-control"}) %>
</div>
</div>
<div class="form-group">
<div class="control-label col-md-4">
<%= f.label :street, 'Street' %>
</div>
<div class="col-md-4">
<%= f.text_field :street, class: 'form-control' %>
</div>
</div>
<div class="form-group">
<div class="control-label col-md-4">
<%= f.label :building, 'Building' %>
</div>
<div class="col-md-4">
<%= f.text_field :building, class: 'form-control' %>
</div>
</div>
<div class="form-group">
<div class="control-label col-md-4">
<%= f.label :name, 'Place name' %>
</div>
<div class="col-md-4">
<%= f.text_field :name, class: 'form-control' %>
</div>
</div>
<div class="form-group">
<div class="control-label col-md-4">
<%= f.label :phones, 'Phone' %>
</div>
<div class="col-md-4">
<%= f.fields_for :phone do |phone_form| %>
<%= phone_form.text_field :number, class: 'form-control' %>
</div>
</div>
<div class="form-group">
<div class="control-label col-md-4">
<%= phone_form.label :extension, 'Ext.' %>
</div>
<div class="col-md-4">
<%= phone_form.text_field :extension, class: 'form-control' %>
</div>
<% end %>
</div>
<div class="form-group">
<div class='col-md-offset-4 col-md-6'>
<%= f.submit #address.new_record? ? 'Add' : 'Update', class: 'btn btn-primary btn-md' %>
</div>
</div>
</div>
<% end %>
</div>
</div>
First issue I encountered with is if I set key :phones instead :phone into the following line <%= f.fields_for :phone do |phone_form| %> my phone text fields don't render in view but they should. One user emphasized this moment here https://stackoverflow.com/a/3328041/2049476
if I use :phone everything somehow works fine but seems like it's wrong.
And the second one.
Phone object doesn't save in DB, when I create new address or edit current I succeed but phone doesn't show any validation errors if I leave all it fields blank.
Here what I have in params hash
{"utf8"=>"✓",
"authenticity_token"=>"inwXr3Ev/Aj/hZRY2IadizDHDgdSFo2zFhY9DAvysfFu3jjD9AS66esKVsTzEuKo2WC46YQt6HnOKTgInvfUEg==",
"address"=>{"city"=>"Moscow", "street"=>"ul. Tsentralnaya d. 4 kv. 220", "building"=>"1212", "name"=>"Astoria", "phone"=>{"number"=>"9215555555", "extension"=>"111"}},
"commit"=>"Add",
"controller"=>"addresses",
"action"=>"create"}
What am I missing?
Try answer for 2 issues:
The correct way is to pass :phones, and then phone as variable to field_for, like is was done here:
<%- #address.phones.each.with_index do |phone, index| %>
<%- f.fields_for :phones, phone do |phone_form| %>
<%- end %>
<%- end %>
Should be resolved as of the 1-st question, since the fields shoudl sent ot server via params not a phone hash, but phones_attributes array of hashes, in order to accepts_nested_attributes_for could accept phones:
phones_attributes: [{ ... },{ ... }]