Accessing model properties in Rails - ruby-on-rails

So basically I have a controller. something like this
def show
#user = User.find[:params[id]]
#code to show in a view
end
User has properties such as name, address, gender etc. How can I access these properties in the model? Can I overload the model accesser for name for example and replace it with my own value or concatenate something to it. Like in the show.html.erb view for this method I might want to concatenate the user's name with 'Mr.' or 'Mrs.' depending upon the gender? How is it possible?

I would hesitate to override the attributes, and instead add to the model like this:
def titled_name
"#{title} #{name}"
end
However, you can access the fields directly like this:
def name
"#{title} #{self[:name]}"
end

You can create virtual attributes within your model to represent these structures.
There is a railscast on this very subject but in summary you can do something like this in your model
def full_name
[first_name, last_name].join(' ')
end
def full_name=(name)
split = name.split(' ', 2)
self.first_name = split.first
self.last_name = split.last
end
If you wish to explicitly change the value of an attribute when reading or writing then you can use the read_attribute or write_attribute methods. (Although I believe that these may be deprecated).
These work by replacing the accessor method of the attribute with your own. As an example, a branch identifier field can be entered as either xxxxxx or xx-xx-xx. So you can change your branch_identifier= method to remove the hyphens when the data is stored in the database. This can be achieved like so
def branch_identifier=(value)
write_attribute(:branch_identifier, value.gsub(/-/, '')) unless value.blank?
end

If you are accessing data stored directly in the database you can do this in you view:
<%= #user.firstname %>
<%= #user.gender %>
etc.
If you need to build custom representations of the data, then you will either need to create helpers, or extend the model (as above).

