Custom validation, error message can't display - ruby-on-rails

I have an order form where my users are suposed to validate the conditions of sales to proceed to payment.
If the user hasn't checked the box, I want him to be warned to do so..
I just don't manage to do that...
Update (add the shopping cart)
the order is initialzed in a shooping cart:
class ShoppingCart
delegate :sub_total, to: :order
def initialize(token:)
#token = token
end
def order
#order ||= Order.find_or_initialize_by(token: #token, status: 0) do |order|
order.sub_total = 0
end
end
end
the form in orders/new.html.erb
<%= simple_form_for #order, url: clients_checkout_path do |f| %>
<div class="<%= 'error_message' if #order.errors.full_messages.any? %> ">
<% if #order.errors.any? %>
<% #order.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
<% end %>
</div>
<%= f.hidden_field :user_id %>
<%= f.input :cgos_accepted, as: :boolean, checked_value: true, unchecked_value: false %>
<%= f.submit, class: "btn btn-main btn-block" %>
<% end %>
here is the controller:
class Clients::OrdersController < Clients::ApplicationController
def index
#orders = Order.all
#orders = #orders.filter_by_status(params[:status]) if params[:status]
end
def show
#order = Order.find(params[:id])
end
def new
#order = current_cart.order
#billing_address = BillingAddress.new
end
def create
#order = current_cart.order
#order.update_sub_total!
#order.update_total!
if #order.update_attributes!(order_params.merge(user_id: current_user.id))
redirect_to new_clients_order_payment_path(#order)
end
end
private
def order_params
params.require(:order).permit(:status, :user_id, :token , :sub_total, :cgos_accepted)
end
end
in my order model I added a validation:
class Order < ApplicationRecord
validate :accept_cgos, on: :update
#[...]
private
def accept_cgos
if self.cgos_accepted == false
errors.add(:base, :must_accept_cgos)
end
end
end
my yml file is:
en:
activerecord:
errors:
models:
order:
attributes:
base:
must_accept_cgos: Please accept the general condition of sales
I was expecting the warning message to be displayed in the div but it is opening Better Error and says:
ActiveRecord::RecordInvalid at /clients/cart/checkout
Validation failed: Please accept the general condition of sales
So it's kinda working but why isn't it displaying in my error div?

update_attributes!
You are using the bang-version of update_attributes, update_attributes!. Just like save! and create! it will throw an exception when saving is unsuccessful.
If you remove the exclamation mark in that method call, update_attributes will return false when being unable to save, instead.
Rendering the error
The other part that is missing is rendering the new template in case of a validation error. If update_attributes returns true, a redirect will take place, but if it returns false, rails will attempt to render create.html.erb by default. But the new template can be also used in this case but rails has to be told to do so.
Code changes
The changed implementation might then look like this.
if #order.update_attributes(order_params.merge(user_id: current_user.id)) # no bang
redirect_to new_clients_order_payment_path(#order)
else
#billing_address = BillingAddress.new # because the #new template requires it.
render :new
end

why isn't it displaying in my error div?
Because of your create action, you're doing the redirect_to, the redirect_to flush the order instance and all your errors from object is vanished, try to add render :new instead.
# for example
def create
if #order.updated
redirect_to path
else
render :new
end
end

Related

param is missing or the value is empty: activity Rails 4

When i try to create an entry in my application i get
ActionController::ParameterMissing in TodosController#create
param is missing or the value is empty: activity
Expected Source:
def todo_params
params.require(:activity).permit(:name, :due)
end
Here is my request:
{"utf8"=>"✓",
"authenticity_token"=>"mR11T50dN8tm/q+S0fNd+mYYZBR6Xq4it4ujtEOmt/7RmA4TjzTKD//XmFU+UumpbmKcaj9DtG6noHOD2pwk2w==",
"todo"=>{"name"=>"Ezekiel",
"activity"=>"test....an 8th time"},
"commit"=>"Create Todo"}
I am not sure why this is not working as the activity parameter is clearly passed through as it is in the request
Here is the controller:
def create
#todo = Todo.create(todo_params)
end
def todo_params
params.require(:activity).permit(:name, :due)
end
And finally my View:
<div class="reveal" id="ezekielTask" data-reveal>
<%= simple_form_for #todo do |f| %>
<%= f.input :name, :as => :hidden, :input_html => {:value => "Ezekiel"} %>
<%= f.input :activity %>
<%= f.button :submit %>
<% end %>
</div>
This part of my rails application is a todo list, it displays fine, i can add entries through the console, this way just will not work and i have no idea why. it is passing the data correctly just not accepting it.
Controller
def create
#todo = Todo.create(todo_params)
respond_to do |format|
if #todo.save
format.html { redirect_to location_path, notice: 'Todo was successfully created.' }
else
format.html { render :new }
}
end
end
end
Params
def todo_params
params.require(:todo).permit(:name, :activity)
end
You need to change the strong params method to require todo and then accept the :name and :activity params
def todo_params
params.require(:todo).permit(:name, :activity)
end
The reason for that is
{"utf8"=>"✓",
"authenticity_token"=>"mR11T50dN8tm/q+S0fNd+mYYZBR6Xq4it4ujtEOmt/7RmA4TjzTKD//XmFU+UumpbmKcaj9DtG6noHOD2pwk2w==",
"todo"=>{"name"=>"Ezekiel",
"activity"=>"test....an 8th time"},
"commit"=>"Create Todo"}
If you see in the params you are receiving name and activity inside todo
In the require parameter normaly we use the name of the model (todo) and inside permit we use the attributes of that model (:name, :activity)
def todo_params
params.require(:todo).permit(:name, :activity)
end

Rails undefined method `model_name' for nil:NilClass nested routes

I'm a newbie on rails. My rails version is 4.2.6, simple_form_for version is 3.2.1. I want to redirect to the new page after I validated the params is false. And I get the error message.
undefined method `model_name' for nil:NilClass <%= simple_form_for
[:account, #station] do |f| %>
...
let me show you. This is my routes.rb
namespace :account do
resources :stations, expect: [:index]
end
stations_controller.rb
def new
#lines = Line.where(line_status: 1)
#station = Station.new
end
def create
unless check_post_params post_params
flash[:notice] = "miss params"
render action: :new # not work
# render :new # not work
# render 'new' # not work
# it work for me, but can I use render?
#redirect_to '/account/stations/new'
else
#station = Station.new(post_params)
if #station.save
redirect_to '/account/lines'
else
flash[:notice] = 'failed'
redirect_to 'account/stations/new'
end
end
end
private
def post_params
params.require(:station).permit(...)
end
def check_post_params(post_params)
if ...
return false
else
return true
end
end
_form.html.erb
<%= simple_form_for [:account, #station] do |f| %>
<div class="form-group">
<%= f.label :line_id, t('line.line_name') %>
<%= f.input_field :line_id,
... %>
<% end %>
The original I open the URL is http://localhost:3000/acoount/stations/new. After I click the submit, and I want to render :new, it will redirect to original URL, but I found the URL become http://localhost:3000/account/stations. The URL's /new is disappear. I have guessed it's about my routes.rb config.
And I tried to use rake routes to check my routes.
$ rake routes
...
POST /account/stations(.:format) account/stations#create {:expect=>:index]}
new_account_station GET /account/stations/new(.:format) account/stations#new {:expect=>[:index]}
...
I tried to use render account_stations_new_path and render new_account_station_path, but still not work for me. Thanks your help.
Edit
I know how to modify my code. I should validate my params on my model. And then I should use redirect_to not use render. I will update my code later.
Edit 2
I modified my model.
class Station < ActiveRecord::Base
belongs_to :line
has_many :spot_stations
validates :name, presence: true, uniqueness: { case_sensitive: false }
validates :line_id, presence: true
validates ....
end
And I modified my controller.
def new
# Because this #lines did't exist when I render 'new' on the create method. So I deleted it. And I create a method on the helper.
# #lines = Line.where(line_status: 1)
#station = Station.new
end
def create
#station = Station.new(post_params)
if #station.save
redirect_to '/account/lines'
else
flash[:notice] = 'failed'
render 'new'
end
end
I add a method for getting lines data
module Account::StationsHelper
def get_lines
#lines = Line.where(line_status: 1)
end
end
I will use it on my _form.html.erb
<%= simple_form_for [:account, #station] do |f| %>
<div class="form-group">
<%= f.label :line_id, t('line.line_name') %>
<%= f.input_field :line_id,
collection: get_lines,
label_method: :line_name,
input_html: { class: 'form-control' },
value_method: :id,
prompt: t('common.please_select') %>
</div>
...
<% end %>
This work great for me. Thanks your help.
try this:
if #station.save
redirect_to account_stations
else
flash[:notice] = 'failed'
render 'new'
end
as argument for render method you must pass action name, not url.

Edit page to display all category names with edit inputs on each

I have categories nested inside of guides. I'm building an app to learn rails better and I'm trying to make a page that will display all categories that belong to a guide and have edit inputs under them and a save button next to it so the user can edit the names of the categories they want to change.
Bit stuck on how exactly how get this done.
here is the category_item_keys controller
def edit
#guide = Guide.friendly.find(params[:guide_id])
#category = Category.friendly.find(params[:category_id])
#key = #category.category_item_keys
end
def update
#guide = Guide.friendly.find(params[:guide_id])
#category = Category.friendly.find(params[:category_id])
#key = #category.category_item_keys.friendly.find(key_params) # no idea what to make this equal because it isn't one set key being edited on the page
if #key = #category.category_item_keys.update_attributes(key_params)
flash[:success] = "Key updated"
redirect_to #guide
else
render 'edit'
end
end
private
def key_params
params.require(:category_item_key).permit(:key, :slug)
end
routes
match '/guides/:guide_id/:category_id/keys/edit' => 'category_item_keys#edit', :via => :get
match '/guides/:guide_id/:category_id/keys/' => 'category_item_keys#update', :via => :post, as: :category_item_keys_update
edit.html.erb
<ul>
<% #key.each do |key| %>
<li><%= key.key #just shows key name %><br>
<%= form_for([#category, #keys], url: category_item_keys_create_path) do |f| %>
<%= render 'shared/error_messages', object: f.object %>
<%= f.label :key, "Key name" %>
<%= f.text_field :key %>
<%= f.submit "Save" %>
<% end %>
</li>
<% end %>
</ul>
This just gives me an error of:
undefined method `to_key' for #<CategoryItemKey::ActiveRecord_Associations_CollectionProxy:0x007fe20a86b480>
Later I plan on using an in-place editor gem but i would like to learn how this can be done fist.
EDIT:
Fixed the error ( changed form_for([#category, #keys] to form_for([#category, key] and turns out this way works for displaying and allowing all categories to be edited... to an extent.
I get another error when i submit a form
undefined method 'update_attribute'
EDIT 2
slowly getting there. I Changed the update #key variable to #key = #category.category_item_keys.all to fix the error. But this line is now giving me problems
if #key = #category.category_item_keys.update_attributes(key_params)'
THIRD EDIT
ERROR
Couldn't find CategoryItemKey without an ID
on line
#key = #category.category_item_keys.find params[:id]
paramaters:
{"utf8"=>"✓", "_method"=>"patch", "authenticity_token"=>"egj/OebdSbxxaoaTkr46WVIOIIu4Ezijzu45kqxLT0krjFWHqi67SRJDSgV7bcL6SeoGpUSYsrolspylCXBu9g==",
"category_item_key"=>{"name"=>"def1111"},
"commit"=>"Save",
"guide_id"=>"dbz",
"category_id"=>"characters"}
Here's how to clean up the code:
#config/routes.rb
resources :guides do
resources :categories, path: "" do
resources :category_item_keys, path: "keys", only: [:update] do
get :edit, on: :collection #-> url.com/guides/:guide_id/:category_id/keys/edit
end
end
end
#app/controllers/keys_controller.rb
class KeysController < ApplicationController
def edit
#guide = Guide.find params[:guide_id]
#category = Category.find params[:category_id]
#keys = #category.category_item_keys
end
def update
#guide = Guide.find params[:guide_id]
#category = Category.find params[:category_id]
#key = #category.category_item_keys.find params[:id]
if #key.update key_params
redirect_to #guide, success: "Key Updated"
else
render 'edit'
end
end
private
def key_params
params.require(:category_item_key).permit(:key)
end
end
#app/views/keys/edit.html.erb
<% #keys.each do |key| %>
<%= form_for [#guide, #category, key] do |f| %>
<%= f.text_field :key %>
<%= f.submit %>
<% end %>
<% end %>
If you wanted to use an in-place editor gem, I'd recommend looking at X-Editable, as we've applied it here (its only a demo app, just sign up for free and go to profile):
Looks like you are trying to do update_attributes on a collection instead of an object. Try to first fetch the key object
#key = #category.category_item_keys.friendly.find(params[:id])
and then try to update its attributes
if #key.update_attributes(key_params)
...
end
use nested forms available in rails 4.

Nested sign up form does not display the error messages for the nested object

Organization and User have a many-to-many relationship through Relationship. There's a joined signup form. The sign up form works in that valid information is saved while if there's invalid information it rolls back everything.
The problem is that the form does not display the error messages for the nested User object. Errors for Organization are displayed, the form correctly re-renders if there are errors for User, but the errors for User are not displayed.
Why are the errors when submitting invalid information for users not displayed? Any help is appreciated.
The signup form/view:
<%= form_for #organization, url: next_url do |f| %>
<%= render partial: 'shared/error_messages', locals: { object: f.object, nested_models: f.object.users } %>
... fields for organization...
<%= f.fields_for :users do |p| %>
...fields for users...
<% end %>
<%= f.submit "Register" %>
<% end %>
The shared error messages partial:
<% if object.errors.any? %>
<div id="error_explanation">
<div class="alert alert-danger">
The form contains <%= pluralize(object.errors.count, "error") %>.
</div>
<ul>
<% object.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% if defined?(nested_models) && nested_models.any? %>
<div id="error_explanation">
<ul>
<% nested_models.each do |nested_model| %>
<% if nested_model.errors.any? %>
<ul>
<% nested_model.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
<% end %>
<% end %>
</ul>
</div>
<% end %>
The controller method:
def new
#organization = Organization.new
#user = #organization.users.build
end
def create
#organization = Organization.new(new_params.except(:users_attributes))
#organization.transaction do
if #organization.valid?
#organization.save
begin
#user = #organization.users.create!(users_attributes)
#relationship = #organization.relationships.where(user: #user).first
#relationship.update_attributes!(member: true, moderator: true)
rescue
raise ActiveRecord::Rollback
end
end
end
if #organization.persisted?
if #organization.relationships.where('member = ? ', true).any?
#organization.users.where('member = ? ', true).each do |single_user|
single_user.send_activation_email
end
end
flash[:success] = "A confirmation email is sent."
redirect_to root_url
else
#user = #organization.users.build(users_attributes) if #organization.users.blank?
render :new
end
end
The Organization model:
has_many :relationships, dependent: :destroy
has_many :users, through: :relationships, inverse_of: :organizations
accepts_nested_attributes_for :users, :reject_if => :all_blank, :allow_destroy => true
validates_associated :users
The Relationship model:
belongs_to :organization
belongs_to :user
The User model:
has_many :relationships, dependent: :destroy
has_many :organizations, through: :relationships, inverse_of: :users
Update: If I add an additional line to def create as below, it seems to work, i.e., then it does display the error messages. However, then it for some reason doesn't save when valid information is submitted. Any ideas how to deal with that?
def create
#organization = Organization.new(new_params.except(:users_attributes))
#user = #organization.users.new(users_attributes)
#organization.transaction do
...
Maybe try this:
<%= render partial: 'shared/error_messages',
locals: { object: f.object, nested_models: [ #user ] } %>
I guess the call to #organization.users.blank? doesn't work in the way you expected it to do, as the user is not correctly created, because #create! threw an exeption. Rails probably does a check on the database, to see if there are any users now, and thinks there is still nothing in there. So your #organization.users.build(users_attributes) gets called, but this doesn't trigger validation.
In general I would also recommend you the use of a form object (like in the other answer), when creating complex forms, as this clarifies things like that and makes the view more clean.
This is classic use case for form objects. It is convenient from many perpectives (testing, maintainance ...).
For example:
class Forms::Registration
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
def persisted?
false
end
def initialize(attributes = {})
%w(name other_attributes).each do |attribute|
send("#{attribute}=", attributes[attribute])
end
end
validates :name, presence: true
validate do
[user, organization].each do |object|
unless object.valid?
object.errors.each do |key, values|
errors[key] = values
end
end
end
end
def user
#user ||= User.new
end
def organization
#organization ||= Organization.new
end
def save
return false unless valid?
if create_objects
# after saving business logic
else
false
end
end
private
def create_objects
ActiveRecord::Base.transaction do
user.save!
organization.save!
end
rescue
false
end
end
the controller:
class RegistrationsController < ApplicationController
def new
#registration = Forms::Registration.new
end
def create
#registration = Forms::Registration.new params[:registration]
if #registration.save
redirect_to root_path
else
render action: :new
end
end
end
and the view in HAML:
= form_for #registration, url: registrations_path, as: :registration do |f|
= f.error_messages
= f.label :name
= f.text_field :name
= f.submit
It is worth to read more about form objects.
Nested attributes bit me SOOO hard every time I decided it's a good time to use them, and I see you know a bit of what I'm talking about.
Here's a suggestion of a different approach, use a form object instead of nested attributes: http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/ see under section 3. Extract Form Objects
You can extract your existing validations on the User model into a module and import that, to expand on the solution from the blog:
https://gist.github.com/bbozo/50f8638787d6eb63aff4
With this approach you can make your controller code super-simple and make simple and fast unit tests of the not-so-simple logic that you implemented inside and save yourself from writing integration tests to test out all different possible scenarios.
Also, you might find out that a bunch of the validations in the user model are actually only within the concern of the signup form and that those validations will come and bite in later complex forms, especially if you're importing data from a legacy application where validations weren't so strict, or when you one day add additional validators and make half of your user records invalid for update.
I had a similar problem. everything seemed to work fine, but I was not getting any errors The solution i found is to build the comment in article#show instead of the view:
#article = Article.find(params[:id])
#comment = #article.comments.build(params[:comment])
and in your articles#show don't use #article.comments.build but #comment:
<%= form_for([#article, #comment]) do |f| %>
<%= render 'shared/error_messages', :object => f.object %>
<p><%= f.submit %></p>
<% end %>
make sure you build the comment in your comment#create as well (you really have no choice though :P)
I think you need to pass f.object instead of #comment.
In case someone might be looking for a solution to render form errors in a form, try:
f.object.errors["account.address"].present?`
The address is the nested attribute here.

Nested controller Resources, how to do update and destroy?

followed a tutorial to help me create instances within a controller. In other words transactions are created on the envelope controller. Like comments on a blog post.
Everything is working perfectly, but I don't know how to edit a transaction now or destroy one. All I need is to find how to edit an existing thing. Let me show you what I have so far:
in views/envelopes/edit (the form code was copied from where you can create new transactions)
<% #envelope.transactions.each do |transaction|%>
<%= form_for [#envelope, #envelope.transactions.build] do |f| %> <!--??? NEED EDIT INSTEAD OF BUILD ???-->
<%= f.text_field :name, "value" => transaction.name %>
<%= f.text_field :cash, "value" => transaction.cash %>
<%= f.submit "Submit" %>
<% end %>
<%= link_to "Remove", root_path %> <!--??? WANT TO REMOVE TRANSACTION ???-->
<% end %>
in routes.rb
resources :envelopes do
resources :transactions
end
in transaction controller
class TransactionsController < ApplicationController
def create
#envelope = Envelope.find(params[:envelope_id])
#transaction = #envelope.transactions.build(transaction_params)#(params[:transaction])
#transaction.save
#envelope.update_attributes :cash => #envelope.cash - #transaction.cash
redirect_to edit_envelope_path(#envelope)
end
def destroy
# ???
end
def update
# ???
end
def transaction_params
params.require(:transaction).permit(:cash, :name, :envelope_id)
end
end
def update
#transaction = #envelope.transactions.find(params[:id])
if #transaction.update(transaction_params)
redirect to #envelope, notice: 'Transaction was successfully updated'
else
redirect_to #envelope, notice: 'Transaction was not updated'
end
end
def destroy
#transaction.destroy
redirect_to #envelope, notice: 'Text here'
end

Resources