Why is my rails simple validation not working - ruby-on-rails

I have a very basic rails app. I am playing around with validation.
Controller
class PagesController < ApplicationController
def new
#user = User.new
end
def edit
#user = User.new(:state => params[:state], :country => params[:country])
#user.save
end
end
Model
class User < ActiveRecord::Base
validates_presence_of :country
validates_presence_of :state
end
Views/pages/edit.html.erb
<% form_for :user, #user, :url => { :action => "edit" } do |f| %>
<%= f.text_field :country %>
<%= f.text_field :state %>
<%= submit_tag 'Create' %>
<% end %>
All I want to do is click Create when I have not entered anything and then have a validation come up and list the required fields. I've read some tutorials and they make it so simple. Why can't I get this to work? what am i doing wrong? When i create a scaffold then it works ok but that generates a scaffold.css in public/stylesheets. W/out scaffold right now i have no stylesheet in the public folder.

you're sending the form to the "edit" action, which doesn't do any processing. You need it to go to the "create" action, which should look something like this:
def create
#user = User.new(params[:user])
if #user.save
flash[:notice] = 'Your user was successfully created.'
redirect_to users_path
else
render :action => 'edit'
end
end
Your form_for line can be short and sweet. Also, you need to call error_messages to get the auto-generated list of errors:
<% form_for #user do |f| %>
<%= f.error_messages %>
...other fields go here...
<% end %>

See Rails conditional validation: if: doesn't working
It seems like you think validates ... if: works differently as it actually does. This line
validates :to_id, presence: true, if: :from_different_to?
translates to validate that the to_id is present if the from_different_to method returns true. When from_different_to evaluates to false then do not validate.

Related

Getting my Ruby on Rails page to save the user's email without using devise

