ActiveRecord Relationships : undefined method for nil:NilClass - ruby-on-rails

Consider the following:
class Manager < ActiveRecord::Base
has_many :employees
end
class Employee < ActiveRecord::Base
belongs_to :manager
end
employee = Employee.first
puts employee.manager.name
If for some reason an employee does not have a manager I get this:
undefined method `name' for nil:NilClass
Which makes sense. However, is there a clean/suggested way to handle this, so I don't always have to check and see if an employee actually has a manager before I ask for the manager's name?

Try:
puts employee.manager.name unless employee.manager.nil?
Or:
puts (employee.manager.nil? ? "No manager" : employee.manager.name)
Which is equivalent in this case to:
puts (employee.manager ? employee.manager.name : "No manager")
(Equivalent as long as employee.manager can't return false.)

Don't try to be too clever. If you're going to use this repeatedly, make it a function.
class Employee
def manager_name
manager.try(:name).to_s # force empty string for nil
end
end

You can check using the has_attribute? method:
employee.has_attribute? :manager
So something like:
puts employee.manager.name if employee.has_attribute? :manager

I tend to prefer puts employee.manager.try(:name)
The try method returns nil if it was called on nil, but will execute the method properly if manager wasn't nil.
If you want default text:
puts employee.manager.try(:name) || "No Manager"

Related

Undefined local variable inside a Struct in rails

I have a class as follow:
class TempUser < Struct.new(:user)
user.attributes.keys.each do |attribute|
delegate attribute, to: :user
end
end
When I try to use it in rails console I get:
$> tmp = TempUser.new(User.last)
NameError: undefined local variable or method `user' for TempUser:Class
But if I replace user.attributes by User.new.attributes it works.
It's not due to the loop, actually I get the same error by simply doing:
class TempUser < Struct.new(:user)
test = user
end
I guess I missed something but I have no clue what it is, any idea?
EDIT
It might not be relevant in my example, but for my needs I will need the users' attributes, so I can't stay with something like User.columns.map(&:name).
You have this error, because when ruby parse this class it understand user as class variable that is clear from error message: 'user' for TempUser:Class
And :user it is an instance variable:
TempUser = Struct.new(:user)
tmp = TempUser.new(User.last)
=> #<struct TempUser user=#<User id: 1>>
All users have the same attributes, I think you can use something like this: User.columns.map(&:name)
EDIT
If each user may have different attributes, I think you can do it with method_missing
TempUser = Struct.new(:user) do
def method_missing(m, *args, &block)
return user.send(m) if user.respond_to?(m)
super
end
end

rails find_by_looking_in_every_field ...or... "why is my function missing?"

