I have a model called Profile which is belong_to User, so there is 'user_id' for the database to keep track of. In the local admin interface I made for this model I wanted to provide the flexibility of allowing admin to enter an username to a field in the editing screen, and then resolve that to user_id for saving in controller.
However the question is, how do I check against that the username have a valid return? I found that in ActiveRecord::Validation there is no method for validating the existence of the association. How will you handle a situation like this?
Update: What I want to do is to validate that the username field in the form is indeed a real user, then I could save that user_id back to the profile admin is editing. Here 'return' means the user object returned.
This problem is a good candidate for virtual attributes.
Instead of trying to resolve the username, let the profile model to the job for you.
class Profile
belongs_to :user
# ...
def username
user.try(:username)
end
def username=(value)
self.user = User.find_by_username(value)
end
end
Then in your form
<% form_for #profile do |f| %>
<%= f.text_field :username %>
<% end %>
When submitted, the value for the username field is automatically passed with all the other real activerecord attributes. ActiveRecord will look for the username= setter and will resolve the association.
If the association returns nil (no record exists with given username), then it will set current user_id to nil and validation will fail as expected.
You might want to customize the error code to make more meaningful.
EDIT: Added example.
validate :ensure_username_exists
def username=(value)
self.user_id = User.find_by_username(value) || 0
end
protected
def ensure_username_exists
if user_id == 0 # nil is allowed
errors.add(:user_id, "Username doesn't exists")
return false
end
end
This is a useful reference for Active Record Associations: http://guides.rubyonrails.org/association_basics.html
To check for the existence of the association, just check association.nil?
if #profile.user.nil?
... something ...
end
To check if the username has a valid return, well I'm not quite sure what you mean. Could you expand on that?
Related
In a project came across a requirement wherein a logged in user should be asked specific data based on his company. This specific data would be company specific, and could be mandatory or unique. This is the approach I took.
1. Defined a model to with three fields: Label (string), Mandatory(boolean), Unique(boolean).
2. The admin of the company could then enter the required fields for. e.g: Label => "Employee number", Mandatory => true, Unique => false using a simple form.
3. This data should be asked at the time of creating another record of model Redeemed Coupon for logged in user.
4. So during initialize of the Redeemed Coupon model, reopening the class, and checking the logged in user's company.
class RedeemedCoupon
def initialize(attrs = nil, options = nil)
super
if Merchant.current #this is set in the application controller as thread specific variable
coupon_custom_field = CouponCustomField.where(:merchant_id => Merchant.current).first
if coupon_custom_field and coupon_custom_field.custom_fields.size > 0
coupon_custom_field.custom_fields.each do |custom_field|
class_eval do
field custom_field.label.to_sym, :type => String
attr_accessible custom_field.label.to_sym
end
if custom_field.unique
class_eval do
index custom_field.label.to_sym
#validates_uniqueness_of custom_field.label.to_sym, :case_sensitive => false
end
end
if custom_field.mandatory
class_eval do
#validates_presence_of custom_field.label.to_sym
end
end
end
end
end
end
However the validations validates presence of and uniqueness does not work, with a failure message being given : callback not defined. this is thrown before save, when is_valid? is called object.
TO work around that
Put in custom validation
validate :custom_mandatory_unique
def custom_mandatory_unique
if Merchant.current
coupon_custom_field = CouponCustomField.where(:ira_merchant_id => Merchant.current).first
if coupon_custom_field and coupon_custom_field.custom_fields.size > 0
coupon_custom_field.custom_fields.each do |custom_field|
field_value = self.send(custom_field.label.to_sym)
self.errors.add(custom_field.label.to_sym, "cannot be blank") if !field_value.present? and custom_field.mandatory
if field_value.present? and custom_field.unique
if RedeemedCoupon.where(custom_field.label.to_sym => field_value, :merchant_id => Merchant.current).size > 0
self.errors.add(custom_field.label.to_sym, "already taken")
end
end
end
end
end
end
My questions:
1. Is this the best way of doing it.
2. Are there any gems already present (have searched, however couldnt get any)?
3. How can i use the validation helpers here instead of defining a seperate validate block?
I would define a model that stores the set of attribute mappings that correspond to a company, and an attribute model that holds its values and is associated with your coupon model. Then create a custom validation method in coupon that makes sure all of the require attributes are present based on the company id, and a method that builds them per the company association.
I wanted some advice about how to handle to_param in regards to permalinks
Basically this is what happens.
Create a new company
The company :name is then parameterized and saved as a :permalink in the db
Updating an existing company enables you to change the :permalink
There are validations to ensure user updated :permalink is unique
The problem I'm having is occurring when updating the company's :permalink to something that already exists. The uniqueness validation works which is great, but it changes the params[:id] to the invalid permalink instead of reseting and using the existing params[:id]
When I try to edit the permalink to something else I get a flash validation error of "Name already taken" because it thinks I'm editing the company of the already existing :permalink (company). The URL reflects the change in permalink since my companies_controller.rb is using #company = Company.find_by_permalink[:id])
I wanted to know the best way to handle this issue?
class Companies < ActiveRecord::Base
before_create :set_permalink
before_update :update_permalink
attr_accessible :name, :permalink
validates :name, :permalink, uniqueness: { message: 'already taken' }
def to_param
permalink
end
private
def set_permalink_url
self.permalink = name.parameterize
end
def update_permalink_url
self.permalink = permalink.parameterize
end
end
Apologies if I'm not making too much sense.
Thanks in advance.
you could try to handle this with an after_rollback callback.
after_rollback :restore_permalink
def restore_permalink
self.permalink = permalink_was if permalink_changed?
end
here's how it works : every update / destroy in Rails is wrapped in a transaction. If the save fails, the transaction rollbacks and triggers the callback.
The callback then restores the old value (permalink_was) if it was changed since the record has been loaded.
See ActiveModel::Dirty and ActiveRecord::Transactions for more info.
EDIT
On the other hand, there may be another solution (untested) - just define your accessor like this :
def permalink=( value )
permalink_will_change! unless #permalink == value
#permalink = value
end
This way, the permalink will not be marked as dirty if the new value is identical to the old one, and so AR will not try to update the column.
Explanation:
i don't know on which version of rails it was implemented (it is relatively recent), but here's how "dirtyness" works :
your "standard" (automagically generated) attribute setters basicly call
#{your_attribute}_will_change! before setting the associated
instance variable (even if you set the exact same value than before)
when you call save, ActiveRecords looks for attributes that have changed ("dirty") and builds the SQL UPDATE query using ONLY these attributes (for performance reasons, mostly)
so if you want to avoid your permalink to appear in the query when it is unchanged, i think you have to override the standard setter - or avoid mass-assignment and only set permalink if it has changed
What is the correct way to refer to current_user.user_profile.name when there may or may not be a user_profile record for a particular user?
I have two tables, User (:email) and UserProfile (:name, :company).
User has_one user_profile, and UserProfile belongs_to User
A profile may or may not not exist. (It's optional for users)
In lot of my code and views I want to display the user's name and company, for example current_user.user_profile.name
Of course, if the user has not created their profile yet, current_user.user_profile is nil, so referencing current_user.user_profile.name throws an error.
I know how to do it the wrong way (check for nil before referencing the .name field EVERY time I need the name).
The best I can think of is create a User method called name that does the nil checking so current_user.name returns the name (or "" if there is no profile). But that feels wrong, too, since I have to write a method for every single field I add to user_profile.
I agree with the Law of Demeter answers, but Rails has a convenient way for your User class to delegate :name and other methods to its user_profile:
class User < ActiveRecord::Base
has_one :user_profile
delegate :name, :email, :phone, :height, :etc,
to: :user_profile, allow_nil: true, prefix: :profile
end
Now these:
user.profile_name
user.profile_email
user.profile_phone
will return nil if user.user_profile is nil, and otherwise return user.user_profile.name, user.user_profile.email, and user.user_profile.phone, respectively. But they hide that fact from callers, who only interact with User. You could leave off the prefix option if you'd rather have this:
user.name
user.email
user.phone
In general I'd shy away from strategies that involve exceptions or rescue, unless a nil user_profile is truly something that should not happen. And I'd hate to use andand or try because they push the responsibility of managing the user_profile to callers.
You could do something like this that should work with all attributes of the UserProfile:
# User.rb
def try_this(attribute)
self.user_profile ? self.user_profile.send(attribute) : "Not Available"
end
Then you'd just call
current_user.try_this(:name)
Edit
Dylan's try method also works:
def try_this(attribute)
self.user_profile.try(attribute) || "Not Available"
end
You could use try:
current_user.user_profile.try(:name) || "Not Available"
When you use rescue, there's a risk that you may be rescuing from a different type of error than NoMethodError as a result of user_profile being nil.
current_user.andand.user_profile.name || "Not Available"
The "andand" gem implements null-safe chaining.
As you suggest, creating a User.name method may be preferable (and Demeter would be proud).
You are correct. The best way, according to the Law of Demeter, is to add a method on User with this logic. Whatever needs the user's name shouldn't need to know about user_profile.
Each unit should have only limited knowledge about other units: only units "closely" related to the current unit.
So...
class User < ActiveRecord::Base
def user_name
user_profile ? user_profile.name : ""
end
[...]
end
current_user.user_profile.name rescue "Not Available"
Let's say I have a form_tag in a view gathering a view user input . With this input that I get through the params hash, I want to verify some of htis information such as email phone name.
Can I just verify in the controller or is this a bad thing? I don't plan to save the thing I get to a db or anything, it's just getting put into an email and sent off, but I want to verify these things before it's sent.
Thank you.
EDIT:
Found this http://www.viddler.com/explore/rails3/videos/6/
class Test
include ActiveRecord::Validations
validates_presence_of :name, :email
attr_accessor :name, :email
end
You can use the model for whatever you need that is related to the object and don't need to save it. Keeping stuff like this in the model is desirable in order to keep controller tidy. Say you have a user model:
#controller
#user.new params[:user]
#user.check_valid
#user.save # is optional
#user.rb
def check_valid
!email.blank? and !phone.blank?
end
I have the following database table:
account
-
id
first_name
last_name
email_address
password_md5_hash
last_logged_in
last_ip_address
date_time_created
As you can see, an account record has several fields with values the user will not enter themselves. For instance, the password_md5_hash will be the hashed value of the password they enter, and date_time_created will be set to the current date/time when the record is created.
So, my form will not necessarily contain entry fields for all values, or the values entered will be manipulated before being written to the database (the same would be true if I wanted to strip HTML from a text field before saving to the database).
What would be the proper flow for 1) hashing the password entered in the form and storing that and 2) setting and storing the date_time_created field's value in the database on form submission? These things would take place in the model, no? Can someone provide a code sample or link to one that explains this?
Thanks.
The best way would probably to use an ActiveRecord callback, I will use an accessor here so when you create User.new(:password => "something") in your controller, it'll be assigned to something that's available, but not stored in the database.
class User < ActiveRecord::Base
attr_accessor :password
before_save :hash_password
...
private
def hash_password
attribute(:password_md5_hash => Digest::MD5.hexdigest(password))
end
end
I'm not that an MD5 is the best way to store passwords (I think the general accepted practice is to use a hash and a salt, check out the way restful_authentication or authlogic does it for best practices)
For date_time_created, you should checkout the 'created_at' and 'updated_at' fields that are already given to you. If you need another column, you can use the same callback technique if you need to manipulate it before saving to the database.
When using the attr_accessor, my understanding is you're basically creating a reader and writer, so:
attr_accessor :password
is equal to
#Reader
def password
#password
end
#Writer
def password=(pwd)
write_attribute( :password, pwd )
end
In cases like this where you need to modify the field before saving the model's info to DB, you just need to manually create the writer and modify it how you wish... so in this case take the writer method and use it to create the salt and encrypt... something like:
def password=(pwd)
#password = pwd
return if pwd.blank?
create_new_salt
self.password_md5_hash = User.encrypted_password(self.password, self.salt)
end
Examples of the methods used above:
def create_new_salt
self.salt = "#{object_id}#{rand}"
end
def self.encrypted_password(password, salt)
hashme = password + "morerandomtexthere" + salt
Digest::SHA1.hexdigest(hashme)
end
Also, for the date_time_created, just use "t.timestamps" in your migration and if I'm not mistaken, Rails will handle those for you.