Rails ActiveRecord: Where to define method? - ruby-on-rails

I have a question about ActiveRecord of Rails.
For example I have a Service model, and Service has name as a column.
This is app/model/service.rb
class Service < ActiveRecord::Base
def self.class_method
puts 'This is class method'
end
def instance_method
puts 'This is instance method'
end
end
Then I can do,
Service.class_method #=> 'This is class method'
Service.find(1).instance_method #=> 'This is instance method'
This is easy. But when I get ActiveRecord Instance in Array, for example
Service.where(id: [1,2,3])
and I need method like,
Service.where(id: [1,2,3]).sample_method
def sample_method
self.length
end
but how and where to define method for Active Record Array? I want to handle this object just like other Service class or instances.
Thanks.

First of all, where returns an ActiveRecord::Relation object, not an array. It behaves similar to an array, but inherits a load more methods to process data/ construct SQL for querying a database.
If you want to add additional functionality to the ActiveRecord::Relation class, you can do something like this:
class ActiveRecord::Relation
def your_method
# do something
end
end
This will need to reside somewhere meaningful, such as the lib directory or config/initializers.
This should allow you to do something like
Service.where(id: [1,2,3]).your_method
You can do something similar for any Ruby class, like the Hash, or Array class as well.
However, there's almost ALWAYS a better solution than extending/ overriding Rails/ Ruby source classes...

