Limit queries in custom Rails4 model validation - ruby-on-rails

Trying Ruby on Rails I am fiddling around making a small application. So far I like the rails way. I have a model Administration which has a Manager and an Organisation.
I want to make sure - using validations - that the manager assigned to the adminisration is associated to the organisation the administrator belongs to.
I have a working validation, but my gut-feeling says it's expensive on queries.
class Administration < ActiveRecord::Base
belongs_to :organisation
belongs_to :manager, :class_name => "User", :foreign_key => 'manager_id'
validates :code, numericality: true
validates :manager_id, :presence => true
validates :organisation_id, :presence => true
validates :code, uniqueness: { scope: :organisation_id, message: 'BB: Code already in use' }
validate :manager_belongs_to_organisation
def manager_belongs_to_organisation
errors.add(:base, 'BB: Manager does not exist') unless Organisation.find(self.organisation_id).users.include?(User.find(self.manager_id))
end
end
Any thoughts on this matter?

One way to work around this issue is to assign only manager to Administration. organization_id is then automatically inserted using before_save callback:
class Administration
...
before_save :update_organization_id
...
def update_organization_id
self.organization_id = self.manager.organization_id
end
end

It looks like there are more problems with your data model.
Please don't forget about single responsibility principle. In your Administration model your check relations beetween Manager and Organization.
This is bad idea in general.
Is your manager has only one organization? If so, better solution is to store only manager for Administration, and call administration.manager.organization when you need it. And add validation to your Manager model:
validates :organization, presence: true

Related

Partly invoke validation process on some Activerecord object attributes

I have a situation where User has_one :address and Address belongs_to :user.
I need to be able to validate the address object in these cases:
After a user has signed up, he has an option to partly fill in the address form. In this state I would like to validate for example validates :phone_number, :postal_code, numericality: true but the user can leave the field blank if he wants to.
When user is making a purchase he has to complete the address form. And all the fields have to be validated by validates presence: true + previous validations.
I understand that one approach would be to attach another parameter to the form (i.e.full_validation) and then add a custom validation method that would check for this parameter and then fully validate all attributes.
I was just wondering is there a more code efficient and easier way to do this.
So far I have only found ways to validate some attributes (seethis blog post) but I have not yet found suggestions on how to invoke part of the validation process for certain attributes.
Any help/suggestions will be appreciated :)
#app/models/user.rb
class User < ActiveRecord::Base
has_one :address, inverse_of: :user
end
#app/models/address.rb
class Address < ActiveRecord::Base
belongs_to :user, inverse_of: :address
validates :phone_number, :postal_code, numericality: true, if: ["phone_number.present?", "postal_code.present?"]
validates :x, :y, :z, presence: true, unless: "user.new_record?"
end
--
After a user has signed up
Use if to determine if the phone_number or postal_code are present.
This will only validate their numericality if they exist in the submitted data. Whether the User is new doesn't matter.
--
When user is making a purchase
To make a purchase, I presume a User has to have been created (otherwise he cannot purchase). I used the user.new_record? method to determine whether the user is a new record or not.
Ultimately, both my & #odaata's answers allude to the use of conditional evaluation (if / unless) to determine whether certain attributes / credentials warrant validation.
The docs cover the issue in depth; I included inverse_of because it gives you access to the associative objects (allowing you to call user.x in Address).
If you give more context on how you're managing the purchase flow, I'll be able to provide better conditional logic for it.
For your first use case, you can use the :allow_blank option on validates to allow the field to be blank, i.e. only validate the field if it is not blank?.
http://guides.rubyonrails.org/active_record_validations.html#allow-blank
For both use cases, you can tell Rails exactly when to fire the validations using the :if/:unless options. This is known as Conditional Validation:
http://guides.rubyonrails.org/active_record_validations.html#conditional-validation
For Address, you might try something like this:
class Address
belongs_to :user
validates :phone_number, :postal_code, numericality: true, allow_blank: true, if: new_user?
def new_user?
user && user.new_record?
end
end
This gives you an example for your first use case. As for the second, you'll want to use conditional validation on User to make sure an address is present when the person makes a purchase. How this is handled depends on your situation: You could set a flag on User or have that flag check some aspect of User, e.g. the presence of any purchases for a given user.
class User
has_one :address
has_many :purchases
validates :address, presence: true, if: has_purchases?
def has_purchases?
purchases.exists?
end
end

Rails - What's the proper way to use scopes to check if a record exists for the current day only

