Refactoring methods which depend on __method__ - ruby-on-rails

So I'm taking my first step into using Presenters for my rails app and I'm just looking at a refactoring of some of my code. I have several fields which display phone numbers (i.e. phone, cell and fax) nicely formatted or show "none given". Obviously I originally had this in the view but moved the logic into my presenter. Once there I noticed it was all the same so refactored it into a private method which uses the method name and the send function:
class CustomerPresenter < BasePresenter
presents :customer
def phone
format_number(__method__)
end
def cell
format_number(__method__)
end
def fax
format_number(__method__)
end
private
def format_number(method)
hande_none customer.send(method) do
h.number_to_phone(customer.send(method), :area_code => true)
end
end
end
and yet this code still doesn't seem DRY. Because format_number uses the method name it seems that I have to define three seperate methods. I was curious if there was something more I could do here.
p.s. hande_none simply returns the block if there is something there or returns "none given"

I generally avoid to mix actual getter/method/attributes names with the methods used to format them.
That's why I'd use *_formatted suffix: with a general suffix, you can have a simple method_missing which would lead you to something like:
class CustomerPresenter < BasePresenter
presents :customer
private
def format_number(method)
hande_none customer.send(method) do
h.number_to_phone(customer.send(method), :area_code => true)
end
end
def method_missing(method_name, *args, &block)
case method_name
when /(.*)_formatted$/
#here you could create the method on the fly to avoid method missing next time
return format_number( $1 )
end
super(method_name, *args, &block)
end
end
Basically, I've this in my BasePresenter for *_currency_format

Related

How to improve lisibility/split "fat" class method (inside ruby class/rails model)

as a new Rubyist, I'm running into a recurring problem when it comes to structure my models.
When a method is too long:
I try to refactor to a better/shorter syntax
I try to split some parts into "sub methods"
PROBLEM: I don't know how to split the method properly + whith which tool (private method, modules etc.)
For example:
I need to run Foo.main_class_method
My model looks like this:
class Foo < Applicationrecord
def self.main_class_method
[...] # way too long method with nasty iterations
end
end
I try to split my method to improve lisibility. It becomes :
class Foo < Applicationrecord
def self.main_class_method
[...] # fewer code
self.first_splitted_class_method
self.second_splitted_class_method
end
private
def self.first_splitted_class_method
[...] # some code
end
def self.second_splitted_class_method
[...] # some code
end
end
Result: It works, but I fell like this is not the proper way to do it + I have side effects
expected: splitted_methods are not accessible, except inside main_class_method
got: I can call Foo.first_splitted_class_method since class methods "ignore" Private. splitted_class_methods under Private are not private
Question: Is it an acceptable way to split main_class_method or is it a complete misuse of private method ?
Using private method to split your code:
Possible but not the real solution if the code belongs somewhere else
It's rather about "does it belongs here?" than "does it look nicer?"
To fix the "not private" private class method (original post) :
use private_class_method :your_method_name after you defined it
or right before
private_class_method def your_method_name
[...] # your code
end
If your splitting a class/instance method:
the splitted_method must be the same type(class/instance) as the main_class_method calling it
In the main_method you can call the splitted_method with or without using self.method syntax
class Foo < Applicationrecord
def self.main_class_method
# Here, self == Foo class
# first_splitted_class == class method, I can call self.first_splitted_class_method
self.first_splitted_class_method
# I can also call directly without self because self is implicit
second_splitted_class_method
end
def self.first_splitted_class_method
end
def self.second_splitted_class_method
end
private_class_method :first_splitted_class_method, :second_splitted_class_method
end

easy way to check up series of consistency in rails model object

In my ruby on rails project, I have a model of different parts: A, B, C. A has different fields, depending on that which entity of B and C may fit in. When user put A,B,C in cart I have to tell them if A,B,C these parts fits in or not?
class A < ActiveRecord::Base
def supports_x(B)
...
return true
end
def supports_y(C)
...
return true
end
....10/15 methods like this
def report(cart)
report=[]
if support_x(cart.a)
report<<"a does not support x opporation"
end
.... like this 10/15 hand written if else operation.
end
end
Personally I can do that, I can write 10/15 similar looking methods and if else blocks, but my instinct is telling me I am doing it wrong, I am violating DRY principle.
Is there any way I can avoid this in ruby/rails?
You can use the method_missing method like this:
class A < ActiveRecord::Base
def method_missing(m, *args, &block)
if m.to_s.start_with? 'support_'
# Check the full method name and do whatever you want with args
else
super
end
end
def report(cart)
report=[]
parts = ['x', 'y', 'z']
parts.each do |part|
if self.send("support_#{part}", cart.a)
report << "a does not support #{part} operation"
end
end
end
end
Note that I haven't tested this, and it's more like a pseudocode to give you an idea.

