There are already a couple of questions addressing this issue:
render/redirect to new action when validation fails (rails)
Keeping validation errors after redirect from partial back to the same page that has the partial
I tried to implement some of the solutions recommended but did not manage fixing my issue.
So here we go.
In my Rails 4 app, there are four models:
class User < ActiveRecord::Base
has_many :administrations
has_many :calendars, through: :administrations
has_many :invitations, :class_name => "Invite", :foreign_key => 'recipient_id'
has_many :sent_invites, :class_name => "Invite", :foreign_key => 'sender_id'
end
class Calendar < ActiveRecord::Base
has_many :administrations
has_many :users, through: :administrations
has_many :invites
end
class Administration < ActiveRecord::Base
belongs_to :user
belongs_to :calendar
end
class Invite < ActiveRecord::Base
belongs_to :calendar
belongs_to :sender, :class_name => 'User'
belongs_to :recipient, :class_name => 'User'
end
Here, we are going to focus on the Invite and the Calendar models.
In the calendar edit view, I have a form to create a new invite:
<h2>Edit <%= #calendar.name %> calendar</h2>
<%= render 'form' %>
<h2>Invite new users to <%= #calendar.name %> calendar</h2>
<%= form_for #invite , :url => invites_path do |f| %>
<%= f.hidden_field :calendar_id, :value => #invite.calendar_id %>
<%= f.label :email %>
<%= f.email_field :email %>
<%= f.label "Role" %>
<%= f.radio_button(:recipient_role, "Editor") %>
<%= f.label "Editor" %>
<%= f.radio_button(:recipient_role, "Viewer") %>
<%= f.label "Viewer" %>
<%= f.submit 'Send' %>
<% end %>
<%= link_to 'Show', calendar_path %> |
<%= link_to 'Back', calendars_path %>
–––––
UPDATE: please note that I have defined a calendars.html.erb layout for my calendar views, including the following code to display alerts:
<% flash.each do |key, value| %>
<div class="alert alert-<%= key %>">
<%= value %>
</div>
<% end %>
–––––
In the Invite model, I want to validate the presence of :email and :recipient_role (both values submitted by the user through the form), so I added this to the Invite model:
validates :email, :recipient_role, presence: true
And I would like users to be redirected to the form with an error message if they forgot to fill one of these two fields, which I implemented with the following code in the InvitesController:
class InvitesController < ApplicationController
def create
# some code
if #invite.save
# some code
else
format.html { render :template => "calendars/edit", notice: 'Invitation could not be sent.' }
end
redirect_to calendar_path(#calendar), notice: 'Invitation was successfully sent.'
end
private
def invite_params
params.require(:invite).permit(:email, :calendar_id, :recipient_role)
end
end
In particular, I thought that the line format.html { render :template => "calendars/edit", notice: 'Invitation could not be sent.' } would allow me to do what I wanted, as recommended here.
However, when I try to submit the form with at least an empty field, I get the following error:
ArgumentError in InvitesController#create
too few arguments
end
else
format.html { render :template => "calendars/edit", notice: 'Invitation could not be sent.' }
end
redirect_to calendar_path(#calendar), notice: 'Invitation was successfully sent.'
end
And this is the server log:
Processing by InvitesController#create as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"p+yxM9Tw3JiNw/6Chv9//N8uvWhMy0TqrudTu0y9D9zXCl6kK2LtCqPu1rvTGv5gOMBuv2RK/IX2MBpWUTelZw==", "invite"=>{"calendar_id"=>"3", "email"=>"test#example.com"}, "commit"=>"Send"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 1]]
Calendar Load (0.1ms) SELECT "calendars".* FROM "calendars" WHERE "calendars"."id" = ? LIMIT 1 [["id", 3]]
(0.0ms) begin transaction
(0.0ms) rollback transaction
Completed 500 Internal Server Error in 27ms (ActiveRecord: 1.3ms)
ArgumentError (too few arguments):
app/controllers/invites_controller.rb:16:in `format'
app/controllers/invites_controller.rb:16:in `create'
Rendered /Users/TXC/.rvm/gems/ruby-2.2.1#global/gems/actionpack-4.2.2/lib/action_dispatch/middleware/templates/rescues/_source.erb (3.7ms)
Rendered /Users/TXC/.rvm/gems/ruby-2.2.1#global/gems/actionpack-4.2.2/lib/action_dispatch/middleware/templates/rescues/_trace.html.erb (1.5ms)
Rendered /Users/TXC/.rvm/gems/ruby-2.2.1#global/gems/actionpack-4.2.2/lib/action_dispatch/middleware/templates/rescues/_request_and_response.html.erb (0.9ms)
Rendered /Users/TXC/.rvm/gems/ruby-2.2.1#global/gems/actionpack-4.2.2/lib/action_dispatch/middleware/templates/rescues/diagnostics.html.erb within rescues/layout (54.0ms)
Rendered /Users/TXC/.rvm/gems/ruby-2.2.1/gems/web-console-2.2.1/lib/web_console/templates/_markup.html.erb (0.3ms)
Rendered /Users/TXC/.rvm/gems/ruby-2.2.1/gems/web-console-2.2.1/lib/web_console/templates/_inner_console_markup.html.erb within layouts/inlined_string (0.3ms)
Rendered /Users/TXC/.rvm/gems/ruby-2.2.1/gems/web-console-2.2.1/lib/web_console/templates/_prompt_box_markup.html.erb within layouts/inlined_string (0.4ms)
Rendered /Users/TXC/.rvm/gems/ruby-2.2.1/gems/web-console-2.2.1/lib/web_console/templates/style.css.erb within layouts/inlined_string (0.3ms)
Rendered /Users/TXC/.rvm/gems/ruby-2.2.1/gems/web-console-2.2.1/lib/web_console/templates/console.js.erb within layouts/javascript (36.5ms)
Rendered /Users/TXC/.rvm/gems/ruby-2.2.1/gems/web-console-2.2.1/lib/web_console/templates/main.js.erb within layouts/javascript (0.2ms)
Rendered /Users/TXC/.rvm/gems/ruby-2.2.1/gems/web-console-2.2.1/lib/web_console/templates/error_page.js.erb within layouts/javascript (0.5ms)
Rendered /Users/TXC/.rvm/gems/ruby-2.2.1/gems/web-console-2.2.1/lib/web_console/templates/index.html.erb (83.2ms)
What am I doing wrong?
Do the following things please
def create
# some code
if #invite.save
redirect_to calendar_path(#calendar), notice: 'Invitation was successfully sent.'
else
render template: "calendars/edit", notice: 'Invitation could not be sent.'
end
end
Related
I have three tables:
Staff
Staff_locations
Locations
Business case: Staff can work in multiple locations. Association between Staff and Location is done through staff_locations table. While creating Staff entry I am choosing locations that he/she belongs to. This is working fine.
But I have a problem with correct display of collection_select in the edit action. It is displayed as many times as many entries matching staff_id there are in the staff_locations table.
I can't figure out how to fix that and I didn't find any good hint anywhere so far.
models
class Staff < ApplicationRecord
has_many :visits, dependent: :destroy
has_many :work_schedules
has_many :customers, through: :visits
has_many :staff_locations, dependent: :destroy
has_many :locations, through: :staff_locations
accepts_nested_attributes_for :staff_locations, allow_destroy: true
def staff_locations_attributes=(staff_locations_attributes)
staff_locations_attributes.values[0][:location_id].each do |loc_id|
if !loc_id.blank?
staff_location_attribute_hash = {};
staff_location_attribute_hash['location_id'] = loc_id;
staff_location = StaffLocation.create(staff_location_attribute_hash)
self.staff_locations << staff_location
end
end
end
end
class StaffLocation < ApplicationRecord
belongs_to :staff
belongs_to :location
validates :staff_id, :location_id, uniqueness: true
end
class Location < ApplicationRecord
has_many :staff_locations
has_many :staffs, through: :staff_locations
end
staffs_controller
class StaffsController < ApplicationController
before_action :set_staff, only: %i [ show edit update destroy ]
def index
#staffs = Staff.all
end
def show
end
def new
#staff = Staff.new
#staff.staff_locations.build
end
def create
#staff = Staff.new(staff_params)
if #staff.save
redirect_to #staff
else
render :new, status: :unprocessable_entity
end
end
def edit
end
def update
respond_to do |format|
if #staff.update(staff_params)
format.html { redirect_to #staff, notice: 'Staff was successfully updated.' }
format.json { render :show, status: :ok, staff: #staff }
else
format.html { render :edit }
format.json { render json: #staff.errors, status: :unprocessable_entity }
end
end
end
def destroy
end
private
def staff_params
params.require(:staff).permit(:first_name, :last_name, :status, :staff_type, staff_locations_attributes: [:location_id => [] ])
#due to multiple select in the new staff form, staff_locations_attributes needs to contain Array of location_ids.
#Moreover check Staff model's method: staff_locations_attributes. It converts staff_locations_attributes into hashes.
end
def set_staff
#staff = Staff.find(params[:id])
end
end
form partial
<%= form_for(#staff) do |form| %>
<div>
<% if params["action"] != "edit" %>
<%= form.fields_for :staff_locations do |staff_location_form| %>
<%= staff_location_form.label :location_id, 'Associated Locations' %><br>
<%= staff_location_form.collection_select :location_id, Location.all, :id, :loc_name, {include_blank: false}, {:multiple => true } %>
<% end %>
<% else %>
<%= form.fields_for :staff_locations do |staff_location_form| %>
<%= staff_location_form.label :location_id, 'Associated Locations' %><br>
<%= staff_location_form.collection_select :location_id, Location.all, :id, :loc_name, {selected: #staff.locations.map(&:id).compact, include_blank: false}, {:multiple => true} %>
<% #debugger %>
<% end %>
<% end %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
UPDATE
After changes suggested by #Beartech, update method works fine. However new action stopped working. Below I am pasting what I captured while submitting form to create one entry in Staff table and two associated entries in Staff_locations table.
Before saving objetct to the DB, I checked in the console:
#staff
#staff.location_ids
staff_params
After that I did save. I don't understand reason why it ends up with FALSE status.
14| ##staff.staff_locations.build
15| end
16|
17| def create
18| #staff = Staff.new(staff_params)
=> 19| debugger
20|
21| respond_to do |format|
22| if #staff.save
23| format.html { redirect_to #staff, notice: 'Staff was successfully created.' }
=>#0 StaffsController#create at ~/rails_projects/dentysta/app/controllers/staffs_controller.rb:19
#1 ActionController::BasicImplicitRender#send_action(method="create", args=[]) at ~/rails_projects/dentysta/vendor/bundle/ruby/3.0.0/gems/actionpack-7.0.4/lib/action_controller/metal/basic_implicit_render.rb:6
# and 75 frames (use `bt' command for all frames)
(ruby) #staff
#<Staff:0x00007f2400acb2e8 id: nil, first_name: "s", last_name: "dd", status: "Active", staff_type: "Doctor", created_at: nil, updated_at: nil>
(ruby) #staff.location_ids
[4, 5]
(ruby) staff_params
#<ActionController::Parameters {"first_name"=>"s", "last_name"=>"dd", "status"=>"Active", "staff_type"=>"Doctor", "location_ids"=>["", "4", "5"]} permitted: true>
(ruby) #staff.save
TRANSACTION (0.1ms) begin transaction
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
StaffLocation Exists? (0.1ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."staff_id" IS NULL LIMIT ? [["LIMIT", 1]]
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
StaffLocation Exists? (0.1ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."location_id" = ? LIMIT ? [["location_id", 4], ["LIMIT", 1]]
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
CACHE StaffLocation Exists? (0.0ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."staff_id" IS NULL LIMIT ? [["LIMIT", 1]]
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
StaffLocation Exists? (0.3ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."location_id" = ? LIMIT ? [["location_id", 5], ["LIMIT", 1]]
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
TRANSACTION (0.1ms) rollback transaction
↳ (rdbg)//home/mw/rails_projects/dentysta/app/controllers/staffs_controller.rb:1:in `create'
false
(rdbg) c # continue command
TRANSACTION (0.1ms) begin transaction
↳ app/controllers/staffs_controller.rb:22:in `block in create'
StaffLocation Exists? (0.2ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."staff_id" IS NULL LIMIT ? [["LIMIT", 1]]
↳ app/controllers/staffs_controller.rb:22:in `block in create'
StaffLocation Exists? (0.1ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."location_id" = ? LIMIT ? [["location_id", 4], ["LIMIT", 1]]
↳ app/controllers/staffs_controller.rb:22:in `block in create'
CACHE StaffLocation Exists? (0.0ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."staff_id" IS NULL LIMIT ? [["LIMIT", 1]]
↳ app/controllers/staffs_controller.rb:22:in `block in create'
StaffLocation Exists? (0.2ms) SELECT 1 AS one FROM "staff_locations" WHERE "staff_locations"."location_id" = ? LIMIT ? [["location_id", 5], ["LIMIT", 1]]
↳ app/controllers/staffs_controller.rb:22:in `block in create'
TRANSACTION (0.1ms) rollback transaction
↳ app/controllers/staffs_controller.rb:22:in `block in create'
Rendering layout layouts/application.html.erb
Rendering staffs/new.html.erb within layouts/application
Location Count (0.1ms) SELECT COUNT(*) FROM "locations"
↳ app/views/staffs/_form.html.erb:36
Location Load (0.1ms) SELECT "locations".* FROM "locations"
↳ app/views/staffs/_form.html.erb:36
Rendered staffs/_form.html.erb (Duration: 18.5ms | Allocations: 2989)
Rendered staffs/new.html.erb within layouts/application (Duration: 21.7ms | Allocations: 3059)
Rendered layout layouts/application.html.erb (Duration: 24.6ms | Allocations: 4054)
Completed 422 Unprocessable Entity in 2302301ms (Views: 30.1ms | ActiveRecord: 1.8ms | Allocations: 174939)
Edit Important: Using a multi-select may have unintended user interface issues. When you use the code below the multi-select for an existing record will load with the existing associated Locations highlighted as selections. If you don't touch that form element and then save the form, they will remain associated. But the entire multi-select list may not display at once. And if the person can not see all of the selected elements they may click on one and that will unselect all the others, thus deleting those associations when the record saves. I have edited the answer to add size: to the HTML attributes. This will show all of the options so they can see which are selected and what happens when they click on one (the deselecting of all others requiring a shfit/option select to get them reselected). I would consider if this is the correct interface element for what you are doing. You may want to consider collection_check_boxes as the correct UI element for this as they will have to purposely unselect any they want to get rid of and won't have to reselect them every time they add or remove one location.
Took me a while to remember how to do this. It's because you are focusing on the join table. Normally that is what you would do when you WANT multiple form fields. But you are actually looking to leverage the has_many relationship.
Remember, your accepts_nested_attributes_for give you a method of location_ids= which lets you set those locations just by passing the IDs. Rails will take care of making the associations using the join model.
In your console try:
#staff = Staff.first
# returns a staff object
#staff.locations
#returns an array of location objects due to the has_many
#staff.location_ids
# [12, 32]
#staff.location_ids = [12, 44, 35]
#this will update the joined locations to those locations by id. If any current locations are not in that array, they get deleted from the join table.
change your strong params from:
params.require(:staff).permit(:first_name, :last_name, :status,
:staff_type, staff_locations_attributes: [:location_id => [] ])
to:
params.require(:staff).permit(:first_name, :last_name, :status,
:staff_type, :location_ids => [] )
In your form you just want ONE form element, built using methods on #staff:
<%= f.label :locations %><br />
<%= f.collection_select :location_ids, Location.all, :id, :name,{selected: #staff.location_ids,
include_blank: false}, {:multiple => true, size: Location.all.count } %>
So this works since .location_ids is a valid method on #staff, Location.all returns a collection of all locations, then the two symbols (:id and :name) are both valid methods for a single location object. Then in the selected... you are just using the same .location_ids to grab the ones that already exist to mark them as selected.
I'd forgotten how to do this, it's been a while. Once I remembered it was so easy.
For those who will be struggling with similar case in the future, I am pasting what works for me right now. #Beartech thanks once again for your help. It saved me a lot of time.
models
class Staff < ApplicationRecord
has_many :visits, dependent: :destroy
has_many :work_schedules
has_many :customers, through: :visits
has_many :staff_locations, dependent: :destroy
has_many :locations, through: :staff_locations
accepts_nested_attributes_for :staff_locations, allow_destroy: true
end
class StaffLocation < ApplicationRecord
belongs_to :staff
belongs_to :location
end
class Location < ApplicationRecord
has_many :staff_locations
has_many :staffs, through: :staff_locations
end
staffs_controller
class StaffsController < ApplicationController
before_action :set_staff, only: %i[ show edit update destroy ]
def index
#staffs = Staff.all
end
def show
#debugger
end
def new
#staff = Staff.new
end
def create
#staff = Staff.new(staff_params)
debugger
respond_to do |format|
if #staff.save!
format.html { redirect_to #staff, notice: 'Staff was successfully created.' }
format.json { render :show, status: :ok, staff: #staff }
#redirect_to #staff
else
format.html { render :new, status: :unprocessable_entity, notice: 'Somthing went wrong' }
format.json { render json: #staff.errors, status: :unprocessable_entity }
#render :new, status: :unprocessable_entity
end
end
end
def edit
end
def update
respond_to do |format|
if #staff.update(staff_params)
format.html { redirect_to #staff, notice: 'Staff was successfully updated.' }
format.json { render :show, status: :ok, staff: #staff }
else
format.html { render :edit }
format.json { render json: #staff.errors, status: :unprocessable_entity }
end
end
end
def destroy
end
private
def staff_params
params.require(:staff).permit(:first_name, :last_name, :status, :staff_type, :location_ids => [] )
end
def set_staff
#staff = Staff.find(params[:id])
end
end
_form partial
<%= form_for(#staff) do |form| %>
<div>
<%= form.label :first_name %><br>
<%= form.text_field :first_name %>
<% #staff.errors.full_messages_for(:first_name).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :last_name %><br>
<%= form.text_field :last_name %>
<% #staff.errors.full_messages_for(:last_name).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :staff_type %><br>
<%= form.collection_select :staff_type, Staff.valid_types, :to_s, :to_s, {include_blank: false}, {:multiple => false} %>
<% #staff.errors.full_messages_for(:staff_type).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :status %><br>
<%= form.collection_select :status, Staff.valid_statuses, :to_s, :to_s, {include_blank: false}, {:multiple => false} %>
<% #staff.errors.full_messages_for(:status).each do |message| %>
<div><%= message %></div>
<% end %>
</div>
<div>
<%= form.label :locations %><br />
<%= form.collection_select :location_ids, Location.all, :id, :loc_name,{selected: #staff.location_ids, include_blank: false}, {:multiple => true, size: Location.all.count } %>
</div>
<div>
<%= form.submit %>
</div>
<% end %>
I would like to assign the event_option_id to the registration. I can easily do it in the view by adding this to the form:
<%= f.text_field :event_option_id, value: #event_option.id %>
I would like to do it in the model not in the view. For security I'm doing the same for the registration price. Setting the price from the model is working but doing the same thing for the event_option_id is not.
Registration Model:
class Registration < ActiveRecord::Base
belongs_to :event_option
belongs_to :order_item
belongs_to :order
before_save :set_event_options
def order_present
if order.nil?
errors.add(:order, "is not a valid order.")
end
end
def registration_price
self[:price] = event_option.price
end
def event_option_id
self.event_option_id = event_option
end
private
def set_event_options
self[:price] = registration_price
self.event_option_id = event_option_id
end
end
EventOptions model:
class EventOption < ActiveRecord::Base
belongs_to :event
has_many :registrations
end
Create Method in the Registrations controller:
def create
#event_option = EventOption.find(params[:id])
#order = current_order
#registration = #order.registrations.build(registration_params)
##registration = Registration.new(registration_params)
#order_id = current_order.id
respond_to do |format|
if #registration.save
format.html { redirect_to #registration, notice: 'Registration was successfully created.' }
format.json { render :show, status: :created, location: #registration }
format.js {}
#order.save
session[:order_id] = #order.id
else
format.html { render :new }
format.json { render json: #registration.errors, status: :unprocessable_entity }
end
end
Error in log:
Started POST "/registrations" for 127.0.0.1 at 2016-01-04 21:16:06 -0500
Processing by RegistrationsController#create as JS
Parameters: {"utf8"=>"âo"", "registration"=>{"name"=>"saasas", "lastname"=>"asas"}, "commit"=>"Create Registration"}
EventOption Load (0.0ms) SELECT "event_options".* FROM "event_options" WHERE "event_options"."id" = ? LIMIT 1 [["id", nil]]
Completed 404 Not Found in 8ms (ActiveRecord: 0.0ms)
ActiveRecord::RecordNotFound (Couldn't find EventOption with 'id'=):
app/controllers/registrations_controller.rb:27:in `create'
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/actionpack-4.2.4/lib/action_dispatch/middleware/templates/rescues/_source.erb (0.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/actionpack-4.2.4/lib/action_dispatch/middleware/templates/rescues/_trace.text.erb (0.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/actionpack-4.2.4/lib/action_dispatch/middleware/templates/rescues/_request_and_response.text.erb (1.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/actionpack-4.2.4/lib/action_dispatch/middleware/templates/rescues/diagnostics.text.erb (44.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/web-console-2.2.1/lib/web_console/templates/_markup.html.erb (1.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/web-console-2.2.1/lib/web_console/templates/_inner_console_markup.html.erb within layouts/inlined_string (0.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/web-console-2.2.1/lib/web_console/templates/_prompt_box_markup.html.erb within layouts/inlined_string (0.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/web-console-2.2.1/lib/web_console/templates/style.css.erb within layouts/inlined_string (0.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/web-console-2.2.1/lib/web_console/templates/console.js.erb within layouts/javascript (48.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/web-console-2.2.1/lib/web_console/templates/main.js.erb within layouts/javascript (0.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/web-console-2.2.1/lib/web_console/templates/error_page.js.erb within layouts/javascript (0.0ms)
Rendered C:/RailsInstaller/Ruby2.1.0/lib/ruby/gems/2.1.0/gems/web-console-2.2.1/lib/web_console/templates/index.html.erb (105.0ms)
I'm reading this part of the rails documentation:
http://api.rubyonrails.org/classes/ActiveRecord/Callbacks.html,
but still can't figure out whats going on.
Update:
Routes:
Rails.application.routes.draw do
resource :cart, only: [:show]
resources :orders
resources :order_items
resources :registrations
resources :event_options
resources :events
resources :charges
root 'events#index'
Registration form - inside event_option show.html.erb:
<%= form_for(#registration, remote: true) do |f| %>
<% if #registration.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#registration.errors.count, "error") %> prohibited this registration from being saved:</h2>
<ul>
<% #registration.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="field">
<%= f.label :name %><br>
<%= f.text_field :name %>
</div>
<div class="field">
<%= f.label :lastname %><br>
<%= f.text_field :lastname %>
</div>
<div class="actions">
<%= f.submit %>
</div>
<% end %>
I think your controller's create function is missing params[:id].
The easiest way to fix is adding it into your form:
<%= form_for(#registration, remote: true) do |f| %>
<%= hidden_field_tag :id, your_event_option_id %>
### your other stuffs
The error clearly states that Rails is unable to find an EventOption without an id:
def create
#event_option = EventOption.find params[:id] #-> :id is not passed to create action
To fix it, just use the param that's submitted as part of the form:
#app/controllers/registrations_controller.rb
class RegistrationsController < ApplicationController
def create
#order = current_order
#registration = #order.registrations.new registration_params
#registration.save
end
private
def registration_params
params.require(:registration).permit(:event_option_id, :other, :params)
end
end
--
The above would work well if the user could choose the event_option_id in the form; if you're using a hidden_field, you'll be better using nested routes:
#config/routes.rb
resources :event_option do
resources :registrations #-> url.com/event_options/:event_option_id/registrations/new
end
This will set the event_option_id as part of the top-level params hash, which will be passed to the controller as params[:event_option_id] (as you have it already):
#app/controllers/registrations_controller.rb
class RegistrationsController < ApplicationController
def create
#event_option = EventOption.find params[:event_option_id]
end
end
Tip - you can declare multiple resources at once:
#config/routes.rb
resource :cart, only: [:show]
resources :orders, :order_items, :registrations, :event_options, :events, :charges
Trying to learn RoR. Currently adding comments to posts by user. So far I have a posts model, comments model and post_comments model (to associate the two). So for in 'rails console' I can run: (say I set p = Post.first and c = Comment.first)
p.comments << c
This forms an association so it works in the console. I just can't seem to get the comments to form this association from the UI. So far I am creating the comments at "comments/new" (not sure if this is the issue. Do they need to be created on the "show view" for "post").
Here are some code snippets
Controllers
comments_controller.rb
class CommentsController < ApplicationController
def index
#comment = Comment.all
end
def new
#comment = Comment.new
end
def create
#comment = Comment.new(commentParams)
if #comment.save
flash[:success] = "Comment successfully added"
redirect_to comments_path(#comment)
else
render 'new'
end
end
def show
#comment = Comment.find(params[:id])
end
private
def commentParams
params.require(:comment).permit(:comment)
end
end
posts_controller
class PostsController < ApplicationController
before_action :setPost, only: [:edit, :update, :show, :destroy, :sold]
before_action :requireUser, except: [:index, :show]
before_action :requireSameUser, only: [:edit, :update, :destroy, :sold]
def index
#posts = Post.paginate(page: params[:page], per_page: 20)
end
def new
#post = Post.new
end
def create
#post = Post.new(postParams)
#post.user = currentUser
if #post.save
flash[:success] = "Post successfully added."
redirect_to post_path(#post)
else
render 'new'
end
end
def update
if #post.update(postParams)
flash[:success] = "Post successfully updated."
redirect_to post_path(#post)
else
render 'edit'
end
end
def show
end
def edit
end
def sold
#post.toggle(:sold)
#post.save
redirect_to post_path(#post)
end
def destroy
#post.destroy
flash[:danger] = "Item successfully deleted."
redirect_to posts_path
end
private
def postParams
params.require(:post).permit(:title, :price, :description, category_ids:[])
end
def setPost
#post = Post.find(params[:id])
end
def requireSameUser
if currentUser != #post.user and !currentUser.admin
flash[:danger] = "You can only edit or delete your own items"
redirect_to root_path
end
end
end
Models
comment.rb
class Comment < ActiveRecord::Base
belongs_to :post_comments
belongs_to :user
belongs_to :post
end
post_comment.rb
class PostComment < ActiveRecord::Base
belongs_to :post
belongs_to :comment
end
post.rb
class Post < ActiveRecord::Base
belongs_to :user
has_many :post_categories
has_many :categories, through: :post_categories
has_many :post_comments
has_many :comments, through: :post_comments
validates :title, presence: true,
length: { minimum: 4, maximum: 20 }
validates :description, presence: true,
length: { maximum: 1000 }
validates :user_id, presence: true
end
Views
posts/show.html.erb
<p>Comments: <%= render #post.comments %></p>
This renders the partial below
comments/_comment.html.erb
<%= link_to comment.name, comment_path(comment) %>
Finally is the new comment page as it is.
comments/new.html.erb
<h1>New Comment</h1>
<%= render 'shared/errors', obj: #comment %>
<div class="row">
<div class="col-xs-12">
<%= form_for(#comment, :html => {class: "form-horizontal", role: "form"}) do |f| %>
<div class="form-group">
<div class="control-label col-sm-2">
<%= f.label :comment %>
</div>
<div class="col-sm-8">
<%= f.text_area :comment, rows: 3, class: "form-control", placeholder: "Please enter a comment", autofocus: true %>
</div>
</div>
<div class="form-group">
<div class="center col-sm-offset-1 col-sm-10">
<%= f.submit class: "btn btn-primary btn-lg" %>
</div>
</div>
<% end %>
Any help would be greatly received.
Update
Log
Started GET "/posts/2" for ::1 at 2016-01-15 12:39:55 +0000
Processing by PostsController#show as HTML
Parameters: {"id"=>"2"}
[1m[36mPost Load (0.1ms)[0m [1mSELECT "posts".* FROM "posts" WHERE "posts"."id" = ? LIMIT 1[0m [["id", 2]]
[1m[35mUser Load (0.1ms)[0m SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]]
[1m[36m (0.1ms)[0m [1mSELECT COUNT(*) FROM "posts" WHERE "posts"."user_id" = ?[0m [["user_id", 1]]
[1m[35mCategory Exists (0.1ms)[0m SELECT 1 AS one FROM "categories" INNER JOIN "post_categories" ON "categories"."id" = "post_categories"."category_id" WHERE "post_categories"."post_id" = ? LIMIT 1 [["post_id", 2]]
[1m[36mCategory Load (0.0ms)[0m [1mSELECT "categories".* FROM "categories" INNER JOIN "post_categories" ON "categories"."id" = "post_categories"."category_id" WHERE "post_categories"."post_id" = ?[0m [["post_id", 2]]
Rendered categories/_category.html.erb (0.2ms)
[1m[35mComment Load (0.1ms)[0m SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = ? [["post_id", 2]]
Rendered comments/_comment.html.erb (0.1ms)
Rendered posts/show.html.erb within layouts/application (6.5ms)
[1m[36mCategory Load (0.1ms)[0m [1mSELECT "categories".* FROM "categories"[0m
Rendered layouts/_navigation.html.erb (0.9ms)
Rendered layouts/_messages.html.erb (0.1ms)
Rendered layouts/_footer.html.erb (0.1ms)
Completed 200 OK in 52ms (Views: 50.3ms | ActiveRecord: 0.5ms)
As a comment can belong to a single post only, you do not need an association table (post_comments). You just need a simple one-to-many relationship.
Your post comment would be:
class Post < ActiveRecord::Base
has_many :comments
...
end
And comment would be like this:
class Comment < ActiveRecord::Base
belongs_to :post
...
end
Just make sure that you have the necessary post_id column in the comments table (you can check the db/schema.rb file). If that is missing, you can use the following migration to add it:
class AddPostIdToComments < ActiveRecord::Migration
def change
add_column :comments, :post_id, :integer
add_index :comments, :post_id
end
end
You also need to make sure you keep somewhere the reference to the post, whenever a user tries to create a comment to a post. You can add this in a hidden field to your comments/new.html.erb template. You could set the hidden field in the new action, in PostsController, after passing it through the URL.
So, in your posts/show.html.erb template you would have:
<%= link_to "Add Comment", new_comment_path(post_id: #post.id) %>
In your new action, in PostsController:
def new
#comment = Comment.new(post_id: params[:post_id])
end
And finally the hidden field in your form would be:
<%= f.hidden_field :post_id %>
Finally, add the post_id parameter to the list of permitted parameters in CommentsController.
instead of
In your new action, in PostsController:
def new
#comment = Comment.new(post_id: params[:post_id])
end
you can use this in the form views
<%= form.hidden_field :post_id, value: "#{params[:post_id]}" %>
I am trying to create a new record for 'campaign' via the 'customer' as follows. The form submits correctly, however no new campaign record is created for the customer. Any advise would be greatly appreciated.
Models
class Customer::Customer < User
has_one :library, dependent: :destroy
has_many :campaigns, dependent: :destroy
accepts_nested_attributes_for :campaigns, :library, allow_destroy: true
end
class Customer::Campaign < ActiveRecord::Base
belongs_to :customer
end
Customer Controller - Update Method only shown
class Customer::CustomersController < ApplicationController
layout "customer_layout"
def update
session[:return_to] ||= request.referer
#customer = User.find(params[:id])
if #customer.update_attributes(customer_params)
flash[:success] = "Profile updated"
redirect_to #customer
else
flash[:notice] = "Invalid"
redirect_to session.delete(:return_to)
end
end
def customer_params
params.require('customer_customer').permit(:name, :email, :password, :password_confirmation, :country_code, :state_code, :postcode, :first_name, :surname, campaigns_attributes:[:name, :description, :start_date, :complete_date ])
end
end
And the form I am using
<%= form_for #customer do |f| %>
<%= render 'shared/customer_error_messages' %>
<%= f.fields_for :campaign do |builder| %>
<%= builder.label :name, "Campaign Name" %></br>
<%= builder.text_field :name %>
<% end %>
<div class="actions">
<%= f.submit class: "btn btn-large btn-primary"%>
</div>
<% end %>
And the console output (with error, is as follows)
Started PATCH "/customer/customers/38" for 127.0.0.1 at 2014-05-26 23:13:10 +1000
Processing by Customer::CustomersController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"9ChLYna0/VfZSMJOa2yGgvWTT61XkjoYwlVup/Pbbeg=", "customer_customer"=>{"campaign"=>{"name"=>"My new campaign"}}, "commit"=>"Update Customer", "id"=>"38"}
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."remember_token" = '0e8b4ba6dc957e76948fc22bae57673404deb9fe' LIMIT 1
User Load (0.3ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", "38"]]
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT 1 [["id", "38"]]
Unpermitted parameters: campaign
If this form performs adding only one campaign i guess you should handly add this exect campaign to customer. If you update all campaign model in such way, you should have all your campaigns in array. So it implies another controller action for this.
Another way: to check if params contains field campain and then just add it to customer object:
def update
session[:return_to] ||= request.referer
#customer = User.find(params[:id])
if new_campaign_customer_params.has_key? :campaign
#customer.campaigns << Campaign.new(new_campaign_customer_params)
if #customer.save
flash[:success] = "Profile updated"
redirect_to #customer
else
flash[:notice] = "Invalid"
# you should rerender your view here, set it path
render 'your_view'
end
else
if #customer.update_attributes(customer_params)
flash[:success] = "Profile updated"
redirect_to #customer
else
flash[:notice] = "Invalid"
redirect_to session.delete(:return_to)
end
end
end
def new_campaign_customer_params
params.require('customer_customer').permit(campaign_attributes:[:name, :description, :start_date, :complete_date ])
end
Check this out, not sure it works.
Or maybe it would work how to suggests in comment: change campaigns_attributes => campaign_attributes in customer_params method, and f.fields_for :campaigns in form (belongs to #Nikita Chernov)
I've searched a lot and the common cause of this problem is attr_ascessible :model_attributes not being declared but I can't seem to get it working.
Looking at the Log below :referee, and :ticket_order values are in the params hash but then are inserted as null in the table. Foreign keys for user_id and event_id are saved in a new record without any errors. The warning about mass assignment led me to the attr_ascessible declaration, tried different variations of it without luck. I'm using devise.
Development log
Started POST "/events/1" for 127.0.0.1 at 2011-07-14 17:38:16 +0100
Processing by EventsController#update as HTML
Parameters: {"utf8"=>"✓", "authenticity_token"=>"ddddddjTdnaLKQgZncSDGYt63JA=", "event"=>{"relationship"=>{"event_id"=>"1", "referee"=>"9", "ticket_order"=>"1"}}, "commit"=>"Confirm Purchase", "id"=>"1"}
User Load (20.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = 8 LIMIT 1
Event Load (13.1ms) SELECT "events".* FROM "events" WHERE "events"."id" = 1 LIMIT 1
AREL (18.7ms) INSERT INTO "relationships" ("user_id", "event_id", "created_at", "updated_at", "referee", "ticket_order") VALUES (8, 1, '2011-07-14 16:38:16.963351', '2011-07-14 16:38:16.963351', NULL, NULL)
WARNING: Can't mass-assign protected attributes: relationship
[paperclip] Saving attachments.
Redirected to http://localhost:3000/events/1
Completed 302 Found in 588ms
Events
class Event < ActiveRecord::Base
attr_accessible :artist, :venue, :show_info, :date, :doors, :ticket_issue, :ticket_price,
:travel_cost, :accomodation_cost, :hire_cost, :image, :avatar_url, :relationships_attributes, :referee, :ticket_order
has_many :relationships
has_many :users, :through => :relationships
accepts_nested_attributes_for :relationships
Events Controller
def new
#event = Event.new
#users = Relationships.find(:all)
relationship = #event.relationships.build()
end
def create
#event = Event.new(params[:event])
current_user.attend!(#event)
if #event.save
redirect_to #event, :notice => "Successfully created event."
else
render :action => 'new'
end
end
User.rb
def attending?(event)
relationships.find_by_event_id(event)
end
Relationship.rb
class Relationship < ActiveRecord::Base
belongs_to :user
belongs_to :event
attr_accessible :event_id
form view
<%= semantic_form_for #event do |form| %>
<%= form.semantic_fields_for :relationship do |builder| %>
<%= builder.hidden_field :event_id, :value => #event.id %>
<%= builder.inputs do %>
<%= builder.input :referee, :as => :select, :collection => #event.users.all %>
<%= builder.input :ticket_order, :as => :number %>
<% end %>
<% end %>
Yes it must be this, replace:
form.semantic_fields_for :relationship
with:
form.semantic_fields_for :relationships