In our website we will have a search history feature so users can view and retrieve their last x number of searches for the current day.
I would like to check that the user hasn't already entered the same keyword for the current day before creating a new record. These records would be kept in the db for a few days before being removed so if I just validate the uniqueness of the keyword and the user entered that keyword in the past, the record would not be created.
Below is how I have my model and controller setup. Bear with me, I'm still learning about rails and scopes.
MODEL
class UserLog < ActiveRecord::Base
belongs_to :user
validates :user_id, presence: true
validates :query_type, presence: true
validates :keyword, presence: true
validates :url, presence: true
validates_uniqueness_of :id
scope :user_searches, -> (user = nil) {where(user_id: user).order(created_at: :desc)}
scope :today_only, -> {where(created_at: Time.now.beginning_of_day..Time.now.end_of_day)}
end
I believe I could add these checks in my model that would do what I want.
validates_uniqueness_of :keyword, scope: :keyword, conditions: -> {where(created_at: Time.now.beginning_of_day..Time.now.end_of_day)}
OR THIS?
validates_uniqueness_of :keyword, conditions: -> {where(created_at: Time.now.beginning_of_day..Time.now.end_of_day)}
And the controller
# to save user query in db
if query_valid (other checks in controller)
UserLog.create(user_id: current_user.id, query_type: query_type, keyword: query_value, url: request.fullpath)
end
And to get records to display on user request
#recent_searches = UserLog.user_searches(current_user).today_only.limit(15)
The whole Time.now.beginning_of_day..Time.now.end_of_day sounds overcomplicated to me. How about you store created_on just like created_at, but a Date, not a DateTime. Your uniqueness scope becomes much easier, similarly creation could be:
current_user.logs.where(keyword: query_value, created_on: Date.today).first_or_create(other_fields)
I'm assuming user has_many :logs, for readability. Instead of UserLog.create(user_id: current_user.id, ...

Rails - new User builds dependent Email and validates both?

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

Rails validations and belongs_to association

In my rails projects I have a lot of association tables. And I have some validations. Nothing really difficult, and it works almost every times.
But from time to time (like tonight), I have to switch from
validates_presence_of :project_id
validates_presence_of :tag_id
validates_uniqueness_of :project_id, :scope => [:tag_id]
to
validates_presence_of :project
validates_presence_of :tag
validates_uniqueness_of :project, :scope => [:tag]
Do you know the difference ? Do you if one is better than the other ?
From the Rails Guides: http://guides.rubyonrails.org/active_record_validations.html#presence
2.9 presence This helper validates that the specified attributes are not empty. It uses the blank? method to check if the value is either
nil or a blank string, that is, a string that is either empty or
consists of whitespace.
class Person < ActiveRecord::Base
validates :name, :login, :email, presence: true
end
If you want to be sure that an association is present, you'll need to
test whether the associated object itself is present, and not the
foreign key used to map the association.
class LineItem < ActiveRecord::Base
belongs_to :order
validates :order, presence: true
end
So, you should use the second example you gave, which tests if the associated object itself is present, and not the first example, which only tests if the foreign key used to map the association is present.

validates_associated not checking existence of associations

Can anyone figure out what's going on here? I was able to get my code to work the way I want it to, but I can't figure out why validates_associated isn't working as I expect. Here's a snippet of my code:
class Flag < ActiveRecord::Base
belongs_to :user
belongs_to :post
# allow only one flag per post per user
validates_uniqueness_of :user_id, :scope => :post_id
validates :user_id, :post_id, :presence => true
validates_associated :user, :post
attr_accessible :user_id, :post_id
end
With this code I can't save a flag with user_id == nil. I can save a flag with user_id == 12345 (i.e. some user_id not in the database). This is what the validates_associated API specification says:
validates_associated(*attr_names)
Validates whether the associated object or objects are all valid themselves. Works with any kind of association.
...
NOTE: This validation will not fail if the association hasn’t been assigned. If you want to ensure that the association is both present and guaranteed to be valid, you also need to use validates_presence_of.
I was able to get the desired behavior by using this, instead:
validates :user, :post, :presence => true
My understanding of the API specification is that validates_associated checks the associated table to see if a row exists with an id matching the foreign key of Flag provided the foreign key is non-nil. Can anyone offer any insight on this? Am I misunderstanding how validates_associated is supposed to work?
validates_associated simply runs the validations that are specified within the associated object's class, it does nothing in regard to foreign keys.
validates :user_id, :presence=>true ensures the presence of a user_id in your flag record, but that's all.
validates :user, :presence=>true is used on the association itself and ensures that foreign keys are properly set up.
Man... all I got was that validates_presence_of is needed for this to work as you got from the API. Seem's overkill to be checking for association validness, but I'm a noob.

Resources