Your methods like get_count and get_name are a bit pointless... Why not just do:
Service.count
Service.find(1).name
Class methods like count, and instance methods like name (i.e. database column names) are all public - so you don't need to define your own getter methods.
As for your second example, you could just write the following:
Service.where(id: [1,2,3]).map{ |s| s.name }
Or equivalently:
Service.where(id: [1,2,3]).map(&:name)
But the following is actually more efficient, since it is performing the calculation in SQL rather than in ruby. (If you're confused what I mean by that, try running both versions and compare what SQL is generated, in the log):
Service.where(id: [1,2,3]).pluck(:name)

Related

Ruby: metaprogramming methods based in model attribute

I'm trying to expose some methods to a class based on the values in its order attribute, which can be something like ['top', 'bottom', 'right', 'lower-right'].
First, my class deletes from that array anything that responds blank like so:
def order
order.split(' ').delete_if do |o|
send(o).blank? if respond_to? o
end
end
After that, I want to "expose" top (for example's sake) as a method for the class. I've tried stuff like this in the initialize method:
order.each do |o|
V1_ATTRIBUTES << o.to_sym
define_method(o) do |a|
send(a).blank? ? '' : send(a)
end
end
But o isn't defined when the class isn't instantialised, a la:
<NameError: undefined local variable or method `o' for #<Class:#<APIDocument:0x007fb8123609f8>>>
Anyone have any success with a better way to get this result?
Note: I know it's not great practice allowing the user to write instance method names, but in this case security and breakages aren't a concern.
You can define singleton methods (per-object methods).
def order
orders.split(' ').map{|o| send(o)}.compact.each do |o|
singleton_class.send(:define_method, o) do
send(o).to_s
end
end
end
But as you can see, this only messes up your code. So don't abuse metaprogramming, use it wisely.

ruby string formatting in rails

So the goal is to turn for instance "ProductCustomer", which comes from the class, into "product customer".
I used to have this:
notification.notifiable.model_name.human.downcase
It didn't work out of course, since if the notifiable is nil it breaks. I don't
want to use try or something similar since it can be solved with using notifiable_type.
So now I changed to this:
notification.notifiable_type.split(/(?=[A-Z])/).join(' ').downcase
But this is way too complex to use every time in the view. So either I would like to define this as a view helper or using some ruby formatting method if there is a simple one.
Can somebody tell me what the Rails convention is in this case? If it's a helper, how does the method looks like and where should I put it?
Options:
Initializer
/your_app/config/initializers/my_initializer.rb
module MyModule
def human_model_name
self.class.to_s.tableize.singularize.humanize.downcase
end
end
ActiveRecord::Base.send(:include, MyModule)
Including MyModule in ActiveRecord::Base will add human_model_name in all ActiveRecord instances. So, you will be able to do...
user.human_model_name #=> user
notification.human_model_name #=> notification
notification.notifiable.human_model_name #=> product customer
any_active_record_instance.human_model_name #=> etc.
To avoid exceptions when notifiable is nil, you can use try method.
notification.try(:notifiable).try(:human_model_name)
A cleaner way can be use delegate
class Notification < ActiveRecord::Base
delegate :human_model_name, to: :notifiable, prefix: true, allow_nil: true
end
Then, you can do:
notification.notifiable_human_model_name # if notifiable is nil will return nil as result instead of an exception
A simple method in your Notification model
class Notification < ActiveRecord::Base
def human_notifable_name
return unless self.notifiable # to avoid exception with nil notifiable
self.notifiable.class.to_s.tableize.singularize.humanize.downcase
end
end
Then...
notification.human_notifable_name
View Helper (If you think this is a view related method only)
module ApplicationHelper # or NotificationHelper
def human_model_name(instance)
return unless instance # to avoid exception with nil instance
instance.class.to_s.tableize.singularize.humanize.downcase
end
end
Then, in your view...
<%= human_model_name(notification.notifiable) %>
Either option is fine. I would use one or the other depending on the case. In this case, I would use the first option. I think you are adding behaviour that can be useful in any model. I mean your method is not directly related with something about notifications. In a more generic way you want a method to return the class name of an ActiveRecord's instance. Today you want the model name of the notifiable ActiveRecord's instance. But, tomorrow you may want the model name of any ActiveRecord model.
To answer the question "Where should I put a method?" I suggest to break (without fear) a little bit the MVC pattern and read about:
Decorators, presenters and delegators
Services
(a little bit old, but you can get the idea)
"ProductCustomer".tableize.singularize.humanize.downcase

How self[:name] works rails

I created a reader method in my users model
def name
self[:name]
end
I'm having a hard time understanding self[:name]
it looks like I'm accessing a value with a key in a Hash but from what i can tell its not a Hash.
I have also tried to create classes in ruby to emulate this but cant get them to work so i"m not sure whether this is ruby or rails thing that I'm not understanding.
ActiveRecord supplies a [] method:
[](attr_name)
Returns the value of the attribute identified by attr_name after it has been typecast...
So saying self[:name] is just a round-about way to access the name attribute of your model.
[] is a method like any other in Ruby, you can define your own in any class you want:
class C
def [](k)
# do whatever you want
end
end
c = C.new
c[:pancakes]
ActiveRecord is used with data that is, more or less, a Hash backed by a relational database so saying model[:attribute_name] is fairly natural. Hence the existence of the [] method.

Ruby on Rails decorator for caching a result?

In Python, you can write a decorator for memoizing a function's response.
Is there something similar for Ruby on Rails? I have a model's method that makes a query, which I would like to cache.
I know I can do something inside the method, like:
def foo(param)
if self.cache[param].nil?
self.cache[param] = self.get_query_result(param)
else
self.cache[param]
end
end
However, given that I would do this often, I'd prefer a decorator syntax. It is clearer and better IMO.
Is there something like this for Ruby on Rails?
I usually do this using custom accessors, instance variables, and the ||= operator:
def foo
#foo ||= something_or_other
end
something_or_other could be a private method on the same class that returns the object that foo should be.
EDIT
Here's a slightly more complicated solution that lets you cache any method based on the arguments used to call them.
class MyClass
attr_reader :cache
def initialize
#cache = {}
end
class << self
def cacheable(symbol)
alias_method :"_#{symbol}_uncached", symbol
define_method(symbol) do |*args|
cache[[symbol, *args]] ||= (send :"_#{symbol}_uncached", *args)
end
end
end
end
How this works:
class MyClass
def foo(a, b)
a + b
end
cacheable :foo
end
First, the method is defined normally. Then the class method cacheable is called, which aliases the original method to a new name, then redefines it under the original name to be executed only if it's not already cached. It first checks the cache for anything using the same method and arguments, returns the value if present, and executes the original method if not.
http://martinfowler.com/bliki/TwoHardThings.html:
There are only two hard things in Computer Science: cache invalidation and naming things.
-- Phil Karlton
Rails has a lot of built in caching(including query caching). You might not need to do anything:
http://guides.rubyonrails.org/caching_with_rails.html
Here is a recent blog post about problems with roll your own caching:
http://cmme.org/tdumitrescu/blog/2014/01/careful-what-you-memoize/

Rails Model: includes method on self

I'm trying to optimise database queries so have been adding Model.includes(:related_model) where appropriate.
What is the appropriate way use this within methods inside my model? For example if I have a method in my model like:
def some_method
self.child_models.each do |child_model|
total_score += child_model.attribute
end
end
How do I use includes in instances like this? It seems natural to do it like this but it doesn't work:
def some_method
self.includes(:child_model).child_models.each do |child_model|
total_score += child_model.attribute
end
end
Most times when I produce an n+1 query it seems I'm referencing the model self but I can't seem to find any examples of this.
Thanks!
You are using self in an instance method so self is the instance of your class but includes is a class method. You need to use your original sample code to use includes Model.includes(:related_model). I think what you really want is:
def some_method
self.child_models.sum('attribute')
end
I would use includes when I am building conditions in a relation not looking at the children of an instance.

Resources