I tend to use helper methods added to the model for things like that:
def formatted_name
"#{title} #{first_name} #{last_name}"
end
(Edit previous post. Looked back at my code and realized helpers are supposed to be for presentation-related (mark-up) stuff only.)
(Edit again to remove left-over parameter... Geez, not enough coffee this morning.)
(Edit again to replace $ with #... Perhaps I should just remove this one huh?)

You can easily overload the attributes as you suggest.
i.e. if name is a field in the users database table, you can do:
def name
"#{title} #{read_attribute[:name]}"
end
The read_attribute function will return the database column value for the field.
Caveat: I am not sure this is a good idea. If you want a method that displays model data in a modified way, I would be tempted not to overload the default methods, and call them something different - this will avoid a certain level of obfuscation.
Documentation here: http://api.rubyonrails.org/classes/ActiveRecord/Base.html (under 'Overwriting default accessors')

in http://api.rubyonrails.org/classes/ActionController/Base.html
search for
Overwriting default accessors

Related

How to get the parent objects class name from an attribute

Is it possible to return the parent object of a given attribute?
Example
a = User.birthdate
a.parent_object ... should return the user record that is the parent of the birthdate attribute
A better example?
Helper
def item_grade(subject, obj)
obj.scale.grades.find(subject.grade_id).name # would return something like "Pass", "Fail", "Good Job"
end
In the view
item_grade(#course.subject, #course)
This approach requires two options to be passed to the helper. It seems I should be able to pass #course.subject and then get the parent object from that
Helper
def item_grade(subject)
a = subject.parent_object.scale
a.grades.find(subject.grade_id).name
end
View
item_grade(#course.subject)
This approach requires two options to be passed to the helper.
You can remove some duplication by doing this, for example.
def item_grade(obj, property)
obj.scale.grades.find(obj.send(property).grade_id).name
end
item_grade(#course, :subject)
Now you don't have to repeat #course in the call.
Having to pass two parameters is much less harmful than any sort of hackery you can come up with (thanks #muistooshort). There's no built-in way to do this.

Bind paramerts in Object if those present there

I am receiving an API call at my server with parameters
first_name , :last_name , :age
etc
I want to bind those params to my object against which user is having attribute with same name , like i want to have these in user[first_name] , user[:last_name]
so that I can just put the complete user object into database in following way ,
User.new(params[:user]) or User.new(some_hash)
I dont want to use the following ,
User.new(:first_name=>params[:first_name],:last_name=>params[:last_name])
thanks in advance for you help :)
Something like this may work:
user = User.new
params.each do |key,value|
user[key] = value if user.attribute_names.include?(key.to_s)
end
Note, however, that you should protect sensitive attributes of your User model with attr_protected or attr_accessible in this case.
Writing that functionality into User.initialize can take care of this:
def initialize(args={})
args.each_with_key do |key,val|
instance_variable_set("##{key}", val)
end
end
This of course has no validation and does not protect your object from bad data. For example, if you want to make sure only valid accessible attributes are being set, add if respond_to? key to end end of line 3.

hash instead of id

I want to use auto-generated hash'es instead of auto-incremented integers in my activerecords as primary keys. This raises two questions:
how to perform this generation in
most efficient way?
how to handle possibility that
generated hash exists already in
table?
Regards,
Mateusz
If you want this because you don't want to show the id in the web url. You can use a gem like https://github.com/peterhellberg/hashids.rb
It creates a reversible hash from your database id so the hash does not need to be stored in the database.
Use it in your models to_param method.
class MyModel < ActiveRecord::Base
def to_param
Hashids.new("salt").encode(id)
end
end
And decode the hash before finding the record from the database.
def show
id = Hashids.new("salt").decode(params[:id]).try(:first)
record = MyModel.find(id)
end
It might not be exactly what you asked for. But I had a similar problem where I wanted to use a hash instead of the ID in the URL. My solution follows
I added a column in my table called privatelink
In my model i wrote:
#changes the url to use privatelink instead of the id
def to_param
privatelink
end
#calls the private method set_privatelink
before_create :set_privatelink
private
#generates a unique hash looking something like this: c24bea1693d9e56a1878cb83f252fba05532d9d0
def set_privatelink
self.privatelink = Digest::SHA1.hexdigest([Time.now, rand].join)
end
Source:
Railcast #63 Model Name in URL - shows how to use the to_param method
It's not a duplicate of your question, but i think you want to do the same thing :
Assigning Each User a Unique 100 character Hash in Ruby on Rails
When using Oracle i had the case where I wanted to create the ID ourselves (and not use a sequence), and in this post i provide the details how i did that. In short the code:
# a small patch as proposed by the author of OracleEnhancedAdapter: http://blog.rayapps.com/2008/05/13/activerecord-oracle-enhanced-adapter/#comment-240
# if a ActiveRecord model has a sequence with name "autogenerated", the id will not be filled in from any sequence
ActiveRecord::ConnectionAdapters::OracleEnhancedAdapter.class_eval do
alias_method :orig_next_sequence_value, :next_sequence_value
def next_sequence_value(sequence_name)
if sequence_name == 'autogenerated'
# we assume id must have gotten a good value before insert!
id
else
orig_next_sequence_value(sequence_name)
end
end
end
while this solution is specific to Oracle-enhanced, i am assuming inside the other adapters you can overrule the same method (next_sequence_value).

update_attributes field tweaks

So I've got an edit page that has butt-load of editable fields on it...simple update...
#patient.update_attributes(params[:patient])...everything's great, except....
I've got one field out of these 20 that I need to tweak a little before it's ready for the db and it would seem I either need to do
two trips
#patient.update_attributes(params[:patient])
#patient.update_attribute( :field=>'blah')
or set them all individually
patient.update_attributes(:field1=>'asdf', :field2=>'sdfg',:field3=>'dfgh', etc...)
Am I missing a way to do this is one swoop?
What's the attribute you need to tweak? There's two ways to do this:
Either massage the params before you send them to the update_attribute method:
I'm just giving an example here if you wanted to underscore one of the values:
params[:patient][:my_tweak_attribute].gsub!(" ", "_")
#patient.update_attributes(params[:patient])
Then there's the preferred way of doing your tweaking in a before_save or before_update callback in your model:
class Patient < ActiveRecord::Base
before_update :fix_my_tweak_attribute, :if => :my_tweak_attribute_changed?
protected
def fix_my_tweak_attribute
self.my_tweak_attribute.gsub!(" ", "_")
end
end
This keeps your controller clean of code that it probably doesn't really need.
If you just need to add a new param that didn't get sent by the form you can do it in the controller like this:
params[:patient][:updated_by_id] = current_user.id
#patient.update_attributes(params[:patient])
Assuming current_user is defined for you somewhere (again, just an example)
You can create a virtual attribute for that field. Say the field is :name. You create a function in your Patient model like :
def name
self[:name] = self[:name] * 2
end
And of course, you do your things inside that function :) Instaed of self[:name], you can also use read_attribute(:name).

Human readable URL causes a problem in Ruby on Rails

I have a basic CRUD with "Company" model. To make the company name show up, I did
def to_param
name.parameterize
end
Then I accessed http://localhost:3000/companies/american-express which runs show action in the companies controller.
Obviously this doesn't work because the show method is as following:
def show
#company = Company.find_by_id(params[:id])
end
The params[:id] is american-express. This string is not stored anywhere.
Do I need to store the short string (i.e., "american-express") in the database when I save the record? Or is there any way to retrieve the company data without saving the string in the database?
Send the ID with the parameterized value;
def to_param
new_record? ? super : "#{id}-#{name}"
end
And when you collect the data in the show method, you can use the whole parameter;
def show
#company = Company.find("12-american-express"); // equals to find(12)
end
There's also a plugin called permalink_fu, which you can read more about here.
I think friendly_id is more usable.
I do something similar with the Category model in my blog software. If you can guarantee that the only conversion the parameterize method is doing to your company names is replacing space characters with dashes then you can simply do the inverse:
def show
#company = Company.find_by_name(params[:id].gsub(/-/, ' '))
end
Try permalink_fu plugin, which creates SEO friendly URLs in rails
http://github.com/technoweenie/permalink_fu
cheers
sameera
I would suggest the friendly_id gem also.
It gives you the flexibility to use persited permalink slugs, also strip diacritics, convert to full ASCII etc.
Basically it makes your life a lot easier, and you get "true" permalinks (no to_param and the id workaround needed) with little effort.
Oh and did i mention that the permalinks are also versioned, so you can make old outdated permalinks to redirect to the current one? :)

Resources