I am trying to display a model attribute only if it is present. If it is not, then a placeholder should be displayed. This is what I've got:
class Person < ActiveRecord::Base
def name
if self.name.blank?
"[You have no name yet]"
else
read_attribute(:name)
end
end
end
However, I am getting a stack level too deep error.
How can this be done?
Thanks for any help.
I agree with Ishank but you can call super to use Rails' getter and then use ActiveSupport's presence method which will return the value if it is present? or otherwise return nil (which will trigger the statement after the ||).
def name
super.presence || "[You have no name yet]"
end
To be clear, stack level too deep is happening because you are checking self.name.blank? - when you use self.name here, that is calling the name method on self (which is the method you are currently in) - so that results in an infinite loop.
This should not be a part of Model. You should write this method in your views.
You can have something like #person.name || "You have no name yet"
You are getting exception stack level too deep because read_attribute[:name] again calls the name method.
Also a thing to keep in mind for using self. According to Ruby style guide:
Avoid self where not required. (It is only required when calling a
self write accessor.)
# bad
def ready?
if self.last_reviewed_at > self.last_updated_at
self.worker.update(self.content, self.options)
self.status = :in_progress
end
self.status == :verified
end
# good
def ready?
if last_reviewed_at > last_updated_at
worker.update(content, options)
self.status = :in_progress
end
status == :verified
end
Can you try this for stack too deep?
def name
if self.first.name.blank?
"[You have no name yet]"
else
read_attribute[:name]
end
end
Personally, i always do something like this, using self[:name] as a way of accessing the database rather than the .name method:
def name
if self[:name].blank?
"[You have no name yet]"
else
self[:name]
end
end
I am still using rails 2.2, so this may function differently for you.
Having said that, a cleaner and more transparent way to do it is to set "[You have no name yet]" as the default value for the name column in the database. Then you don't need to override the accessor method, which always feels a bit dirty to me.
It is not a good practise to include presentation logic in data models.
You should instead use decorators, view objects, or similar, or just do it in the view, but not in the model.
Examples using the Draper gem:
class PersonDecorator < Draper::Decorator
delegate_all
def name
object.name.presence || I18n.t('warnings.no_name_yet')
end
end
In the view:
<%= #person.name.presence || I18n.t('warnings.no_name_yet') %>
See the "Introduce View Objects" section in http://blog.codeclimate.com/blog/2012/10/17/7-ways-to-decompose-fat-activerecord-models/.
Related
This is the weirdest thing ever happened to me with ruby/rails.
I have a model, Store, which has_many Balances. And I have a method that gives me the default balance based on the store's currency.
Store model.
class Store < ActiveRecord::Base
has_many :balances, as: :balanceable, dependent: :destroy
def default_balance
#puts self.inspect <- weird part.
balances.where(currency: self.currency)[0]
end
...
end
Balance model.
class Balance < ActiveRecord::Base
belongs_to :balanceable, :polymorphic => true
...
end
Ok, so then in the Balance controller I have the show action, that will give me a specific balance or the default one.
Balance controller.
class Api::Stores::BalancesController < Api::Stores::BaseController
before_filter :load_store
# Returns a specific alert
# +URL+:: GET /api/stores/:store_id/balances/:id
def show
#puts #store.inspect <- weird part.
#balance = (params[:id] == "default") ? #store.default_balance : Balance.find(params[:id])
respond_with #balance, :api_template => :default
end
...
private
# Provides a shortcut to access the current store
def load_store
#store = Store.find(params[:store_id])
authorize! :manage, #store
end
end
Now here is where the weird part comes...
If I make a call to the show action; for example:
GET /api/stores/148/balances/default
It returns null (because the currency was set as null, and there is no Balance with null currency), and the SQL query generated is:
SELECT `balances`.* FROM `balances` WHERE `balances`.`balanceable_id` = 148 AND `balances`.`balanceable_type` = 'Store' AND `balances`.`currency` IS NULL
So I DON'T know why... it is setting the currency as NULL. BUT if in any where in that process I put
puts #store.inspect
or inside the default_balance method:
puts self.inspect
it magically works!!!.
So I don't know why is that happening?... It seems like the store object is not getting loaded until I "inspect" it or something like that.
Thanks
Sam and Adrien are on the right path.
ActiveRecord overrides method_missing to add a whole bunch of dynamic methods including the accessors for the column-backed attributes like Store#currency. While I'm glossing over a lot, suffice it to say that when the logic is invoked then the dynamic class/instance methods are added to the Store class/instances so that subsequent calls no longer require the method_missing hook.
When YOU overrode method_missing without calling super, you effectively disabled this functionality. Fortunately, this functionality can be invoked by other means, one of which you tripped upon when you called store#inspect.
By adding the call to super, you simply assured that ActiveRecord's dynamic methods are always added to the class when they're needed.
OK finally after a lot of debugging, I found the reason...
In the Store model I have a method_missing method and I had it like this:
def method_missing method_name, *args
if method_name =~ /^(\w+)_togo$/
send($1, *args).where(togo: true)
elsif method_name =~ /^(\w+)_tostay$/
send($1, *args).where(tostay: true)
end
end
So when I was calling self.currency it went first to the method_missing and then returned null. What I was missing here was the super call.
def method_missing method_name, *args
if method_name =~ /^(\w+)_togo$/
send($1, *args).where(togo: true)
elsif method_name =~ /^(\w+)_tostay$/
send($1, *args).where(tostay: true)
else
super
end
end
But I continue wondering why after I had called puts #store.inspect or puts self.inspect it worked well?. I mean, why in that case that super call wasn't needed?
I created this helper method. In my view I call it with days_left(duedate). I dont really like my helper. Is it possible to use it with self. Since I dont really know how self is being used. Is it the same as this in java or javascript? What object is it related to? Feel free to tune this method. Thx for your time!
def days_left(duedate)
(if duedate.date == Date.today
"Today"
elsif duedate.date-Date.today < 1
"expired"
elsif duedate.date-Date.today == 1
"Tomorrow"
else
"#{(duedate.date-Date.today).to_i}"
end).to_s.html_safe
end
You might try moving this method to your model.
This would be similar to adding a 'full_name' method to a model with the attributes 'first_name' and 'last_name.' You wouldn't store 'full_name' separately in your database, because that would result in redundant, denormalized data.
For example:
class Employee < ActiveRecord::Base
def full_name
"#{first_name} #{last_name}"
end
end
So you could similarly add the 'days_left' method to your model, which fits there because it's adding a friendlier version of an existing data attribute.
I have a helper method which checks whether the collection of objects is empty? If not then it checks each one to make sure that the the existing event_id is not the #current_event.id.
Here is my crack at it:
def build_answers(question)
if question.answers.empty?
return question.answers.build
else
question.answers.each do |a|
if a.event_id != #current_event.id
return question.answers.build
end
end
end
end
Update: This helper method sets the form to build new children objects if the conditions pass. I've updated the example above. btw, it doesn't need to be a single line. I just wanted something cleaner than what I have above.
Without knowing what you're actually doing inside the blocks, it's difficult to give the best solution.
For the most part, all you could really do is select before executing the logic on the filtered collection, rather than testing the logic in the block.
e.g.
uncurrent_answers = questions.answers.select{|a| a.event_id != #current_event.id}
uncurrent_answers.each do |a|
#do whatever
end
IMHO it's a little bit neater, and perhaps more rubyish..
Well, I don't know why would you want to put conditions into a single line, but the else block could be rewritten into this:
question.answers.select {|answer| answer.event_id != #current_event.id }.each
{|ans| #.. logic with answer here }
I think you current method is responsible for too many things, my idea is to create a clase with a single responsibility of building answers. That would make your code more readable and also easy to test. A posible implementation would look something like :
def build_answers(question)
AnswerBuilder.build(question.answers, #current_event)
end
class AnswerBuilder
def initialize(answers, current_event)
#answers = answers
#current_event = current_event
end
def self.build(answers, current_event)
new(answers, current_event).build
end
def build
if answers.empty?
answers.build
else
create_allowed_answers
else
end
private
attr_reader :answers, :current_event
def create_allowed_answers
answers.each do |a|
if a.event_id != current_event.id
return answers.build
end
end
end
end
So I know how to override the default getters for attributes of an ActiveRecord object using
def custom_getter
return self[:custom_getter] || some_default_value
end
I'm trying to achieve the same thing however for a belongs to association. For instance.
class Foo < AR
belongs_to :bar
def bar
return self[:bar] || Bar.last
end
end
class Bar < AR
has_one :foo
end
When I say:
f = Foo.last
I'd like to have the method f.bar return the last Bar, rather than nil if that association doesn't exist yet.
This doesn't work however. The reason is that self[:bar] is always undefined. It's actually self[:bar_id].
I can do something naive like:
def bar
if self[:bar_id]
return Bar.find(self[:bar_id])
else
return Bar.last
end
end
However this will always make a db call, even if Bar has already been fetched, which is certainly not ideal.
Does anyone have an insight as to how I might have a relationship such that the belongs_to attribute is only loaded once and has a default value if not set.
alias_method is your friend here.
alias_method :original_bar, :bar
def bar
self.original_bar || Bar.last
end
The way this works is that you alias the default "bar" method as "original bar" and then implement your own version of "bar". If the call to original_bar returns nil then you return the last Bar instance instead.
i found that using "super" is the best way
def bar
super || Bar.last
end
I hope this helps you :D
Randy's answer is spot on, but there's an easier way to write it, using alias_method_chain:
def bar_with_default_find
self.bar_without_default_find || Bar.last
end
alias_method_chain :bar, :default_find
That creates two methods - bar_with_default_find and bar_without_default_find and aliases bar to the with method. That way you can explicitly call one or the other, or just leave the defaults as is.
Building upon other answers here, you may also want to handle assignment operations as well.
Alias method:
alias_method :original_bar, :bar
alias_method :original_bar=, :bar=
def bar
self.original_bar ||= Bar.last
end
Super method:
def bar
super || bar = Bar.last
end
Where this was useful for me was when using Bar.find_or_initialize_by which meant the record wasn't always persisted, and also any non-persisted changes would reflect on the parent record as well.
For example, if I have a user model and I need to validate login only (which can happen when validating a form via ajax), it would be great if I use the same model validations defined in the User model without actually instantiating a User instance.
So in the controller I'd be able to write code like
User.valid_attribute?(:login, "login value")
Is there anyway I can do this?
Since validations operate on instances (and they use the errors attribute of an instance as a container for error messages), you can't use them without having the object instantiated. Having said that, you can hide this needed behaviour into a class method:
class User < ActiveRecord::Base
def self.valid_attribute?(attr, value)
mock = self.new(attr => value)
unless mock.valid?
return mock.errors.has_key?(attr)
end
true
end
end
Now, you can call
User.valid_attribute?(:login, "login value")
just as you intended.
(Ideally, you'd include that class method directly into the ActiveRecord::Base so it would be available to every model.)
Thank you Milan for your suggestion. Inspired by it I created a simple module one can use to add this functionality to any class. Note that the original Milans suggestion has a logic error as line:
return mock.errors.has_key?(attr)
should clearly be:
return (not mock.errors.has_key?(attr))
I've tested my solution and it should work, but ofc I give no guarantees. And here's my glorious solution. Basically a 2-liner if you take away the module stuff.. It accepts method names as stings or symbols.
module SingleAttributeValidation
def self.included(klass)
klass.extend(ClassMethods)
end
module ClassMethods
def valid_attribute?(attr, value)
mock = self.new(attr => value)
(not mock.valid?) && (not mock.errors.has_key?(attr.class == Symbol ? attr : attr.to_sym))
end
end
end
To use your standard validation routines:
User.new(:login => 'login_value').valid?
If that does not work for you, build a custom class method for this:
class User < ActiveRecord::Base
validate do |user|
user.errors.add('existing') unless User.valid_login?(user.login)
end
def self.valid_login?(login)
# your validation here
!User.exist?(:login=> login)
end
end
I had a hell of a time getting this to work in Rails 3.1. This finally worked. (Not sure if it's the best way to do it, I'm kind of a newb.). The problem I was having was that value was being set to type ActiveSupport::SafeBuffer, and was failing validation.
def self.valid_attribute?(attr, value)
mock = User.new(attr => "#{value}") # Rails3 SafeBuffer messes up validation
unless mock.valid?
return (not mock.errors.messages.has_key?(attr))
end
return true
end
I have gone with the custom class solution but I just wanted to make sure there was no better way
class ModelValidator
def self.validate_atrribute(klass, attribute, value)
obj = Klass.new
obj.send("#{attribute}=", value)
obj.valid?
errors = obj.errors.on(attribute).to_a
return (errors.length > 0), errors
end
end
and I can use it like
valid, errors = ModelValidator.validate_attribute(User, "login", "humanzz")
class User < ActiveRecord::Base
validates_each :login do |record, attr, value|
record.errors.add attr, 'error message here' unless User.valid_login?(value)
end
def self.valid_login?(login)
# do validation
end
end
Just call User.valid_login?(login) to see if login itself is valid
An implementation of the 'valid_attribute' method you are suggesting:
class ActiveRecord:Base
def self.valid_attribute?(attribute, value)
instance = new
instance[attribute] = value
instance.valid?
list_of_errors = instance.errors.instance_variable_get('#errors')[attribute]
list_of_errors && list_of_errors.size == 0
end
end
How about:
User.columns_hash.has_key?('login')