Rails 4.2: Client Side Validations for Associations - ruby-on-rails

I have a complex form with associations that I would like to view validation errors inline in real-time. I stumbled upon RailsCasts episode Client Side Validations and decided to take that approach and installed the gem.
gem 'client_side_validations',
github: "DavyJonesLocker/client_side_validations",
branch: "4-2-stable"
As required by the gem, I placed validate: true at the top of the form helper and saw that it only worked on attribute fields and not on the associated objects.
For example, when the field name is blank, it displays the error inline stating can't be blank. When the user has not selected a program, I would like it to inform the user Need a program! but it does not. Currently, it simply throws the flash[:error] message I stated in the controller that "There was a problem launching your Campaign." with no inline message specific to the program select box.
Here is the set-up..
views/campaigns/_forms.html.erb
<%= form_for #campaign, url: {action: "create"}, validate: true do |f| %>
<%= f.label :campaign_name, "Campaign Name", class: "right-label" %>
<%= f.text_field :name %>
<%= f.label :comments %>
<%= f.text_area :comment %>
<%= f.label :program %>
<%= f.select :program_id,
Program.all.collect { |p| [ p.name, p.id ] }, { include_blank: true }, validate: { presence: true } %>
<%= f.label :data_file, "Data File (.zip)", class: "right-label" %>
<%= select_tag :uploadzip_id,
options_from_collection_for_select(
Uploadzip.all, :id, :file_name
), { include_blank: "Include a Zip File" } %>
<%= f.label :additional_files %>
<ul><%= f.collection_check_boxes :uploadpdf_ids, Uploadpdf.last(5).reverse, :id,
:file_name, { mulitple: true } do |b| %>
<li><%= b.label do %>
<%= b.check_box + truncate(File.basename(b.text,'.*'), length: 45) %>
<% end %>
</ul><% end %>
<%= render 'target', :f => f %>
<%= f.submit "Confirm & Execute", id: "campaign-create-button" %>
<% end %>
views/campaigns/_target.html.erb
<%= f.label :plan, "Pick a Plan", class: "right-label" %>
<%= f.select :plan_id,
Plan.all.collect { |p|
[ p.name, p.id ]
}, { include_blank: true } %>
<%= f.label :channels, "Distribution Channel", class: "right-label" %>
<ul class="channels-list">
<% ["Folder", "Fax", "Email"].each do |channel| %>
<li><%= check_box_tag "campaign[channels][]", channel,
#campaign.channels.include?(channel),
id: "campaign_channels_#{channel}" %> <%= channel %></li>
<% end %>
</ul>
app/models/campaign.rb
class Campaign < ActiveRecord::Base
belongs_to :program
belongs_to :plan
has_many :uploadables, dependent: :destroy
has_many :uploadpdfs, through: :uploadables
has_one :uploadzip, dependent: :nullify
validates :name, presence: true,
uniqueness: true
validates :program, presence: { message: "Need a program!"}
validates :uploadzip, presence: true
validates :uploadpdf_ids, presence: true
serialize :channels, Array
end
app/controllers/campaigns_controller.rb
class CampaignsController < ApplicationController
def index
#campaigns = Campaign.all.order("created_at DESC")
end
def new
#campaign = Campaign.new
end
def create
#campaign = Campaign.new(campaign_params)
if #campaign.save
zip = Uploadzip.find(params[:uploadzip_id])
zip.campaign = #campaign
zip.save
flash[:success] = "Campaign Successfully Launched!"
redirect_to #campaign
else
flash[:error] = "There was a problem launching your Campaign."
redirect_to new_campaign_path
end
end
def show
#campaign = Campaign.includes(:program, :uploadzip, :plan, :uploadpdfs).find(params[:id])
end
private
def campaign_params
params.require(:campaign).permit(:name, :comment, :plan_id, :program_id, :campaign_id, uploadpdf_ids: [], channels: [])
end
end
I also followed this tutorial that provided code to help display validation errors for associations in the view.
config/initializers/errors_for_associations.rb
module ActionView::Helpers::ActiveModelInstanceTag
def error_message_with_associations
if #method_name.end_with?('_ids')
# Check for a has_(and_belongs_to_)many association (these always use the _ids postfix field).
association = object.class.reflect_on_association(#method_name.chomp('_ids').pluralize.to_sym)
else
# Check for a belongs_to association with method_name matching the foreign key column
association = object.class.reflect_on_all_associations.find do |a|
a.macro == :belongs_to && a.foreign_key == #method_name
end
end
if association.present?
object.errors[association.name] + error_message_without_associations
else
error_message_without_associations
end
end
alias_method_chain :error_message, :associations
end

Related

Edit and Update for assosiated model Rails

I tried to create edit and update action for my assosiated model named as Entity.
But When edit pages pop up no saved data shown. Means it is showing all field as empty and when I put values in it, creates a another object.
And also validation messages are not showing
Entities Controller
class EntitiesController < ApplicationController
def edit
#schema = Schema.find(params[:schema_id])
#entity = #schema.entities.find(params[:id])
end
def update
#schema = Schema.find(params[:schema_id])
#entity = #schema.entities.find(params[:id])
if #entity.update(entity_params)
redirect_to schema_entities_path(#schema)
else
render 'edit'
end
end
private
def entity_params
params.require(:entity).permit(:clientId, :name, :description, :mnemonic)
end
end
edit form for it:
<%= form_for([#schema, #schema.entities.build], data: { turbo: false }) do |f| %>
<%= f.text_field :clientId, placeholder:"ClientId" %>
<%= f.text_area :name, placeholder: "Name" %>
<%= f.text_area :description, placeholder: "Description" %>
<%= f.text_area :mnemonic, placeholder: "Mnemonic" %>
<%= f.submit 'Submit' %>
<% if #entity.errors.any? %>
<div id="error_explanation">
<% #entity.errors.full_messages.each do |msg| %>
<p class="error_msg"><%= msg %></p>
<% end %>
</div>
<% end %>
<% end %>
Its model:
class Entity < ApplicationRecord
belongs_to :schema
has_many :fields, dependent: :destroy
has_many :joins, through: :fields
validates :name, presence: true, uniqueness: true
def self.search(search)
where("name LIKE ?", "%#{search}%")
end
end
This is how I am passing values from index page:
<%= link_to 'Edit', edit_schema_entity_path(schema_id: #schema.id, id: data.id) if data.id %>

Unchangable checkbox

I've been struggling with this issue for 2 days now and I think I'm slowly starting to lose my mind.
I'm trying to update boolean 'schedule_display' for 'profiles' table in my nested form. Everything except this checkbox works just fine. At the present state html looks like this:
<%= nested_form_for #profile, html: { multipart: true } do |f| %>
<span class="picture">
<%= f.file_field :picture, accept: 'image/jpeg,image/gif,image/png' %>
</span>
<%= f.label :description %>
<%= f.text_field :description %>
<%= f.label :schedule_display %>
<%= f.check_box :schedule_display, {}, "true", "false" %>
<%= f.fields_for :buttons %>
<%= f.link_to_add "Add a button", :buttons %>
<%= f.submit "Save changes" %>
<% end %>
Parameters after submit look fine I think:
profile"=>{"description"=>"Dolor et exercitationem.", "schedule_display"=>"true", ...
Schedule_display is also in permited params in the right place:
params.require(:profile).permit(:id, :description, :picture,
:schedule_display, buttons_attributes: [
Corresponding part of Profile model looks like this:
class Profile < ActiveRecord::Base
belongs_to :user
has_many :buttons, :dependent => :destroy
accepts_nested_attributes_for :buttons,
reject_if: proc { |attributes| attributes['user_website_url'].blank? },
:allow_destroy => true
mount_uploader :picture, PictureUploader
attr_accessor :schedule_display
validates :description, presence: true, length: { maximum: 500 }
and update method is simply:
def update
#user = User.find(params[:id])
#profile = #user.profile
if #profile.update_attributes(profile_params)
flash[:success] = "Profile updated"
redirect_to #user
else
render 'edit'
end
end
I've tried doing something like this:
if #profile.update_attributes(profile_params)
params[:profile][:schedule_display] == '1' ?
profile.turn_schedule_on : profile.turn_schedule_off
with these turn_schedule_ functions and simple update_attribute(), but it didn't work either.
Why isn't it working?

ActiveRecord associated objects not being saved when submitting form - Ruby on Rails

Im having problems with my child objects (scores) not being saved when submitting a form for a new (Qa).
Here are the models:
QA
belongs_to :user
has_many :scores, :dependent => :destory
has_many :call_components, through: :scores
accepts_nested_attributes_for :scores
Score
belongs_to :qa
has_one :call_component
Call Component Is just a title and description for a score
belongs_to :score
User
has_many :qas
has_many :scores, through: :qas
For whatever reason when submitting the post the scores are not created, the QA is however.
Form
<%= form_for [#qa], role: "form" do |f| %>
<%= f.label :call_id %>
<%= f.number_field :call_id, :autofocus => true, class: "form-control monospaced-control", placeholder: "Call Id", required: "" %>
... more fields
<% CallComponent.all.each do |comp| %> <!-- Usually is an array of about 5 components, so there 5 scores -->
<h4><b><%= comp.title.to_s.capitalize %></b></h4>
<p><%= comp.description.to_s.capitalize %></p>
<%= f.fields_for :scores, #qa.scores do |builder| %>
<%= builder.label :score, "Score" %>
<%= builder.number_field :score, :autofocus => true, class: "form-control monospaced-control", placeholder: "Score", required: ""%>
<%= builder.label :comments, "Comments" %><br />
<%= builder.text_area :comments, :autofocus => true, class: "form-control monospaced-control", placeholder: "Score", required: ""%>
<%= builder.hidden_field :call_component_id, :value => comp.id %>
<% end %>
<% end %>
Here is the QA New method
def new
#qa = Qa.new
#qa.scores.build
# Tried it this way too
##score = Score.new
##score.build_qa # then in the view linking the form like #score.qa, this didnt work.
end
And here is the QA Create Method
def create
#qa = Qa.new(qa_params)
#qa.final_score = #qa.scores.sum(:score).to_i
#qa.user_id = current_user.id
if #qa.save
redirect_to qas_path, notice: "New qa published!"
else
flash[:alert] = "Qa not published!"
render :new
end
end
def qa_params
params.require(:qa).permit(:call_id,...,:scores_attributes)
end
Any thoughts on how to fix this would be awesome. Thanks for your time.
try this..
def qa_params
params.require(:qa).permit(:agent_user_id, :call_id, :call_date, :case_number, :completion_date, scores_attributes: [:score,:comments,:call_component_id] )
end
as you need to specify which attributes of the nested set are permitted.

Rails nested Attributes of join table won't be saved

Nested attributes of join model won't be saved. relation id's seems to be missing. The following error messages are added when the fields get validated:
* Assigned projects user can't be blank
* Assigned projects project can't be blank
The submitted params look like this ( <%= debug(params) %> )
--- !map:ActionController::Parameters
utf8: "\xE2\x9C\x93"
authenticity_token: HrF1NHrKNTdMMFwOvbYFjhJE1ltlKbuz2nsfBYYBswg=
project: !map:ActionController::Parameters
name: Peter Pan
assigned_projects_attributes: !map:ActiveSupport::HashWithIndifferentAccess
"0": !map:ActiveSupport::HashWithIndifferentAccess
position: Group Leader
currency: " Neverland Dollars"
commit: Add Project
action: create
controller: projects
I have 3 models, as followed:
class User < ActiveRecord::Base
has_many :assigned_projects
has_many :projects, :through => :assigned_projects
has_many :created_projects, :class_name => "Project", :foreign_key => :creator_id
end
class AssignedProject < ActiveRecord::Base
belongs_to :user, class_name: "User"
belongs_to :project, class_name: "Project"
attr_accessible :project_id, :user_id, :position, :project_attributes
accepts_nested_attributes_for :project
validates :user_id, presence: true
validates :project_id, presence: true
validates :position, presence: true
end
class Project < ActiveRecord::Base
belongs_to :user
has_many :assigned_projects
has_many :users, :through => :assigned_projects
belongs_to :creator, :class_name => "User", :foreign_key => :creator_id
attr_accessible :name, :creator_id, :currency :assigned_projects_attributes
accepts_nested_attributes_for :assigned_projects
validates :name, presence: true, length: { minimum: 3, maximum: 100 }
validates :currency, presence: true, length: { minimum: 1, maximum: 5 }
validates :creator_id, presence: true
end
So each User can create a Project. He can add any User to the Project through the join model.
Each Project belongs to a User resp. Creator and has_many user through assigned_projects
I want to give each user of a project a "position", which should be saved in the join model: assigned_project :position
the Project controller looks like that:
class ProjectsController < ApplicationController
def new
#project = Project.new
#project.assigned_projects.build(user_id: current_user)
end
def create
#project = current_user.assigned_projects.build.build_project(params[:project])
#project.creator = current_user
if #project.save
redirect_to current_user
else
render 'new'
end
end
end
and the project/new.html.erb form looks like that:
<%= form_for( #project ) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :assigned_projects do |ff| %>
<%= ff.label :position %>
<%= ff.text_field :position%>
<% end %>
<%= f.label :currency %>
<%= f.text_field :currency %>
<%= f.submit "Add Project", class: "" %>
<% end %>
UPDATE: current controller & view
def create
#project = Project.new(params[:project])
if #project.save
redirect_to current_user
else
render 'new'
end
end
<%= form_for( #project ) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.hidden_field :creator_id, value: current_user.id %>
<%= f.fields_for :assigned_projects, #project.assigned_projects do |ff| %>
<%= ff.label :position %>
<%= ff.text_field :position%>
<% end %>
<%= f.label :currency %>
<%= f.text_field :currency %>
<%= f.submit "Add Project", class: "" %>
<% end %>
View:
I think you need to pass the objects collection #project.assigned_projects you built in the new action to the fields_for:
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.fields_for :assigned_projects, #project.assigned_projects do |ff| %>
<%= ff.label :position %>
<%= ff.text_field :position%>
<% end %>
<%= f.label :currency %>
<%= f.text_field :currency %>
<%= f.submit "Add Project", class: "" %>
Controller:
If i understood the first line in the create action i think you try to re-build the project assigned_projects in-order to stamp the creator attribute !!
Instead you could remove this line and put a hidden field in the nested for, something like:
<%= ff.hidden_field :creator, current_user %>
So your controller looks pretty basic now:
def create
#project = Project.new(params[:prject])
if #project.save #nested objects will be saved automatically
redirect_to current_user
else
render 'new'
end
end
What does the build_project method do?
I think in your controller you should just have build, not build.build_project, so like this:
#project = current_user.assigned_projects.build(params[:project])
of if build_project is a method used to create the params then
#project = current_user.assigned_projects.build(project_params)
in the case of rails 4 you would need something like this:
def project_params
params.require(:project).permit(:your_params)
end
In the case of rails 3 I think you need to add
attr_accessible :param1, :param2
in the project model for the parameters you want to set.
Problem is solved by removing the validations for project_id and user_id in the join table "AssignedProject"
So the join Model looks like that:
# Join Model AssignedProject
class AssignedProject < ActiveRecord::Base
belongs_to :user#, class_name: "User"
belongs_to :project#, class_name: "Project"
attr_accessible :project_id, :user_id, :position, :project, :project_attributes
accepts_nested_attributes_for :project
validates :position, presence: { message: " can't be blank." }
end
The New and Create methods look like that:
# Projects Controller
class ProjectsController < ApplicationController
def new
#project = Project.new
#project.assigned_projects.build(user_id: current_user)
end
def create
#project = Project.new(params[:project])
if #project.save
#project.assigned_projects.create(user_id: current_user)
redirect_to current_user
else
render 'new'
end
end
end
And the form in the view for the new method looks like that:
<%= form_for( #project ) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :name %>
<%= f.text_field :name %>
<%= f.hidden_field :creator_id, value: current_user.id %>
<%= f.fields_for :assigned_projects, #project.assigned_projects do |ff| %>
<%= ff.hidden_field :project_id, value: #project %>
<%= ff.hidden_field :user_id, value: current_user.id %>
<%= ff.label :position %>
<%= ff.text_field :position%>
<% end %>
<%= f.label :currency %>
<%= f.text_field :currency %>
<%= f.submit "Add Project", class: "" %>
<% end %>

Ruby on Rails trouble creating record with has_many :through relationship

I'm using rails 3.2.14 and having trouble using has_many :through for the first time. I'm using a has_many :through relationship between Image and Design tables using a Design_Pictures table that will store the order ranking for design images. All Images are associated with one User. Later I want the flexibility to store other images in the Images table that are not associated with a particular design.
I can successfully add sample data to my database and show the image title (which is stored in the images table) and ranking (which is stored in the design_pictures table) in my show design pages. What I can't figure out is how to create a new design_picture. Once I can get this working I'm going to use CarrierWave or Paperclip to add images to the Image table.
Here are my models:
app/models/image.rb:
class Image < ActiveRecord::Base
attr_accessible :title
belongs_to :user
has_many :design_pictures, dependent: :destroy
has_many :designs, :through => :design_pictures
validates :user_id, presence: true
validates :title, length: { maximum: 80 }
end
app/models/design.rb:
class Design < ActiveRecord::Base
attr_accessible :title
has_many :design_pictures, dependent: :destroy
has_many :images, :through => :design_pictures
validates :title, presence: true, length: { maximum: 60 }
end
app/models/design_picture.rb:
class DesignPicture < ActiveRecord::Base
attr_accessible :ranking
belongs_to :image
belongs_to :design
validates :image_id, presence: true
validates :design_id, presence: true
default_scope order: 'design_pictures.ranking ASC'
end
app/models/user.rb:
class User < ActiveRecord::Base
attr_accessible :name
has_many :designs, dependent: :destroy
has_many :images, dependent: :destroy
validates :name, presence: true, length: { maximum: 50 }
end
Views:
app/views/designs/show.html.erb:
<div>
<% if #design.design_pictures.any? %>
<h3>Images (<%= #design.design_pictures.count %>)</h3>
<ul>
<%= render #design_pictures %>
</ul>
<% end %>
</div>
<div>
<%= render 'shared/design_picture_form' %>
</div>
app/views/design_pictures/_design_picture.html.erb
<li>
<%= design_picture.image.title %> - Ranking: <%= design_picture.ranking %>
</li>
app/views/shared/_design_picture_form.html.erb:
<%= form_for(#design_picture) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :title, "Title" %>
<%= f.text_field :title,
placeholder: "Name your image.",
class: "" %>
<%= f.label :ranking, "Ranking" %>
<%= f.text_field :ranking,
placeholder: "Rank your design picture.",
class: "" %>
<%= hidden_field_tag(:design_id, #design.id) %>
<%= f.submit "Add Image", class: "btn btn-large btn-primary" %>
<% end %>
Controllers:
app/controllers/designs_controller.rb
def show
#design = Design.find(params[:id])
#design_pictures = #design.design_pictures.find(:all, :limit => 10)
#image = current_user.images.build
#design_picture = #design.design_pictures.build
end
app/controllers/design_pictures_controller.rb
def create
#current_design = Design.find(params[:design_id])
#image = current_user.images.new(params[:title])
#design_picture = #current_design.design_pictures.build(:image_id => #image.id, params[:ranking])
if #design_picture.save
flash[:success] = "Micropost created!"
redirect_to #current_design
else
redirect_to designs_url
end
end
Error when I visit the design_picture form partial:
undefined method `title' for #<DesignPicture image_id: nil, design_id: 1427, ranking: nil>
If I remove the title from the form I can submit form but nothing is created.
Any help would be greatly appreciated.
Your DesignPicture does not have a title attribute. When you do:
#design.design_pictures.build
It build a new DesignPicture instance. And the following do not work:
<%= form_for(#design_picture) do |f| %>
# ...
<%= f.label :title, "Title" %>
# ...
You might want to take a look at Nested Form
I'm not sure if this is the best way to go about this but this is how I was able to get my form to submit and create both an Image entry with it's title attribute and the Design_Picture entry with it's rank attribute. I ended up not needing to use accepts_nested_attributes_for.
app/views/shared/_design_picture_form.html.erb
<%= form_for(#design_picture) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= label_tag(:title, "Title") %>
<%= text_field_tag(:title) %>
<%= f.label :ranking, "Ranking" %>
<%= f.text_field :ranking,
placeholder: "Rank your design picture.",
class: "" %>
<%= hidden_field_tag(:design_id, #design.id) %>
<%= f.submit "Add Image", class: "btn btn-large btn-primary" %>
app/controllers/designs_controller.rb
def show
#design = Design.find(params[:id])
#design_pictures = #design.design_pictures.find(:all, :limit => 10)
#design_picture = #design.design_pictures.build
end
app/controllers/design_pictures_controller.rb
def create
#image = current_user.images.create(:title => params[:title])
#current_design = Design.find(params[:design_id])
#design_picture = #current_design.design_pictures.build(params[:design_picture])
#design_picture.image_id = #image.id
if #design_picture.save
flash[:success] = "Design Picture created!"
redirect_to #current_design
else
redirect_to designs_url
end
end

Resources