extract `.for()` into a method - ruby-on-rails

I have a class like this one:
class House
def bricks
Brick.for(#house_plan).where(size: 5)
end
def wood
Wood.for(#house_plan).where(size: 5)
end
end
My goal is to extract the call for(self).where(size: 5):
What I tried first was:
class House
def bricks
Brick.match_material
end
def wood
Wood.match_material
end
def match_material
for(#house_plan).where(size: 5)
end
end
But then I got this error:
syntax error, unexpected '\n', expecting :: or '[' or '.'
Then I changed my code to:
def match_material
.for(#house_plan).where(size: 5)
end
And now when I do:
house = House.new(HousePlan.new)
house.bricks
I get this error:
formal argument cannot be an instance variable
In this line: for(#house_plan).where(size: 5)
What do I wrong?

Your approach isn't right, remember match_material method will always be called in the context of your self. I would do it this way:
def bricks
match_material(Brick)
end
def wood
match_material(Wood)
end
def match_material(klass)
klass.for(#house_plan).where(size: 5)
end

Just out of curiosity:
def bricks
klazz = Kernel.const_get(__callee__[0...-1].capitalize)
klazz.for(#house_plan).where(size: 5)
end
alias :woods :bricks
NB: In this approach aliased methods are to be named consistently(bricks, woods.) Please don’t use it in production unless you understand what you are doing.

Related

RSpec "change all" matcher

I've got some job that updates records, and I want something like:
it 'updates each record' do
expect {
described_class.perform_now
}.to(change_all_of{
Record.pluck(:updated_at)
})
end
Only I can't find anything that looks like how to accomplish this, or what I can recognize as the docs for how to write a custom matcher.
The issue is that change, on an array, will return true if any element has changed; I only want to return true if every element has changed.
Can anyone point me either at whatever I missed that would let me do this, OR, whatever docs/info I need to write my own change matcher?
Alright, thanks to this answer on another question, and staring at the actual code, here's what I've got -
module RSpec
module Matchers
def change_all &block
BuiltIn::ChangeAll.new(nil, nil, &block)
end
module BuiltIn
class ChangeAll < Change
def initialize receiver=nil, message=nil, &block
#change_details = ChangeAllDetails.new(receiver, message, &block)
end
def failure_message
"expected all elements to change, but " + (
#change_details.actual_after & #change_details.actual_before
).collect do |unchanged|
"before[#{#change_details.actual_before.find_index(unchanged)}] " \
"after[#{#change_details.actual_after.find_index(unchanged)}] " \
"#{unchanged}"
end.join(",") + " remained the same"
end
end
class ChangeAllDetails < ChangeDetails
attr_accessor :actual_before
def changed?
!(#actual_after & #actual_before).any?
end
end
end
end
end
Suggestions welcome!

Is there a way to override set intersection in ruby?

i.e. can I define my own '==' method in a class and then make the set intersection operator ('&') use it? Alternatively, is there a way to override the '&' operator itself?
Can we do something like this?
def &(another_object)
#Code for intersection
end
First of all, & is not an operator. It’s a syntactic sugar to call the method & on LHO, passing RHO as the only argument.
Secondary, I doubt whether copy-pasting your attempt to pry/irb and check whether it works is harder/longer than to post a question here.
class MyObject
def ==(other)
other.is_a?(MyObject) && self.&(other).empty?
end
def &(other)
[]
end
end
mo1, mo2 = 2.times.map { MyObject.new }
mo1 == mo2 #⇒ true
mo1 == 42 #⇒ false
can I define my own '==' method in a class
Yes, you can:
class Foo
def ==(other)
# bla
end
end
and then make the set intersection operator ('&') use it?
Yes, you can:
require 'set'
module MySetEqualityExtension
def &(other)
reduce([]) {|acc, el| if acc.any? {|a| a == el } then acc else acc << el end }
end
end
module MySetEquality
refine Set do
prepend MySetEqualityExtension
end
end
Alternatively, is there a way to override the '&' operator itself?
Can we do something like this?
def &(another_object)
#Code for intersection
end
Yes. (As a side-note: it would have taken you less time and effort to just try it instead of asking.)
class Foo
def &(other)
# bla
end
end
You can do all of what you ask, but the correct way would be to simply implement eql? and hash properly, which is what Set#& uses.

How to decouple functionality and logging in a ruby method

I like to log a lot. In my Rails app I have a lot of methods like:
def my_method(argument1:, argument2:)
logger.info "Starting my_method with arguments: #{argument1} and #{argument2}"
result = argument1 + argument2
logger.info "Finished my_method with result: #{result}"
end
How to decouple the functionality and the logging of the methods?.
Ideally the result would look something like this (borrowing the callback concept from Rails just as an example):
before_method: :my_method_log_start, only: :my_method
after_method: :my_method_log_end, only: :my_method
def my_method(argument1:, argument2:)
result = argument1 + argument2
end
private
def my_method_log_start
logger.info "Starting my_method with arguments: #{argument1} and #{argument2}"
end
def my_method_log_end
logger.info "Finished my_method with result: #{result}"
end
I know this is less efficient in terms of lines of code, it is more readable (in my opinion).
I have read about Aspect Orient Programming and some of their gems like Aquarius, but looks like an overkill to add a new paradigm just for logging.
I think Avdi Grimm has a good explanation of the technique you could use. The idea is to extract logging (or anything else) to the listener class and publish events to that listener, basic example would be
class Task
# ...
def add_listener(listener)
(#listeners ||= []) << listener
end
# ...
def notify_listeners(event_name, *args)
#listeners && #listeners.each do |listener|
if listener.respond_to?(event_name)
listener.public_send(event_name, self, *args)
end
end
end
end
and do sth like
task = Task.new
task.add_lestener(YourLoggerClass.new)
task.notify_listeners(:start_logging)
task.notify_listeners(:end_logging)
If this is only for local debugging, it is the good use case for TracePoint class. Here is the code:
tp1 = TracePoint.new do |tp|
if tp.event == :call
method = tp.defined_class.method(tp.method_id)
arguments = method.parameters.map do |param|
"#{param[1]}: #{tp.binding.local_variable_get(param[1])}"
end.join(", ")
puts "Starting #{tp.method_id} with arguments #{arguments}"
elsif tp.event.to_s == "return"
puts "Finished #{tp.method_id} with result: #{tp.return_value}"
end
end
tp1.enable
def my_method1(a, b)
a + b
end
puts my_method1(2, 3)
I recommend reading the documentation for this class, it has really nice features. Of course you need to polish this code a little bit to handle some edge cases. You can add some filter to only invoke tracing block for methods that you care about. Or you can enable/disable this based on some parts of the code.
You can call method by it's name, or turn it to proc and pass to another method. So you can write something like that:
def foo(a, b)
a + b
end
def call_with_logging(method_name, *args)
args_as_string = args.map(&:to_s).join(' ')
puts "Starting my_method with arguments #{args_as_string}"
result = Object.send(method_name, *args)
puts "Finished my_method with result: #{result}"
end
call_with_logging :foo, 1, 2

How can I cleanly define "antonym" or "opposite" methods in Ruby / Rails?

I'm pretty often defining methods and their antonyms in the code I'm writing, as in:
def happy?
#happiness > 3
end
def sad?
!happy?
end
Which is fine, but I'm a little surprised that Ruby or ActiveSupport doesn't give me something like:
def happy?
#happiness > 3
end
alias_opposite :sad? :happy?
Or am I just looking in the wrong place?
There is no such method in popular libraries, but there is how this could be implemented
class Module
def alias_opposite(a, b)
define_method(a) { !self.send(b) }
end
end
Usage
class A < Struct.new(:happiness)
def happy?
happiness > 3
end
alias_opposite :sad?, :happy?
end
p A.new(1).sad? # => true
p A.new(5).sad? # => false
I suspect this pattern is not as common in ruby because the unless keyword often does the trick:
# ...
clap_your_hands if happy?
stomp_your_feet unless happy?
# ...
Of course, its simple to roll your own:
module Antonymator
def define_antonym(as, of)
define_method(as.to_sym) do |*args|
return !(send(of.to_sym, *args))
end
end
end
# Usage Example
class AreThey
extend Antonymator
define_antonym :uneql?, :eql?
define_antonym :nonconsecutive?, :consecutive?
def eql?(a, b)
a == b
end
def consecutive?(a, b)
a.next == b
end
end
are_they = AreThey.new
puts are_they.uneql? 1, 2 # true
puts are_they.nonconsecutive? 1, 2 # false
If your methods return a Boolean, you can always include the positive method in the negative method.
def drinking_age?(age)
age > #restricted_age
end
def not_drinking_age?(age)
!drinking_age?(age)
end
#restricted_age = 20
Hope that helps.
I guess it depends on what 'opposite' means in the context.

Ruby/Rails: Prepend, append code to all methods

I wrote a small benchmarking Class for testing my code doing development. At the moment I have to add the Class to the beginning and end of every method. Is it posible to prepend, append on the fly, so that I don't have to clutter my code?
class ApplicationController
before_filter :init_perf
after_filter :write_perf_results_to_log!
def init_perf
#perf ||= Perf.new
end
def write_perf_results_to_log!
#perf.results
end
end
class Products < ApplicationsController
def foo
#perf.log(__methond__.to_s)
caculation = 5 *4
#perf.write!
end
def bar
#perf.log(__methond__.to_s)
caculation = 1 / 5
#perf.write!
end
end
This is the Perf class. It is located in the services folder.
class Perf
def initialize
#results = []
end
def log(note)
#start = Time.now
#note = note
end
def write!
if #results.find {|h| h[:note] == #note } # Update :sec method exists in results
#results.select { |h| h["note"] == #note; h[":sec"] = (Time.now - #start).round(3) }
else # Add new Hash to results
#results << { :note => #note, :sec => (Time.now - #start).round(3) }
end
end
def results
content = "
PERFORMANCE STATISTICS!
"
#results.each do |r|
content += r[:note] + " " + r[:sec].to_s + "
"
end
content += "
"
Rails.logger.info content
end
end
In general computing terms what you want to do is called code instrumentation. There are several ways to accomplish this, however here's one (crude) example using some metaprogramming:
First define a new method that we will use for injecting our instrumentation code:
class ApplicationController
def self.instrument_methods(*methods)
methods.each { |m|
# Rename original method
self.send(:alias_method, "#{m}_orig", m)
# Redefine old method with instrumentation code added
define_method m do
puts "Perf log #{m}"
self.send "#{m}_orig"
puts "Perf write"
end
}
end
end
How to use it:
class Product < ApplicationController
def foo
puts "Foo"
end
def bar
puts "Bar"
end
# This has to be called last, once the original methods are defined
instrument_methods :foo, :bar
end
Then:
p = Product.new
p.foo
p.bar
Will output:
Perf log foo
Foo
Perf write
Perf log bar
Bar
Perf write
Here are some other ways to instrument ruby code and measure performance:
http://ruby-prof.rubyforge.org/
http://www.igvita.com/2009/06/13/profiling-ruby-with-googles-perftools/
There is better solution.
class ApplicationController
def self.inherited(klass)
def klass.method_added(name)
return if #_not_new
#_not_new = true
original = "original #{name}"
alias_method original, name
define_method(name) do |*args, &block|
puts "==> called #{name} with args: #{args.inspect}"
result = send original, *args, &block
puts "<== result is #{result}"
result
end
#_not_new = false
end
end
end
class Product < ApplicationController
def meth(a1, a2)
a1 + a2
end
end
product = Product.new
puts product.meth(2,3)
And the result:
==> called meth with args: [2, 3]
<== result is 5
5
The source & explanation are here: http://pragprog.com/screencasts/v-dtrubyom/the-ruby-object-model-and-metaprogramming. I recommend to spend not a big money to get this course.
I'm the author of aspector gem. Thanks to dimuch for mentioning it.
I've come up with a solution using aspector. Here are the high level steps:
Create an aspect as a subclass of Aspector::Base
Inside the aspect, define advices (before/after/around are the primary types of advices)
Apply the aspect on target class (or module/object)
The full code can be found in this gist. Please feel free to let me know if you have questions or the solution doesn't do what you intend to.
class PerfAspect < Aspector::Base
around options[:action_methods] do |proxy|
#perf ||= Perf.new
proxy.call
#perf.results
end
around options[:other_methods], :method_arg => true do |method, proxy, *args, &block|
#perf.log(method)
result = proxy.call *args, &block
#perf.write!
result
end
end
action_methods = [:action]
other_methods = Products.instance_methods(false) - action_methods
PerfAspect.apply(Products, :action_methods => action_methods, :other_methods => other_methods)
Guess aspector gem can help. It's not well documented but has useful examples.

Resources