I have two different forms (two different controllers) and both these forms are using the Member model.
Devise uses it to create and update
MyController uses it to create and update
My model validates all fields as required.
So an example:
I have MyController presenting a form with all the fields (it works fine)
I have Devise presenting the register form with (email, password, password_confirmation).
When I try to submit the devise form, it shows me errors for fields that aren't there.
How can I skip validation for specific fields when Devise controller in the model?
It's good design to keep the devise model as small as possible and just keep it limited to user authentication. The reason being, every time you call current_user, it will load the whole object (with all the profile fields) whether or not you need them.
Since you've already created a new controller, I would suggest to create a new model for the profile attributes and create a has_one relationship with the devise model:
class User < ActiveRecord::Base
require 'devise'
has_one :user_profile
end
class UserProfile < ActiveRecord::Base
belongs_to :user
<user_profile fields>
end
With this approach your devise object will be lightweight and you can access the profile attributes only when required:
current_user.user_profile
Related
In general, I have a website which needs to have complex registration process. And in that registration process I need to include 4 tables from database.
Now... I cannot validate one by one model and to enter 4 of them in database. Is there a way to make common points of all those tables in one model?
Let's say:
User model has columns: username, name, etc.
Package model has: type, account_number
etc
And in registration process I need to include username, name, account_number and to validate them. How to do that?
Without seeing your model structure, this is just speculation, but here goes:
--
Virtual Attributes
In your User model, you can use attr_accessor to create a set of virtual attributes - which basically mean you can create a series of setter / getter methods in your User model.
Although I don't think this will help you directly, it should give you an idea as to how you can create single-model validation:
#app/models/user.rb
Class User < ActiveRecord::Base
attr_accessor :new, :values, :to, :validate
validates, :new, :values, :to, :validate, presence: true
end
This will allow you to create the attributes in the User model - and although they won't be saved, you can then use them to validate against
attr_accessor
To give you a quick overview of this method, you first need to remember that Rails is just a collection of modules and classes.
This means that every model you load is just a class which has been populated with a series of getter / setter methods. These methods are defined by ActiveRecord from your data table columns, and are why you can call #user.attribute
The magic is that if you use attr_accessor, you'll basically create your own attributes in your User model - which won't be saved in the database, but will be treated like the other attributes your objects have, allowing you to validate them
Because your registration process seems to be complex, I would go even futher as virtual attributes and use Form Objects
7 Patterns to Refactor Fat ActiveRecord Models
LA Ruby Conference 2013 Refactoring Fat Models
ActiveModel Form Objects
I understand that you multistep registration. You shouldn't create 4 models only because your view pages needs it. You should:
remove validation from User model and add validation on each form
create 4 different forms (for example extends by ActiveModel or user gem reform)
add validation to each form
after form.valid? save part of user info to #user object
Thats all.
Say I have the following in a Rails 4 app:
class Person < ActiveRecord::Base
has_many :email_addresses, as: :emailable
has_one :user_account
end
class EmailAddress < ActiveRecord::Base
belongs_to :emailable, polymorphic: true
# There is an :address column
end
class UserAccount < ActiveRecord::Base
belongs_to :person
end
A person can have multiple email addresses. A person can also have a user account. (I've moved this into it's own model because not all people would be users.) Any of the person's email addresses could be used as the "username" when logging in.
Given this, I would like to use the Devise gem. I see you can specify the model to which the authentication is applied. User is widely used, but I would be using UserAccount. However, Devise then expects the email (username) field to be in this model.
When a person registers a user account, there would actually be three associated records created (Person, EmailAddress, and UserAccount). I can't figure out how to to get Devise to work with this setup. Any ideas?
One option would be, to delegate the email method from your UserAccount to your email model and override the finder def self.find_first_by_auth_conditions(warden_conditions) used by the devise login procedure. I found a pretty nice blog post that describes this in depth and another stackoverflow answer that has the same approach. There is also a section in the docs about how to confirm a devise account with multiple emails.
As your setup is a bit more complicated, you could also maybe use EmailAddress as your primary devise model and delegate the password methods to the UserAccount.
This would potentially be useful if you have to confirm every email address with confirmable and not only the user account. This setup would you from overriding that finder, but you may run into other issues with the delegated password as is never tried that before.
If you are using ActiveRecord,
First, add
attr_accessor :email
to user_account (i think this is the easiest way to deal with devise form)
next, you need to modify devise login procedure. Still, in your user_account, override the devise method such
def self.find_for_database_authentication(warden_conditions)
conditions = warden_conditions.dup
if email = conditions.delete(:email)
where(conditions.to_h).includes(:email_addresses).where(email_addresses: {email: email}).first
else
where(conditions.to_h).first
end
end
you may also need to define following to get work the code above
class UserAccount < ActiveRecord::Base
belongs_to :person
has_many :email_addresses, through: :person
That should just work, i have tested this using active record but if you are using mongoid, probably the solution will be different.
Note that, i have modified the code from devise's How To: Allow users to sign in using their username or email address documentation to get the solution.
Using devise, what is the best way to have multiple models or roles?
Here are the models or roles I need in my app:
-Author: can upload content to the site
-Customer: pays a monthly fee to access the content
A user can be both an Author and a Customer. I think they can share the same login form because they will both log in with their email address.
I have tried using CanCan and rolify, but couldn't figure out how to add different roles during registration. When a user registers as an Author, he should be given the "author" role and when a user registers by paying the monthly fee, he should be given the "customer" role. I don't want to use a checkbox for the roles either.
There is a nav link called "Authors" that will allow authors to register and then there is a link for users to register by going through the payment and billing form. Based on which link is clicked, should determine which role is given. Should I pass in a role parameter in the url during registration like "user/sign_up/index?role=customer"? If so, how do I get devise to use this in the registration process?
Using devise, CanCan, and rolify, how can I solve this? I thought it might be better to have a User class and then have Customer and Author extend from User, but from reading many similar questions on StackOverflow it seems rolify is easier.
I know what I am trying to do is very basic, I just haven't figured it out yet. I have been trying to find a solution for this all day.
Update:
Danny's answer below pushed me in the right direction and I have managed to get the roles added properly during registration by passing in a parameter in the URL "users/sign_up?role=customer" or "users/sign_up?role=author". I don't know if this is the best way, but that's what I have so far.
One question I have though is how will my has_many relations work now? Here is what I have:
class User < ActiveRecord::Base
rolify
devise :database_authenticatable, :registerable,
:recoverable, :rememberable, :trackable, :validatable
has_one :plan
has_many :files
end
has_one :plan is for the customer's subscription plan
and has_many :files
is for the author who can upload many files that the customer can
download
The problem is that I would also like to keep track of the user's file downloads so I would like to put has_many :downloads or probably has_many :files, :through => :downloads
Does it matter if I put these in the User model knowing that an author who is not a customer is not allowed to download files and that a customer who is not an author cannot upload files?
From your question, I understand you've already "dived" into rolify and cancan, so I'll focus on assigning roles.
You typically create different roles in your seeds.rb file, with something like
[:admin, :author, :customer].each do |role|
Role.find_or_create_by_name({ name: role }, without_protection: true)
end
You assign the :admin role immediately when you create yourself as administrator, also in the seeds.rb file, using
user.add_role :admin
For your other roles, you assign them "when it's appropriate", exactly as you describe. When a user clicks the Authors link, and proceeds, this triggers some action in some controller. It is there that you assign this role to that user, by using the same
user.add_role :author
You can also assign roles, connected to certain objects only. E.g. an author is author for only the documents he creates himself. In that case you don't want to assign a "general" author role, but you will assign it like
user.add_role :author, document
in the controller's create action, together with saving the created document. Alternative for this kind of assignment is to do it in a callback from your model.
More questions? Just ask!
I am developing a rails application in which I have two models User and Client.
User is backed by devise and is responsible for authentication and has_one Client which holds the client details for a given user. This relation is always present as I ensure that a Client model is created whenever I create a User.
For the administration area I am using ActiveAdmin. Now, when I try to create a User through the administration interface I use a form like this:
form do |f|
f.inputs :username, :email, :password
f.inputs :name => "Client", :for => :client do |client|
client.inputs :name, :address, ...
end
end
The problem is that either the User nor the Client are saved and the page is reloaded with validation errors. I have checked rails console and there's a WARNING: Can't mass-assign protected attributes: client_attributes message every time I try to create a User.
I have searched over this issue and found that in order to allow for mass-assignment one had to define attr_accessible for each of the fields allowed for the assignment. So, I had put this directive in Client model for each of the fields mentioned above and the message keeps appearing, preventing the models to be properly saved.
Does anyone have a clue on this?
The problem is not in your Client model, but in your User model - because this is the primary model you are trying to create. All you need to do is to add client_attributes to the list of attr_accessible attributes in your User model, just as the error message in the log files says, e.g.:
class User < ActiveRecord::Base
attr_accessible :client_attributes
end
I imagine you already have a list of accessible attributes in the User class. So just add client_attributes to the end of that list.
The changes you made to your Client model (i.e. adding a list of attributes to attr_accessible) is not needed for this to work. If you want, you can also go ahead and undo that.
My previous Rails apps have ended up with bloated user model with a thousand has_many associations and a whole bunch of domain-specific code and attributes in the user model.
In the next app, I am thinking about separating the model that I use for authentication (with devise) from the domain-specific model that I use for the rest of the app.
Something like this:
class User
devise :database_authenticatable, :etc
has_one :domain_user
end
class DomainUser
belongs_to :user
end
Controllers would have an accessor to make access to the domain user easy and most of the app would not care about the authentication model at all.
class ApplicationController
def domain_user
current_user.domain_user
end
end
As well as avoiding the bloat of putting everything into one model, I hope to end up with an authentication sub-system that I can reuse in future apps.
Is this a good idea? Is there a better way to approach this? What pitfalls should I look out for?