I am creating a Tumblr alternative to learn how use Rails. I am at the authentication part, and I decided to do it from scratch. I want to allow users to log in using either their username or their email. The user logs in via the Sessions controller but I need to verify if the login is either a valid username or a valid email. So I need to validate data in the Sessions controller using the User model.
I found this answer on SO: How do I validate a non-model form in Rails 3? but it will force me to duplicate the validations. Is that the only way to do it, or is there another way that is cleaner?
The best option I can imagine is creating a module and then including it at your User model and at the object you're going to use for the form:
module AuthenticationValidation
def self.included( base )
base.validates :username, :presence => true
base.validates :email, :presence => true
# add your other validations here
end
end
And then include this module at your models:
class User < ActiveRecord::Base
include AuthenticationValidation
end
class LoginForm
include ActiveModel::Validations
include ActiveModel::Conversion
include AuthenticationValidation
attr_accessor :username, :email
end
And then you have avoided repeating the validation itself.
Related
I have a session controller which manages login/logout actions.These are not associated with any model. I want to use the validates helper methods with this session controller so that I can use, :maximum, :minimum, etc. conditions for the username and password. But if there is no model, how do I use these validation methods. Also, if I use these helpers it is easy to integrate with my simple_form login page for the error message, similar to which I have done for my user registration page.
If above helper methods are not possible to be used, what alternative I have which is easy to interface with my simple_form?
Validations can be used without creating a model, i.e. a class inheriting from ActiveRecord::Base. You can have a class that includes ActiveModel::Validations and use its methods. For example:
class Session
include ActiveModel::Validations
attr_accessor :username, :password
def initialize(user, pass)
#username = user
#password = pass
end
validates_length_of :username, minimum: 5
validates_length_of :password, minimum: 7
end
And you use like that:
session = Session.new("name", "secret")
session.valid? ## => false
I have a set of custom fields attached to a devise model called Entrant.
I have two forms, one for registration form (three fields) and one which sits in the account area (12 fields). Most of the custom fields area required but only within the form the sits in the account area.
How do I achieve this?
I am using rails 4.2 and ruby 2.1
You can simply specify validations on actions, that is:
validates :name, presence: true, on: :create # which won't validate presence of name on update action
If you ask where to put your custom fields, then generate devise's views and update corresponding ones with these fields.
There are several ways! You could do conditional validations, for instance
class Entrant < ActiveRecord::Base
validate :foo, if: :account_area?
def account_area?
!new_record? # Assumes that Entrant that has already been saved
# is in the account area
end
end
However, it sounds like your needs are advanced enough that you should consider making a Form Object
A form object is an object that accepts parameters, performs validations on that data, then saves a model instance.
class AccountForm
include ActiveModel::Model
include Virtus # Provides AR like attribute functionality and mass assignment
def initialize(entrant)
#entrant = entrant
end
attribute :foo, String
validates :foo, presence: true # This is only used on the account page, so no need to mess with conditional logic
def save
if valid?
persist!
true
else
false
end
end
def persist!
#entrant.update_attributes(foo: self.foo)
end
end
This is just a great example of how non-rails-specific object oriented programming can make your life easier and your app more maintainable. Make a class like above, stick it in app/forms and restart your server. Then in your controller, you'll just pass it the model
class EntrantController < ApplicationController
def update
#form = Form.new(Entrant.find(params[:id]))
#form.attributes = params[:entrant]
if #form.save
redirect_to some_path
else
render "edit"
end
end
end
By default devise only asks for a combination of email/password, you can add other fields by adding a sanitizer (see there -> Devise how to add a addtional field to the create User form?).
If you want to add other fileds to validate, you should create a secondary Entrant controller and add a specific callback to your model.
Typically:
after_update :validate_entrant_form, if: :property_changed?
I hope this will help you.
validates :name, presence: true, if: :condition_holds?
def condition_holds?
# some code here that evaluates to a boolean
end
Maybe this way help you.
Add attribute in devise model : say attr_accessor :validate_certain. In your controller action, devise model instance say #user have to update like this #user.validate_certain = true. and change your appropriate validation conditions in devise model
validates :name, presence: true, if: :validate_certain_changed?
def validate_certain_changed?
validate_certain.present?
end
When I have to do something like this I like to think of it as it validates if something in in the field but you can also take a nil value
Entrant.validates_presence_of(:foo, :allow_nil => true)
I also have this concern when using devise on customer with forms on separate pages updating different set of customer fields
I believe most of the solution works but I was looking for the simplest, easiest and foolproof way to implement the solution
Thus came this.
validates :phone, :country, :postal_code, :street_address, presence: true, allow_nil: true
The allow_nil: true instruct the model to validate the fields ONLY if it exists on the submitted form. If you want more protection, you can use extra para like :on => :update
I have a registration form that has fields from 3 models.
I know you can somehow create a form based on multiple models, but I was thinking of just creating another model for this form and then if validation passes just create the other models by myself.
How can I create a table-less model, I am using Rails 4.1 (latest).
Is this a common practise or should I just learn how to create a form that has 3 models in it?
Rails casts has a sample like:
class Message
include ActiveModel::Validations
include ActiveModel::Conversion
extend ActiveModel::Naming
attr_accessor :name, :email, :content
validates_presence_of :name
validates_format_of :email, :with => /^[-a-z0-9_+\.]+\#([-a-z0-9]+\.)+[a-z0-9]{2,4}$/i
validates_length_of :content, :maximum => 500
def initialize(attributes = {})
attributes.each do |name, value|
send("#{name}=", value)
end
end
def persisted?
false
end
end
So what would be a clean way of using this approach? This is what I am thinking so far:
write tests on this tabless model to ensure the validations are how I want them.
when the form posts, verify .valid? is true.
if it is valid, then use the fields to initialize my other models and verify they are all valid.
save all the models
write tests to make sure the tabless-model can correctly initialize the other models so they can be saved correctly.
Any best practises or comments on this approach?
you could look into reform.
using ActiveModel is even easier, make sure to pass the ActiveModel::Lint tests.
other than that, what you described is the way to go.
I'm writing a library that will require programmatically copying validations from one model to another, but I'm stumped on how to pull this off.
I have a model that is an ActiveModel::Model with some validation:
class User < ActiveRecord::Base
validates :name, presence: true
end
And another model that I'd like to have the same validations:
class UserForm
include ActiveModel::Model
attr_accessor :name
end
Now I'd like to give UserForm the same validations as User, and without modifying User. Copying the validators over doesn't work, because ActiveModel::Validations hooks into callbacks during the validation check:
UserForm._validators = User._validators
UserForm.new.valid?
# => true # We wanted to see `false` here, but no validations
# are actually running because the :validate callback
# is empty.
Unfortunately, there doesn't seem to be an easy way that I can see to programmatically give one model another's validation callbacks and still have it work. I think my best bet is if I can ask Rails to regenerate the validation callbacks based on the validators that are present at a given moment in time.
Is that possible? If not, is there a better way to do this?
Checking into the code of activerecord/lib/active_record/validations/presence.rb reveals how this can be achieved:
# File activerecord/lib/active_record/validations/presence.rb, line 60
def validates_presence_of(*attr_names)
validates_with PresenceValidator, _merge_attributes(attr_names)
end
So I guess I would try to hook into validates_with with an alias_method
alias_method :orig_validates_with :validates_with
Now you have a chance to get ahold of the values passed, so you can store them somewhere and retrieve them when you need to recreate the validation on UserForm
alias_method :orig_validates_with, :validates_with
def validates_with(*args)
# save the stuff you need, so you can recreate this method call on UserForm
orig_validates_with(*args)
end
Then you should be able to just call UserForm.validates_with(*saved_attrs). Sorry this is not something you can just copy/paste, but this should get you started. HTH
I have a form that allows the user to send a message to an email, and I want to add validation to it. I do not have a model for this, only a controller. How should I do this in Rails?
I was considering doing the validation in the controller, and displaying the errors to the user using the flash object. Is there a better way of doing this?
The best approach would be to wrap up your pseudo-model in a class, and add the validations there. The Rails way states you shouldn't put model behavior on the controllers, the only validations there should be the ones that go with the request itself (authentication, authorization, etc.)
In Rails 2.3+, you can include ActiveRecord::Validations, with the little drawback that you have to define some methods the ActiveRecord layer expects. See this post for a deeper explanation. Code below adapted from that post:
require 'active_record/validations'
class Email
attr_accessor :name, :email
attr_accessor :errors
def initialize(*args)
# Create an Errors object, which is required by validations and to use some view methods.
#errors = ActiveRecord::Errors.new(self)
end
# Required method stubs
def save
end
def save!
end
def new_record?
false
end
def update_attribute
end
# Mix in that validation goodness!
include ActiveRecord::Validations
# Validations! =)
validates_presence_of :name
validates_format_of :email, :with => SOME_EMAIL_REGEXP
end
In Rails3, you have those sexy validations at your disposal :)
For Rails 3+, you should use ActiveModel::Validations to add Rails-style validations to a regular Ruby object.
From the docs:
Active Model Validations
Provides a full validation framework to your objects.
A minimal implementation could be:
class Person
include ActiveModel::Validations
attr_accessor :first_name, :last_name
validates_each :first_name, :last_name do |record, attr, value|
record.errors.add attr, 'starts with z.' if value.to_s[0] == ?z
end
end
Which provides you with the full standard validation stack that you
know from Active Record:
person = Person.new
person.valid? # => true
person.invalid? # => false
person.first_name = 'zoolander'
person.valid? # => false
person.invalid? # => true
person.errors.messages # => {first_name:["starts with z."]}
Note that ActiveModel::Validations automatically adds an errors method
to your instances initialized with a new ActiveModel::Errors object,
so there is no need for you to do this manually.