Rails 3 + devise - email + user creation - ruby-on-rails

I am new to Rails. I am trying to create user management + login system using Devise. All is great except two things:
1) Is there any way to remove "email" field and/or validation when signing up? When remove :validatable from model, then passwords doesn't validate too.
2) What is best way to make user management in system? Something like overriding devise sign up?
I was searching in google for answers and readed documentation, but don't found answers.
Thank you.

1) You should remove :validatable and write your own validation in User model.
2) You can create users by hand in devise:
#user = User.new(:field1 => "something", :field2 => "something else", :password => "secret", :password_confirmation => "secret")
#user.save
if created object passes validation it will be created just like with devise signup action.

You can remove email validation with
def email_required?
false
end
in your User model

1) I don't know from which version onwards, but at least at that time when you where asking your question, there was already a tutorial at the devise github page how to do it.
They outline two ways, one was described here, how to manage that. And you can keep their validations.
2) As for CRUD users they also have a tutorial which is a bit more recent.
Hope this still helps someone ;-)

Related

How do you implement lazy token authentication for non-registered users in Rails?

UPDATE
I have further clarified my question, listed at the end of this post.
Problem Summary:
I am trying to implement lazy (aka soft sign-up) registration in Devise via an emailed URL which includes token authentication. On my site, a User has_many :payments, and a Payment belongs_to :user. When a User creates a new Payment, it includes the attribute :email which represents a new non-registered user (which I'll call "Guest"). I use ActionMailer to send an email to this new Guest.
In this email that is sent, I would like to include a URL with token authentication (e.g. http://localhost/index?auth_token=TOKENVALUE), so that the Guest can see and edit views that require authentication and are specifically customized to them (based on their :email). The Guest should also have the ability to register as a User - since I already have their email address, they would just need to provide a password.
My Progress So Far:
I have implemented the ability for someone to register for the site using Devise and created the associated views, model and controller to make changes to my Payment model
I have setup ActionMailer and am using it to send emails, but have not yet setup token-authentication as I'm not sure how to do so given my use case above
Related Resources:
https://github.com/plataformatec/devise/wiki/How-To:-Simple-Token-Authentication-Example
http://zyphdesignco.com/blog/simple-auth-token-example-with-devise
Multiple user models with Ruby On Rails and devise to have separate registration routes but one common login route
how to create a guest user in Rails 3 + Devise
https://github.com/plataformatec/devise/wiki/How-To:-Create-a-guest-user
http://blog.bignerdranch.com/1679-lazy-user-registration-for-rails-apps/
http://railscasts.com/episodes/393-guest-user-record?view=asciicast
https://github.com/plataformatec/devise/wiki/How-To:-Manage-users-through-a-CRUD-interface
Rails 3 - Devise Gem - How to Manage Users through CRUD interface
http://danielboggs.com/articles/rails-authentication-and-user-management-via-crud/
/app/models/payment.rb
class Payment < ActiveRecord::Base
attr_accessible :amount, :description, :email, :frequency, :paid, :user_id
belongs_to :user
validates :email, :presence => true, :format => { :with => /.+#.+\..+/i }
validates :amount, :presence => true
validates :description, :presence => true
end
/app/models/user.rb
class User < ActiveRecord::Base
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable, :confirmable
# I will also need to add ":token_authenticatable"
attr_accessible :email, :password, :password_confirmation, :remember_me
has_many :payments
end
Questions:
How can I keep my User table in sync with new Payments that are created, such that any new :email created in the Payment table automatically are added in the User table as an :email?
Following from question 1, the new User created should include a token but not include a password, since the User has not yet registered for the site. Should I create the new User with a blank password, randomly generated string, or other? In either case, I think they would need to come to the registration page from their token-authenticated URL to prevent others from registering under their email username (e.g. lazy registration)
Following from question 2, I think that I will need to distinguish between Guests and normal users, as some of the views will change depending on user type. Is there a preferred method other than adding a column that would have a 0 or 1 to delineate between the two user types?
My preference if possible is to use Devise since I am using many of the features included. I'm new to RoR and appreciate any advice you can provide!
EDIT: Here is the code I used to address question #1 above, in my payments controller, in case helpful to someone else
def create
#payment = current_user.payments.build(params[:payment])
#Here is the code I added to keep User :email in sync with Payment :email, without token authentication implemented yet
unless User.find_by_email(params[:payment][:email].downcase)
u = User.new({:email => params[:payment][:email].downcase, :password => nil, :password_confirmation => nil })
u.skip_confirmation!
u.save(:validate => false) #skip validation
end
respond_to do |format|
if #payment.save
format.html { redirect_to payments_url, :flash => { notice: 'Payment was successfully created.' } }
format.json { render json: #payment, status: :created, location: #payment }
else
format.html { render action: "new" }
format.json { render json: #payment.errors, status: :unprocessable_entity }
end
end
end
Frameworks like Devise are great when you know how to use them well and what you want to do fits within what the framework was designed to do. When you start trying to "bend" them to do things they weren't designed to do, though, perhaps trying to monkey-patch them without fully understanding how they work internally, in my experience, you just make a mess. I've been there. Often you are reduced to just "hacking until it works"... which really means "hacking until it appears to work, aside from a dozen subtle bugs which only show up later".
In a case like this, I would be inclined to just "roll my own" login and authentication code, perhaps looking at the Devise source code for ideas.
(In fact, I did just that today on a new Rails app... it took a couple hours of hours, checking the Devise source for ideas now and again, to write the 200 lines of code my project actually needed. Devise itself, in comparison, has about 2500 lines.)
You asked for ideas on "overall strategy and structure" of the code. If you decide to implement your own custom login/authentication code, it will probably go something like this:
Store some information in the session hash to identify if the user is logged in, and as which Guest/User. Make sure your sessions are stored in the DB, not in cookies.
Add some methods to ApplicationController (or a Module mixed in to ApplicationController) to encapsulate access to this login information.
Put a before_filter on all pages which require login, which redirects if the user is not logged in.
Add a controller which authenticates users by password OR token, and allows them to log in/out.
For password storage/authentication, use the bcrypt-ruby gem. On your User model, you'll need a password_hash string field and probably some methods like:
require 'bcrypt'
def password
# BCrypt handles generation and storage of salts
#password ||= ::BCrypt::Password.new(password_hash)
end
def password=(password)
#password = ::BCrypt::Password.create(password)
self.password_hash = #password
end
def authenticate_by_password(password)
self.password == password
end
For token-based authentication, add a login_token string field to your User model (or Guest or whatever it is...). Make it a unique field. You can generate the unique tokens using something like this (borrowed from Devise and modified a little):
require 'securerandom'
def generate_login_token
loop do
token = SecureRandom.base64(15).tr('+/=lIO0', 'pqrsxyz')
unless User.where(:login_token => token).exists?
self.login_token = token
self.save!
return
end
end
end
Then when a user hits your login action with params[:token] set, just do something like:
if user = User.find_by_login_token(params[:token])
# if the token is supposed to be "one time only", null out the attribute
# log user in
else
# login token was invalid
end
Make sure you put a DB index on the login_token field.
Is there anything else?

Trying to add validation to rails devise sign-up page

Kinda a noob rails programmer, but this would save me a ton of headache. Currently, I'm trying to add validation to my devise sign on page, such as only allowing the sign up to complete if their email ends with a certain extension. Does anyone know where the file location stands that overlooks the sign on page? I've looked over all models and views but can't seem to find it. Thank you!
I think that you can use validates_format_of in your user.rb model. You can use rubular.com to create the regex.
validates_format_of :email, :with => /MYREGEXHERE/
Simply add validation to your email field. There is nothing special you have to do. like:
validates :email, format: { with: /my_ending_string\z/, message: "must ends with my_ending_string" }

How can I use validation rules set in a model in several controllers in ruby on rails?

For the sign-up form for my website I don't require that users confirm their passwords so there isn't a password confirmation field.
However when a user resets their password and they click the link in their email and then taken to a change password page I require they confirm their password and provide a field for them to do this.
My question is how can I add password confirmation and min and max lengths for passwords with out it affecting my sign up form because they use they same model. For my signup form I have min and max length validation rules and want these to apply to the change password form but also include password confirmation.
What is the best and most convenient way to do this in your opinion?
If password confirmation should only be done for users already created, the simplest solution is :
class User < ActiveRecord::Base
validates_confirmation_of :password, :unless => :new_record?
end
So it will only call the validation when setting the value for new users and not for users trying to sign up.
You can create your own methods to validate model data, something like this:
class User < ActiveRecord::Base
validate :user_data_validation
def user_data_validation
errors.add(:password_confirmation, "error in the password confirmation") if
!password.blank? and password != password_confirm
end
end
On the other hand you can use control-level validations, but:
Controller-level validations can be tempting to use, but often become
unwieldy and difficult to test and maintain. Whenever possible, it’s a
good idea to keep your controllers skinny, as it will make your
application a pleasure to work with in the long run. (c)

Rails Password Validate only in certain cases

In my rails 3 application, I have a user sign up form. I have it require and validate the password for signing up, however, if a user connects using omniauth, I cannot retrieve their password which brings me to my question. How can I make it so the password field does not get validated in certain cases?
I am aware that I can do user.save(:validate => false), but that takes off all the validations.
P.S. i watched railscasts episode 235 & 236, however Ryan Bates does it with Devise, and I am doing it with my own login system.
You can write your own validation for password with a check if the user is using oauth.
Check this method: validate(*args, &block)
you can add conditions to the validation in the model:
validates_presence_of :password, :unless => :account_from_omniauth?
assuming you also define the method
def account_from_omniauth?
true if //...your conditions
false
end

Flash[:error] based on validates_uniqueness_of :login, :email

I have a user model that validates_uniqueness_of :login, :e-mail. When a user enters his information into user/new.html.erb, the create action may fail because of either of the two fields.
How can I customize my flash to be more helpful to the user, telling them which (or both) of the fields they need to change the next time around?
flash[:error] = #user.errors.full_messages.to_sentence
should do the job. But I would recommend that you display the error right next to the field which contains invalid data. Plugins like formtastic will do this for you automatically.
Check the API for more ideas.

Resources