I am building a one page website where visitors will simply be able to submit their email address. The only goal in the database is to get an email (no name, etc). There is only one page visible at first, which is the homepage. If the user submits an email already in use, it sends the user to an error page. If the email is not in use, it sends the user to a success page.
I have asked a question about this previously, and after a lot of comments and trial and error, it appeared that it worked and then it stopped working. When I do Rails C, there is only one user in the system and that user doesnt have an email...
Here is what my user migration looks like :
class CreateUsers < ActiveRecord::Migration[5.2]
def change
create_table :users do |t|
t.string :email
t.timestamps
end
add_index :users, :email, unique: true
end
end
Here is what my user model looks like:
class User < ApplicationRecord
end
Here is what users/new.html.erb looks like:
<%= form_for #user, as: :post, url: users_path do |f| %>
<div class="wrapper">
<%= f.email_field :email , id: "search", class:"search input" %> <br />
<%= f.submit "yep", class: "submit input" %>
</div>
<% end %>
Here is my user controller:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(params[:email])
if #user.save
redirect_to '/users/success'
else
redirect_to '/users/error'
end
end
def show
end
end
Here are my routes:
Rails.application.routes.draw do
root "users#new"
resources :users
end
When i run the code, it renders the homepage but when i click on submit, it sends me on a page called show.html.erb with http://localhost:3000/users/error on my brownser. No users are being saved in the console.
EDIT:
My model is
class User < ApplicationRecord
validates_uniqueness_of :email
end
It is still not working....
change new.html.erb as
<%= form_with(model: #user, local: true) do |f| %>
<div class="wrapper">
<%= f.email_field :email , id: "search", class:"search input" %> <br />
<%= f.submit "yep", class: "submit input" %>
</div>
your controller will be
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to user_path(#user), notice: "yeh!!!!"
else
redirect_to new_user_path, notice: "email already registered"
end
end
def show
#user = User.find(params[:id])
end
private
def user_params
params.require(:user).permit(:email)
end
end
add
<p id="notice"><%= notice %></p> to your application.html.erb in layouts
rest as your question
There are a couple things wrong here.
You're so close, but you're misusing the as: attribute of form_for. Perhaps you think that will send as a POST request, but instead that is actually wrapping your form params in an object called "post". I saw this in the comments on another thread.
Remove the as: attribute and the helper will again wrap your params in the user object. While we're at it, you should also be able to remove the url: attribute as well since Rails form helpers are smart enough to infer that this is a new resourceful record and output the create URL as well as the POST action accordingly.
You need your controller to expect a whole "user" object instead of just checking for the email param. ALSO, assuming you're on Rails 4 or higher, you need to permit the email attribute to be mass-assigned on your User object. See the code.
def create
#user = User.new(params.require(:user).permit(:email)) # Not params[:email]
if #user.save
redirect_to '/users/success'
else
redirect_to '/users/error'
end
end
Also be careful about duplicate emails with different cases. The default in Rails is case-sensitive validation which means "JIM#gmail.com" would not trigger a validation error against "jim#gmail.com". You can fix this with.
class User < ApplicationRecord
validates :email, uniqueness: { case_sensitive: false }
end
BONUS!
Nowadays, it's better to move over to form_with (instead of form_for). It's on its way to becoming the new Rails standard and also makes a few of these things easier. The one point you'll want to keep in mind is that with form_with (and general Rails assumptions), forms are remote by default. So if you want to trigger a full page submit/refresh, add local: true to your form_with helper.
<%= form_with model: #user, local: true do |f| %>
<div class="wrapper">
<%= f.email_field :email , id: "search", class:"search input" %> <br />
<%= f.submit "yep", class: "submit input" %>
</div>
<% end %>
As you are using resources in routes so def show is called when route is /users/:id. That's why its calling show.html.erb file.
When you try to validate an email, then in model write the validation for it
class User < ApplicationRecord
validates_uniqueness_of :email
end
Hope this helps.
Try to add validates_uniqueness_of in your model
class User < ApplicationRecord::Base
attr_accessor :email
validates_uniqueness_of :email
end
And
def show
#user = User.find(email: params[:email])
end
And if you wanna check all
def show
#user = User.all
end
Please try this.
I hope that helpful

Can't seem able to show validation errors on rails

I've been reading rails and ruby tutorials and just got started with my first project. Everything is going well but I'm having a problem showing validation errors for new users. This is my controller code:
class UsersController < ApplicationController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
redirect_to ''
else
redirect_to '/signup'
end
end
private
def user_params
params.require(:user).permit(:name, :lastname, :nickname, :email, :password)
end
end
And this is my form. I'm omitting all inputs but the nickname one, because I'm testing the validation with it.
<%= form_for(#user) do |f| %>
<div class='form-group'>
<%= f.text_field :nickname, :placeholder => "User name", :class => "form-control", :required => true, :minlength => "2", :maxlength => "24" %>
<div class="text-danger">
<%= #user.errors[:nickname][0] %>
</div>
</div>
<% end %>
I just show the first related error to see if it's working, and avoid any Ifs that would make me doubt if it works or not. The validation on the class is:
class User < ApplicationRecord
has_many :books
has_secure_password
validates :nickname, uniqueness: true
end
When I create a new user by console and try to save it with the same nickname as another it wont save, so the validation works fine. But no error is shown in the view, it just goes back to the signup form and that's all. I'm using ruby 2.2.6 and rails 5.0.1. If I'm missing anything let me know.
Instead of redirect render new action template:
def create
#user = User.new(user_params)
if #user.save
redirect_to ''
else
render 'new'
end
end
When you do redirect you loose #user object that contains validation errors. That is why they are not visible in view

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.

Using form_for with "becomes" loses child association

When building out a form that handles STI, if I use becomes to transform the object to its parent class, I lose the ability to use nested fields with it.
I have two models
class Login < ActiveRecord::Base
belongs_to :user
end
class User < ActiveRecord::Base
has_one :login
accepts_nested_attributes_for :login
end
I also have a few subclasses of User.
class Consumer < User
end
class Admin < User
end
class Agent < User
end
Initially I had problems with the routing, since Rails would assume that I wanted a route specific to the current class rather than the parent class, so I used #user.becomes(User), which is apparently the way to handle that. For the most part it works fine, however this causes #user.login to disappear.
Controller
class Admin::UsersController < AdminController
load_and_authorize_resource
before_filter :authenticate_user!
def index
render 'index'
end
def new
#user = User.new
#user.build_login
render 'new'
end
def create
#user = User.new(user_params)
if #user.save
flash[:notice] = "Account confirmation instructions sent to #{#user.login.email}"
redirect_to new_user_path
else
flash.now[:error] = #user.errors.full_messages.to_sentence
# At this point, I can confirm that #user.login still exists...
render 'new'
end
end
private
def user_params
params.require(:user).permit(
:type,
:dealership_id,
login_attributes: [
:email
])
end
end
Here's the most relevant form view bit
<%= simple_form_for(#user.becomes(User), html: {class: "user-form"}) do |f| %>
<%= f.simple_fields_for :login do |l| %>
<div class="field">
<%= l.label :email %><br />
<%= l.email_field :email %>
</div>
<% end %>
<div class="field">
<%= f.label :type %>
<%= f.select :type, options_for_select(current_user.types_can_create), include_blank: "- Select -", class: "form-control", id: "select_type" %>
</div>
<div class="actions">
<%= f.submit "Register" %>
</div>
<% end %>
The text field for :email doesn't display because #user.login is now nil. Is this expected behavior when using becomes?
Having only used becomes once before, I can only attest to my scant experience -- whenever you use it, it essentially invokes a new instance of the class.
I'm not sure as to the specifics, but the bottom line is that I would surmise that your #user.becomes(User) is overriding #user.build_login...
Returns an instance of the specified klass with the attributes of the current record.
--
In your case, I would set the path explicitly (as you're using User anyway):
<%= simple_form_for #user, url: user_path, method: :post html: {class: "user-form"} do |f| %>
In Rails 5, this can be solved with
<%= form_with scope: :user,
model: #user,
url: #user.id ? user_path(user) : users_path,
local: true do |f| %>
...
...
<% end %>
The previous code instructs the FormBuilder to fill in the fields with the #user attributes, but it also instructs it to submit to the main route (not the inherited ones) and, with scope:, it also instructs it to name the fields using the User class name, not the child class names.

Rails validations not working with habtm

for now i've got followings:
model => User (name, email)
has_and_belongs_to_many :trips
model => Trip (dest1, dest2)
has_and_belongs_to_many :users
validates :dest1, :dest2, :presence => true
model => TripsUsers (user_id, trip_id) (id => false)
belongs_to :user
belongs_to :trip
As you see from the code, trip model has validation on dest1, and dest2, but it's not showing up an errors. Controller and view defined as follow:
trips_controller.rb
def new
#user = User.find(params[:user_id])
#trip = #user.trips.build
end
def create
#user = User.find(params[:user_id])
#trip = Trip.new(params[:trip])
if #trip.save
#trip.users << #user
redirect_to user_trips_path, notice: "Success"
else
render :new
end
end
_form.html.erb
<%= simple_form_for [#user, #trip] do |f| %>
<%= f.error_notification %>
<%= f.input :dest1 %>
<%= f.input :dest2 %>
<%= f.submit "Submit" %>
<% end %>
According to the rails guide on presence validation, it can't be used with associated objects. Try to use a custom validation:
validate :destinations_presence
def destinations_presence
if dest1.nil?
errors.add(:dest1, "missing dest1")
elsif dest2.nil?
errors.add(:dest1, "missing dest2")
end
end

Resources