We are development an announcement mechanism that works in such way:
- Users are nested attributes under an announcement. User's emails are entered in the new announcement form as the nested attributes.
- Would like to work this way - When the announcement is saved, it needs to search user's email w/ current users' emails, if found, then deposit this announcement into that user's profile and the user will be notified via email. If NOT, then a new user account is created and an email is sent for the user to claim his account.
When creating a new announcement, the rails saves all attributes tied w/nested attributes (this is great except when the user's email already exists).
Since only user's email is used to created the User's account, how can we implement password later on (or auto create password to allow user to change later on)? We are using devise for authentication. Is there a way to use devise to perform this function?
How this problem can be solved ? Your help is greatly appreciated.
class Announcement < ActiveRecord::Base
attr_accessible :content, :users_attributes
has_many :users, :through => :awards
has_many :awards, :dependent => :destroy
accepts_nested_attributes_for :users, :reject_if => lambda { |a| a[:email].blank? }, :allow_destroy => true
end
class User < ActiveRecord::Base
attr_accessible :email, :first_name, :last_name, :middle_name
has_many :awards, :dependent => :destroy
has_many :announcements, :through => :awards, :dependent => :destroy
email_regex = /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i
validates :email, :presence => true, :format => { :with => email_regex }, :uniqueness => { :case_sensitive => false }
end
E-mail
You can implement an additional boolean attribute on your user model to indicate that the account has been claimed, and then not use the confirmable module of devise (since the user has effectively confirmed their email by following back a link). When a user arrives who's account has not been claimed, fill in the authentication details at that point. Presumably you'd have an attribute in the Email linkback that lets you retrieve the appropriate record to populate.
Related
I'm building a quick Rails project that allows users to manage their email addresses. Users can have many emails, but one (and only one) of those emails has to be marked as 'primary' (for login), and a user cannot exist without a primary email.
I've been struggling to get this to work right - it seems so circular to me. I need to build a User, and then the Email, but I don't want to save the User into the database unless the Email is valid, which it won't be until the User is saved (because of the validates :user, presence: true constraint).
Accepts nested resources for doesn't seem to work with .new (works fine with .create), and if my Email fails its validations, the User still shows as valid.
Been having a difficult time trying to find good resources (or SO questions) for building/validating multiple/dependent models from a single form.
What's the most Rails way to do this?
User
has_many :emails
has_one :primary_email, -> { where(primary: true) }, class_name: "Email"
accepts_nested_attributes_for :primary_email
validates :first_name, presence: true
validates :last_name, presence: true
validates :birthday, presence: true
validates :password_digest, presence: true
Email
belongs_to :user
validates :user, presence: true
validates :address, presence: true, uniqueness: {
case_sensitive: false
}
UsersController
def new
#user = User.new
end
def create
#user = User.new(user_params)
if #user.save
# do something
else
# show #user.errors
end
end
private
def user_params
params.require(:user).permit(
:first_name,
:last_name,
:birthday,
:password,
:password_confirmation,
:primary_email_attributes => [:address]
)
end
EDIT
The Email model also contains the following fields:
label = string, eg. 'Personal', 'Work', etc
primary = boolean, whether it's marked as primary email or not
confirmation_code = autogenerated on creation, used to confirm ownership
confirmed = boolean, whether it's been confirmed or not
class User
user has_many :emails
user has_one :primary_email, -> { where(primary: true) }, class_name: "Email", autosave: true
after_initialize {
build_primary_email if new_record?
}
end
class Email
# use gem https://github.com/balexand/email_validator
validates :my_email_attribute, :email => true
end
So after a user initialized its building a primary_email so that record is already associated, or at least it will be if it can be saved. the autosave is working pretty cool - if the primary-email can't be saved due validation error, the user can't neither. should work out of the box, im in a bus right now, can't check it. cheers
futher information: http://api.rubyonrails.org/classes/ActiveRecord/AutosaveAssociation.html
If validations for any of the associations fail, their error messages will be applied to the parent. That means, the Parent Model (in your case User) is having errors, and thats why the saving is not possible! that's what you are looking for.
I would store a primary email as a common field and additional emails some another way. I would prefer to store additional emails in another field too that is Array rather than in an associated table. You shouldn't store a primary email in another table. Just imagine, every time you need authorize user or just get his email you will perform an extra request to db.
Meant to post this months ago.
The solution, keeping users and emails normalized across different models without storing a primary email as an attribute on the user, is to use inverse_of:
User.rb
class User < ActiveRecord::Base
has_many :emails, inverse_of: :user, dependent: :destroy
accepts_nested_attributes_for :emails
validates :emails, presence: true
end
Email.rb
class Email < ActiveRecord::Base
belongs_to :user, inverse_of: :emails
validates :user, presence: true
end
This allows validations to be performed using in-memory objects, rather than via database calls (ie the object associations are being validated, rather than the presence of an id/record in the database). Therefore they both pass validation and can both be saved in the same transaction.
See: https://viget.com/extend/exploring-the-inverse-of-option-on-rails-model-associations
I want to have a 'contact person' form that allows a user to enter their Personal and Work email at the same time.
I can imagine two ways of doing this but am not sure they're optimal and may be missing a Rails way of doing so:
Have the nested form create the email model twice, but add a flag for :position to identify them. (Hidden field to do so?)
Set up a delegate that maps :personal_email and :work_email to the Email model such that the model handles them separately.
Something else?
Currently I have emails set up like this:
class Individual < ActiveRecord::Base
attr_accessible :first_name, ...
has_many :emails
#delegate :personal_email, :to => :email, :allow_nil => true
end
class Email < ActiveRecord::Base
attr_accessible :email_address, :owner_id, :owner_klass, :position, :verified, :email_type
belongs_to :individual
# WIP Returns 'primary' email for a user
def personal_email
end
end
I am using the validates_existence_of gem. It works well except when I want to allow my foreign key to be nil.
Here are my models for User and Project. A project belongs_to a user and a contributor (a contributor is also a user), but the contributor can be nil.
Here is my user model:
class User < ActiveRecord::Base
attr_accessible :first_name, :last_name
has_many :projects, :dependent => :destroy
has_many :user_trimester_statuses, :dependent => :destroy
end
And here is my project model:
class Project < ActiveRecord::Base
attr_accessible :added, :amount, :contributor_id, :label, :ref, :trimester_id, :user_id
belongs_to :user
belongs_to :contributor, :class_name => 'User'
belongs_to :trimester
validates :user, :existence => { :both => false }
validates :trimester, :existence => { :both => false }
validates :contributor, :existence => { :allow_nil => true, :both => false }
end
When I try to add a new project, I have an error if the user_id or trimester_id field is blank or invalid. But for the contributor_id field, there is no error thrown if the field is invalid. It goes through either way (valid, invalid, or nil).
What am I doing wrong?
I am using ruby 2.0.0p0 and rails 3.2.13.
It looks like there is an open bug about this in the project.
https://github.com/perfectline/validates_existence/issues/15
You may have to write a simple custom validator for this case until this gets fixed. (or dig in and see if you can fix the issue yourself)
UPDATE:
I just cloned the project and wrote a test to see what the issue was. It seems that when you add the allow_nil, the existence validator doesn't get called at all.
I'm not sure why that is, but in the meantime, you can work around the bug in an easy way, by using a proc.
instead of
validates :contributor, :existence => { :allow_nil => true, :both => false }
this would get the job done
validates_existence_of :contributor, :unless => Proc.new { |obj| obj.contributor_id.blank? }
I was able to prove that in my test case.
(I went with the 'validates_existence_of' method, instead of 'validates', because I thought it was cleaner in this case)
I have a polymorphic association (contact_details) in my Company model and I want to validate the parent model. Note: I am using accepts_nested_attributes_for in my parent model.
The basic rule:
the company must have at least one phone (phone is the kind of
contact_detail)
The problem:
accepts_nested_attributes_for call destroy for child objects AFTER
validation of the parent object
so the user are able to delete a phone. Of course, later, when the user will try to edit a company without a phone, he/she will get an error (The company must have at least one phone).
Company (Parent) model:
class Company < ActiveRecord::Base
PHONES_NUMBER_MIN = 1
attr_accessible :name, :contact_details_attributes, ...
has_many :contact_details, :as => :contactable, :dependent => :destroy
validate do |company|
check_phones_number
end
accepts_nested_attributes_for :contact_details, :allow_destroy => true, :reject_if => :all_blank
private
def phones_number_valid?
kind = ContactDetail::Kind.phone
phones = contact_details.select { |cd| cd.kind_id == kind.id }
phones.size >= PHONES_NUMBER_MIN
end
def check_phones_number
unless phones_number_valid?
errors.add(:base, :phones_too_short, :count => PHONES_NUMBER_MIN)
end
end
...
end
ContactDetail (Child) model:
class ContactDetail < ActiveRecord::Base
attr_accessible :kind_id, :kind_value_source
belongs_to :contactable, :polymorphic => true
belongs_to :kind
validates :kind_value_source, :presence => true, :length => {:maximum => 255}
...
end
Note: I simplified the original version, so objective was clear to you. Here is the gist with the code.
By using the reject_if option I am able to forbid the deletion of all the phones. It is probably the best option by now. But I want to hear your opinions.
I also found this question and tried to apply the answer, but it didn't helped a lot. The same problem, as I described above. I've drawn a flowchart so you can see the trace, as I see it.
How can I validate the parent model in such a case?
I would be grateful for any help.
From the question you referenced, you can get rid of the reject_if and modify the line in phones_number_valid?:
phones = contact_details.select { |cd| cd.kind_id == kind.id && !cd.marked_for_destruction? }
Is it posible to validate the uniqueness of a child model's attribute scoped against a polymorphic relationship?
For example I have a model called field that belongs to fieldable:
class Field < ActiveRecord::Base
belongs_to :fieldable, :polymorphic => :true
validates_uniqueness_of :name, :scope => :fieldable_id
end
I have several other models (Pages, Items) which have many Fields. So what I want is to validate the uniqueness of the field name against the parent model, but the problem is that occasionally a Page and an Item share the same ID number, causing the validations to fail when they shouldn't.
Am I just doing this wrong or is there a better way to do this?
Just widen the scope to include the fieldable type:
class Field < ActiveRecord::Base
belongs_to :fieldable, :polymorphic => :true
validates_uniqueness_of :name, :scope => [:fieldable_id, :fieldable_type]
end
You can also add a message to override the default message, or use scope to add the validation:
class Field < ActiveRecord::Base
belongs_to :fieldable, :polymorphic => :true
validates_uniqueness_of :fieldable_id, :scope => [:fieldable_id, :fieldable_type], :message => 'cannot be duplicated'
end
As a bonus if you go to your en.yml, and enter:
activerecord:
attributes:
field:
fieldable_id: 'Field'
You are going to replace the default 'subject' that rails add to the errors with the one you specify here. So instead of saying: Fieldable Id has been already taken or so, it would say:
Field cannot be duplicated