This is a two parter. I'd be happy with either of the approaches below or other suggestions.
I'd like to be able to retrieve records/objects using my model by passing it a search term and having it look for that search term in any field in the model, or any field that the model deems viable. So, as an example:
class Product < ActiveRecord::Base
...
def search_all_fields(search_term)
return search_term.length == 0 ? nil : Product.where("serial_number like :find_me", { :find_me => search_term })
end
end
This is from a Product model. The same function in the Company model might look like:
class Company < ActiveRecord::Base
...
def search_all_fields(search_term)
return search_term.length == 0 ? nil : Company.where("customer_number like :find_me or name like :find_me", { :find_me => search_term })
end
end
I would love a "railsy" way to do this, such as "find_by_looking_everywhere" but I haven't been able to find such a thing. I've found lots of suggestions for searching a single field for multiple values, but not searching multiple fields for a single value. So that's "Part 1," is there a "railsy" way to do this?
"Part 2" ... using the code above, why am I getting the following exception?
undefined method `search_all_fields` for #<Class:0xa38f2ac>
I'm calling the methods using #products = Product.search_all_fields("xy3445") or #companies = Company.search_all_fields("high")?? The trace shows that the exception is being raised by just a generic class. It doesn't say #<Product...> or #<Company...>
I'm a little lost... any and all help appreciated.
Thanks, gang.
Your method is an instance method (the Model need to be instanciated to access this method). You need a Class method (means you don't need an instance of Company to call it, like the methods where(), find() etc).
class Company < ActiveRecord::Base
def say_hello
return "Hello world!"
end
end
This method say_hello can only be called from an instance of Company (instance method):
company = Company.first
company.say_hello #=> "Hello world!"
# but this will raise a NoMethodError:
Company.say_hello #=> NoMethodError
In order to define a method as a class method, you can do the following:
class Company < ActiveRecord::Base
def self.say_hello
return "Hello world!"
end
# OR you can use the name of the model instead of the self keyword:
def Company.say_hello
return "HEllo World!"
end
end
Now you can do:
Company.say_hello
#=> "HEllo World!"
# but this will fail:
Company.first.say_hello
#=> NoMethodError

Rails delegated attribute not in #attributes, read_attribute returns nil

I have a model which delegates some methods and attributes to a different model, let's say
class ModelOne < ActiveRecord::Base
# this model has some_property column in database
end
and
class ModelTwo < ActiveRecord::Base
belongs_to :model_one
delegate :some_property, :to => :model_one
end
The problem is that I can access 'some_property' by calling the method but not through read_attribute.
> obj1 = ModelTwo.last
> obj1.some_property
=> "some value"
> obj1.read_attribute :some_property
=> nil
> obj1.inspect
=> "#ModelTwo ... , ... , some_property: nil "
It is possible to set this attribute:
> obj1.some_property = "some value"
> obj1.inspect
=> "#ModelTwo ... , ... , some_property: "some value" "
So I can access delegated attribute by calling it but not by read_attribute or through inspect. Is there any chance to get the attribute value by read_attribute?
Maybe you should try to override read_attribute method. I am not using read_attribute, but in a similar scenario I had to override the hash method:
def [](key)
value = super
return value if value
if super(key+"_id")
begin
send(key)
rescue NoMethodError
end
end
end
Its not pretty and there are possibly security issues with calling send(key) without more accurate validation.
If you look into the implementation of read_attribute:
# File activerecord/lib/active_record/attribute_methods/read.rb, line 128
def read_attribute(attr_name)
self.class.type_cast_attribute(attr_name, #attributes, #attributes_cache)
end
is not based on the attribute accessor (in your case some_property) but is accessing the #attributes instance variable directly, which makes sense because read_attribute is a lower level api that allows you to bypass the accessor. Thus, you can't do what you're trying.
This may not be the answer you're looking for, but what I'd be reconsidering in your design is why you need to access your attribute via read_attribute. If you show us where and how you're using read_attribute, I'll be happy to try and help you farther.

rails, how to pass self in function

message and user. my message belongs_to user and user has_many messages.
in one of my views, i call something like
current_user.home_messages?
and in my user model, i have...
def home_messages?
Message.any_messages_for
end
and lastly in my message model, i have
scope :any_messages_for
def self.any_messages_for
Message.where("to_id = ?", self.id).exists?
end
ive been trying to get the current_users id in my message model. i could pass in current_user as a parameter from my view on top but since im doing
current_user.home_messages?
i thought it would be better if i used self. but how do i go about referring to it correctly?
thank you.
You could use a lambda. In your Message model:
scope :any_messages_for, lambda {|user| where('user_id = ?', user.id)}
This would work like so:
Message.any_messages_for(current_user)
And you could add a method to your user model to return true if any messages are found. In this case you use an instance method and pass in the instance as self:
def home_messages?
return true if Message.any_messages_for(self)
end
But really, I'd just do something like this in the User model without having to write any of the above. This uses a Rails method that is created when declaring :has_many and :belongs_to associations:
def home_messages?
return true if self.messages.any?
end
You can do either of the following
def self.any_messages_for(id) #This is a class method
Message.where("to_id = ?", id).exists?
end
to call above method you have to do
User.any_messages_for(current_user.id) #I am assuming any_messages_for is in `User` Model
OR
def any_messages_for #This is a instance method
Message.where("to_id = ?", self.id).exists?
end
to call above method you have to do
current_user.any_messages_for
This stuff in your Message class doesn't make a lot of sense:
scope :any_messages_for
def self.any_messages_for
Message.where("to_id = ?", self.id).exists?
end
The scope macro defines a class method on its own and there should be another argument to it as well; also, scopes are meant to define, more or less, a canned set of query parameters so your any_messages_for method isn't very scopeish; I think you should get rid of scope :any_messages_for.
In your any_messages_for class method, self will be the class itself so self.id won't be a user ID and so it won't be useful as a placeholder value in your where.
You should have something more like this in Message:
def self.any_messages_for(user)
where('to_id = ?', user.id).exists?
# or exists?(:to_id => user.id)
end
And then in User:
def home_messages?
Message.any_messages_for(self)
end
Once all that's sorted out, you can say current_user.home_messages?.

Rails Validation Error

While trying to add an error message using add_to_base, I am getting an undefined method 'errors' message. I am defining it in my model. Am I supposed to include any other file in order to access the errors variable.
Model File - I am defining it inside a method
self.errors.add_to_base("Invalid Name")
Error Message
undefined method `errors' for #<Class:0x0000010179d7a0>
I tried by calling it as errors.add_to_base("Invalid Name") also but still getting the same error.
Thanks.
you should call it in your callback method, something like following
def validate
if !self.interests.blank? && !self.interests.match("<").nil?
self.errors.add :base, 'Please ensure that Interest field do not contain HTML(< and >) tags'
end
end
I suspect that you have defined your method as a class method, instead of as an instance method.
Class methods look like this on ruby:
def self.checkFoo()
...
end
Instance methods looks like this:
def checkFoo()
...
end
Check that your checkFoo method is an instance method, and then use it like this:
class MyModel < ActiveRecord::Base
validate :foo
private
def checkFoo()
self.errors.add etc..
end
end
Typically used the validation callbacks, model errors are used both to cause the prospective database save to fail and to set up a contextual error messages for the end-user. The add_to_base variant is intended for general, non-specific error conditions (i.e. not associated with a particular model attribute).
class MyModel < ActiveRecord::Base
validate do |my_model|
if my_model.some_attribute.blank? # For example
my_model.errors.add :my_model, "must be filled in"
end
end
end
Subsequently
#my_model = MyModel.create(:some_attribute => "")
would fail and the #my_model.errors.full_messages array would contain
[ ..., "Some_attribute must be filled in", ... ]
There is however a shorthand for the above example as follows
class MyModel < ActiveRecord::Base
validates_presence_of :some_attribute, :msg => "must be filled in"
end
Looks like your 'self.errors.add_to_base("Invalid Name")' doesn't have any problem
But your model should inherit from ActiveRecord::Base
cheers
sameera

Resources