Dynamically defining instance method within an instance method

I have a several classes, each of which define various statistics.
class MonthlyStat
attr_accessor :cost, :size_in_meters
end
class DailyStat
attr_accessor :cost, :weight
end
I want to create a decorator/presenter for a collection of these objects, that lets me easily access aggregate information about each collection, for example:
class YearDecorator
attr_accessor :objs
def self.[]= *objs
new objs
end
def initialize objs
self.objs = objs
define_helpers
end
def define_helpers
if o=objs.first # assume all objects are the same
o.instance_methods.each do |method_name|
# sums :cost, :size_in_meters, :weight etc
define_method "yearly_#{method_name}_sum" do
objs.inject(0){|o,sum| sum += o.send(method_name)}
end
end
end
end
end
YearDecorator[mstat1, mstat2].yearly_cost_sum
Unfortunately define method isn't available from within an instance method.
Replacing this with:
class << self
define_method "yearly_#{method_name}_sum" do
objs.inject(0){|o,sum| sum += o.send(method_name)}
end
end
...also fails because the variables method_name and objs which are defined in the instance are no longer available. Is there an idomatic was to accomplish this in ruby?
(EDITED: I get what you're trying to do now.)
Well, I tried the same approaches that you probably did, but ended up having to use eval
class Foo
METHOD_NAMES = [:foo]
def def_foo
METHOD_NAMES.each { |method_name|
eval <<-EOF
def self.#{method_name}
\"#{method_name}\".capitalize
end
EOF
}
end
end
foo=Foo.new
foo.def_foo
p foo.foo # => "Foo"
f2 = Foo.new
p f2.foo # => "undefined method 'foo'..."
I myself will admit it's not the most elegant solution (may not even be the most idiomatic) but I've run into similar situations in the past where the most blunt approach that worked was eval.
I'm curious what you're getting for o.instance_methods. This is a class-level method and isn't generally available on instances of objects, which from what I can tell, is what you're dealing with here.
Anyway, you probably are looking for method_missing, which will define the method dynamically the first time you call it, and will let you send :define_method to the object's class. You don't need to redefine the same instance methods every time you instantiate a new object, so method_missing will allow you to alter the class at runtime only if the called method hasn't already been defined.
Since you're expecting the name of a method from your other classes surrounded by some pattern (i.e., yearly_base_sum would correspond to a base method), I'd recommend writing a method that returns a matching pattern if it finds one. Note: this would NOT involve making a list of methods on the other class - you should still rely on the built-in NoMethodError for cases when one of your objects doesn't know how to respond to message you send it. This keeps your API a bit more flexible, and would be useful in cases where your stats classes might also be modified at runtime.
def method_missing(name, *args, &block)
method_name = matching_method_name(name)
if method_name
self.class.send :define_method, name do |*args|
objs.inject(0) {|obj, sum| sum + obj.send(method_name)}
end
send name, *args, &block
else
super(name, *args, &block)
end
end
def matching_method_name(name)
# ... this part's up to you
end

What's a really good pattern to extract logic from models and have the results persisted to the db?

I've been experimenting with different ways of structuring my app, and particularly with ActiveRecord-based models, looking into the notion of having a separate class whose results are saved as a single field. Now composition might have worked, but there's been a lot of discussion on whether its staying or not, so I was wondering what alternatives you might have.
Example:
class Gadget < ActiveRecord::Base
attr_accessor: lights
end
Now, I would like the lights property to be 'managed' by a completely separate class:
class Lights
def doSomething
end
def doSomethingElse
end
end
What's a good way of, say, proxying or delegating this logic out of the ActiveRecord model and into the Lights class in order to transparently store the results?
e.g. using an instance of the Lights class as the lights property - but that won't work, since there's no association, right?
e.g. use method_missing to push all requests out to the lights instance - but that won't work either, because it won't be persisted to the database.
Maybe it's not possible, but I'm interested in ways of persisting the results of logical operations. All suggestions welcome.
What about something like:
class Gadget < ActiveRecord::Base
def lights
#lights ||= Lights.new(self)
end
end
class Lights
attr_reader :root
delegate :save, :save!, to: :root
def initialize(root)
#root = root
end
def doSomething
end
def doSomethingElse
end
def method_missing(method_name, *args, &block)
#delegate all setters to root
if method_name =~ /.*=$/
root.send(method_name, *args, &block)
else
super
end
end
end
Thus you can do:
gadget = Gadget.new
#say gagdet has a name column
gadget.lights.name = 'foo'
gadget.lights.save
gadget.name #=> 'foo'
Still unsure why you need it but it should work

Object does not get loaded

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?

Resources