accessing data on a Model's associated Model to perform validations - ruby-on-rails

class User < ActiveRecord::Base
belongs_to :school
validates :email, :email => { :message => "Must be a valid email." }, :format => { :with => /\A[\w+\-.]+##{Regexp.quote(school.email_domain)}\z/i }
end
I want to be able to validate that on create a user's email matches their school's email domain. I am creating users by:
#school.users.create(params[:user])
Error thrown :
undefined local variable or method `school' for #<Class:0x007f8aaabb0df0>
Thanks for the help!

You're getting that error because you're trying to call the #school method within the context of your class instead of within an instance of your class, and #school is an instance method.
To call instance methods when constructing your validation format Regexp, you can provide a lambda as the :with option, as follows:
validates :email,
:message => "Must be a valid email",
:format => { :with => lambda {|user| /\A[\w+\-.]+##{Regexp.quote(user.school.email_domain)}\z/i } }
This lambda will be invoked on your model instance, allowing you to call methods on your User instance such as #school. See the documentation for validates_format_of for more details.

Related

update nested_attributes_for errors with uniqueness constraint

So I'm working on build a user model in rails and this user model will have an associated email address model. The email address model has a uniqueness constraint on the email. Right now I have it set up so that the user accepts_nested_attributes_for :email_address. This works great on create but on update I get this error:
ActiveRecord::JDBCError: org.postgresql.util.PSQLException:
ERROR: duplicate key value violates unique constraint
"index_email_addresses_on_email"
I can recreate this bug by doing this in the rails console:
u = User.create(:name => "foo", :new_password => "Passw0rd",
:email_address_attributes => {:email => "foo#bar.com"})
u.update({:name => "new name",
:email_address_attributes => {:email => "foo#bar.com"}})
How do I get it to update the name while not caring about the email_address. Which hasn't changed?
Some other notes and code:
I do index my email_address on email and I'm using rails 4.
class User < ActiveRecord::Base
belongs_to :email_address
validates :email_address, :presence => true
accepts_nested_attributes_for :email_address
end
class EmailAddress < ActiveRecord::Base
validates_format_of :email, :with => RFC822::EmailAddress
validates :email, :presence => true
has_one :user
end
When you update your email_address_attributes in this way, you're actually adding a new email_address object for your user. You need to pass the email address's id as an attribute, i.e.:
u.update({:name => "new name",
:email_address_attributes => {:id => u.email_address.id, :email => "foo#bar.com"}})
Or alternatively, you can update the user's email address in a different update statement
u.update({:name => "new name"})
u.email_address.update({:email => "foo#bar.com"})
As for your controller, all you need to do is add the email addresses's :id field as a permitted parameter.
def user_params
params.require(:user).permit(:name, email_address_attributes: [:id, :email])
end
There is more information about Strong Parameters in the Strong Parameters Rails Guide. Check out the More Example section for a setup similar to yours.
If you don't want to validate the e-mail address except on create, you can add that to the validation:
validates :email_address, presence: true, on: :create
Use ":update_only" option in "accepts_nested_attributes_for" like this:
accepts_nested_attributes_for :email_address, :update_only => true
This way active record will update the child record if it already exists, instead of creating a new one. This should take care of the unique constraint.

why doesn't attribute in a model work in Rails SIMPLE

new to rails/ruby, so this (i think) is a very straightforward question. Why doesn't this work in my model
class Subscription < ActiveRecord::Base
attribute :email, :validate => /\A([\w\.%\+\-]+)#([\w\-]+\.)+([\w]{2,})\z/i
end
I get the error
undefined method `attribute' for #<Class:0x00000101218ed0>
The model does exist, as does the column (or attribute?) 'email', so surely I must be able to validate its submission like so.
Rails format helper validates the attributes' values by testing whether they match a given regular expression, which is specified using the :with option
Try:
class Subscription < ActiveRecord::Base
validates :email, format: { with: /\A([\w\.%\+\-]+)#([\w\-]+\.)+([\w]{2,})\z/i,message: "your validation message" }
end
If you are using rails 3.x then you need validates_format_of
validates_format_of :email, :with => /\A([\w\.%\+\-]+)#([\w\-]+\.)+([\w]{2,})\z/i

Different types of validations in rails

I have a User model
is there a difference between
class User < ActiveRecord::Base
validates :name, :presence => true
end
and
class User < ActiveRecord::Base
def validate
errors.add_to_base "name should not be nil" if name.nil?
end
end
The validates macro is more flexible, as it also allows you to do things like:
validates :name, :format => { :with => /\A[a-zA-Z]+\z/,
:message => "Only letters allowed" }, :length => { :in => 6..20 }
The validate method is really a quick and easy way to do custom validations when existing ones do not exist. (When custom validations get too complex, then you should usually move them into custom validators and use the validates macro).
See more at http://guides.rubyonrails.org/active_record_validations_callbacks.html
Yes -- the first will fail to save an empty string, whereas the second will allow it.

ActiveRecord validate :uniqueness on association

I need to perform the validation to make sure that only one user within a company can exist within a given category.
validates :user_id, :uniqueness => {:scope => [:category, :company_id], :message => "already exists"}
This works except the error message is set on :user_id key.
How can I do the same but set the error on the :user key (validates :user gives an error)?
Here's a simple way to check uniqueness and force the error to be assigned to the :user attribute:
class User < ActiveRecord::Base
validate :user_unique_per_company_per_category
private
def user_unique_per_company_per_category
if self.class.exists?(:user_id => user_id, :company_id => company_id, :category => category)
errors.add :user, 'already exists'
end
end
end
It would be preferable, I think, if you could figure out a way to use the default validation on :user_id, but maybe you have a special use case.
Also, if you're not using this in a form, you might consider assigning the error to :base, since you might confuse future developers who expect the error to appear on :user_id:
errors.add :base, 'already exists'
I don't think this is possible as the validates method of ActiveRecord sends the errors to the method being validated.
So validates :user trys to send to the attr_accessor of :user which doesn't exist in your model.
Though, if you're just trying to make the error message pretty you can:
alias user user_id
And then use :user in your validation.
validates :user, :uniqueness => {:scope => [:category, :company_id], :message => "already exists"}
On a side note, I wouldn't use user in the alias rather something like:
alias the_supplied_user user_id
And then in your validation:
validates :the_supplied_user, :uniqueness => {:scope => [:category, :company_id], :message => "already exists"}

Creating users with has_secure_password

I'm trying to use has_secure_password on my user model, but have found that while it works in the application it breaks all my tests. I have a simple user model:
class User < ActiveRecord::Base
has_secure_password
attr_accessible :email
validates :email, :presence => true,
:format => { :with => /\A[\w+\-.]+#[a-z\d\-.]+\.[a-z]+\z/i },
:uniqueness => { :case_sensitive => false }
validates :password, :presence => true
end
My first test (rspec) simply confirms that I can create a new user with valid attributes:
describe User do
before(:each) do
#attr = { :forename => "Captain",
:surname => "Hook",
:email => "email#test.com",
:password => "password",
:password_confirmation => "password" }
end
it "should create a new instance given valid attributes" do
User.create!(#attr)
end
end
This doesn't work, however when I do
user = User.new(#attr);
user.password = "password";
user.save
it works fine. I believe this is because has_secure_password adds a new method, password, which deals with the generation of the password_digest, so calling it directly like this generates the fields that I need. Is there any way I can use User.create but still call this method?
Turns out the problem was really simple. Since I hadn't added :password to attr_accessible it wasn't populating the field when I called User.create or User.new. The only modification I needed to make to the code was
attr_accessible :email, :password

Resources