I want to create a bunch of methods for a find_by feature. I don't want to write the same thing over and over again so I want to use metaprogramming.
Say I want to create a method for finding by name, accepting the name as an argument. How would I do it? I've used define_method in the past but I didn't have any arguments for the method to take.
Here's my (bad) approach
["name", "brand"].each do |attribute|
define_method("self.find_by_#{attribute}") do |attr_|
all.each do |prod|
return prod if prod.attr_ == attr_
end
end
end
Any thoughts? Thanks in advance.
If I understand your question correctly, you want something like this:
class Product
class << self
[:name, :brand].each do |attribute|
define_method :"find_by_#{attribute}" do |value|
all.find {|prod| prod.public_send(attribute) == value }
end
end
end
end
(I'm assuming that the all method returns an Enumerable.)
The above is more-or-less equivalent to defining two class methods like this:
class Product
def self.find_by_name(value)
all.find {|prod| prod.name == value }
end
def self.find_by_brand(value)
all.find {|prod| prod.brand == value }
end
end
It if you read the examples here http://apidock.com/ruby/Module/define_method you will find this one:
define_method(:my_method) do |foo, bar| # or even |*args|
# do something
end
is the same as
def my_method(foo, bar)
# do something
end
When you do this: define_method("self.find_by_#{attribute}")
that is incorrect. The argument to define_method is a symbol with a single word.
Let me show you some correct code, hopefully this will be clear:
class MyClass < ActiveRecord::Base
["name", "brand"].each do |attribute|
define_method(:"find_by_#{attribute}") do |attr_|
first(attribute.to_sym => attr_)
end
end
end
This will produce class methods for find_by_brand and find_by_name.
Note that if you're looking into metaprogramming, this is a good use-case for method_missing. here's a tutorial to use method_missing to implement the same functionality you're going for (find_by_<x>)
Related
I'm trying to wrap my head around delegation vs. inheritance so I'm manually delegating a version of Array. One of the specific reasons I read to do this is because when you use things like enumerables, your returned value on the inherited methods reverts back to the parent class (i.e. Array). So I did this:
module PeepData
# A list of Peeps
class Peeps
include Enumerable
def initialize(list = [])
#list = list
end
def [](index)
#list[index]
end
def each(...)
#list.each(...)
end
def reverse
Peeps.new(#list.reverse)
end
def last
#list.last
end
def join(...)
#list.join(...)
end
def from_csv(csv_table)
#list = []
csv_table.each { |row| #list << Peep.new(row.to_h) }
end
def include(field, value)
Peeps.new(select { |row| row[field] == value })
end
def exclude(field, value)
Peeps.new(select { |row| row[field] != value })
end
def count_by_field(field)
result = {}
#list.each do |row|
result[row[field]] = result[row[field]].to_i + 1
end
result
end
protected
attr_reader :list
end
end
When I instantiate this, my include and exclude function great and return a Peeps class but when using an enumerable like select, it returns Array, which prevents me from chaining further Peeps specific methods after the select. This is exactly what I'm trying to avoid with learning about delegation.
p = Peeps.new
p.from_csv(csv_generated_array_of_hashes)
p.select(&:certified?).class
returns Array
If I override select, wrapping it in Peeps.new(), I get a "SystemStackError: stack level too deep". It seems to be recursively burying the list deeper into the list during the select enumeration.
def select(...)
Peeps.new(#list.select(...))
end
Any help and THANKS!
I would recommend using both Forwardable and Enumerable. Use Forwardable to delegate the each method to your list (to satisfy the Enumerable interface requirement), and also forward any Array methods you might want to include that are not part of the Enumerable module, such as size. I would also suggest not overriding the behavior of select as it is supposed to return an array and would at the very least lead to confusion. I would suggest something like the subset provided below to implement the behavior you are looking for.
require 'forwardable'
class Peeps
include Enumerable
extend Forwardable
def_delegators :#list, :each, :size
def initialize(list = [])
#list = list
end
def subset(&block)
selected = #list.select(&block)
Peeps.new(selected)
end
protected
attr_reader :list
end
Example usage:
peeps = Peeps.new([:a,:b,:c])
subset = peeps.subset {|s| s != :b}
puts subset.class
peeps.each do |peep|
puts peep
end
puts peeps.size
puts subset.size
produces:
Peeps
a
b
c
3
2
I think that if Peeps#select will return an Array, then it is OK to include Enumerable. But, you want Peeps#select to return a Peeps. I don't think you should include Enumerable. It's misleading to claim to be an Enumerable if you don't conform to its interface. This is just my opinion. There is no clear consensus on this in the ecosystem. See "Examples from the ecosystem" below.
If we accept that we cannot include Enumerable, here's the first implementation that comes to my mind.
require 'minitest/autorun'
class Peeps
ARRAY_METHODS = %i[flat_map map reject select]
ELEMENT_METHODS = %i[first include? last]
def initialize(list)
#list = list
end
def inspect
#list.join(', ')
end
def method_missing(mth, *args, &block)
if ARRAY_METHODS.include?(mth)
self.class.new(#list.send(mth, *args, &block))
elsif ELEMENT_METHODS.include?(mth)
#list.send(mth, *args, &block)
else
super
end
end
end
class PeepsTest < Minitest::Test
def test_first
assert_equal('alice', Peeps.new(%w[alice bob charlie]).first)
end
def test_include?
assert Peeps.new(%w[alice bob charlie]).include?('bob')
end
def test_select
peeps = Peeps.new(%w[alice bob charlie]).select { |i| i < 'c' }
assert_instance_of(Peeps, peeps)
assert_equal('alice, bob', peeps.inspect)
end
end
I don't normally use method_missing, but it seemed convenient.
Examples from the ecosystem
There doesn't seem to be a consensus on how strictly to follow interfaces.
ActionController::Parameters used to inherit Hash. Inheritance ceased in Rails 5.1.
ActiveSupport::HashWithIndifferentAccess still inherits Hash.
As mentioned in the other answer, this isn't really proper usage of Enumerable. That said, you could still include Enumerable and use some meta-programming to override the methods that you want to be peep-chainable:
module PeepData
class Peeps
include Enumerable
PEEP_CHAINABLES = [:map, :select]
PEEP_CHAINABLES.each do |method_name|
define_method(method_name) do |&block|
self.class.new(super(&block))
end
end
# solution for select without meta-programming looks like this:
# def select
# Peeps.new(super)
# end
end
end
Just so you know, this really has nothing to do with inheritance vs delegation. If Peeps extended Array, you would have the exact same issue, and the exact solution above would still work.
I would like to access a class' name in its superclass MySuperclass' self.inherited method. It works fine for concrete classes as defined by class Foo < MySuperclass; end but it fails when using anonymous classes. I tend to avoid creating (class-)constants in tests; I would like it to work with anonymous classes.
Given the following code:
class MySuperclass
def self.inherited(subclass)
super
# work with subclass' name
end
end
klass = Class.new(MySuperclass) do
def self.name
'FooBar'
end
end
klass#name will still be nil when MySuperclass.inherited is called as that will be before Class.new yields to its block and defines its methods.
I understand a class gets its name when it's assigned to a constant, but is there a way to set Class#name "early" without creating a constant?
I prepared a more verbose code example with failing tests to illustrate what's expected.
Probably #yield has taken place after the ::inherited is called, I saw the similar behaviour with class definition. However, you can avoid it by using ::klass singleton method instead of ::inherited callback.
def self.klass
#klass ||= (self.name || self.to_s).gsub(/Builder\z/, '')
end
I am trying to understand the benefit of being able to refer to an anonymous class by a name you have assigned to it after it has been created. I thought I might be able to move the conversation along by providing some code that you could look at and then tell us what you'd like to do differently:
class MySuperclass
def self.inherited(subclass)
# Create a class method for the subclass
subclass.instance_eval do
def sub_class() puts "sub_class here" end
end
# Create an instance method for the subclass
subclass.class_eval do
def sub_instance() puts "sub_instance here" end
end
end
end
klass = Class.new(MySuperclass) do
def self.name=(name)
#name = Object.const_set(name, self)
end
def self.name
#name
end
end
klass.sub_class #=> "sub_class here"
klass.new.sub_instance #=> "sub_instance here"
klass.name = 'Fido' #=> "Fido"
kn = klass.name #=> Fido
kn.sub_class #=> "sub_class here"
kn.new.sub_instance #=> "sub_instance here"
klass.name = 'Woof' #=> "Woof"
kn = klass.name #=> Fido (cannot change)
There is no way in pure Ruby to set a class name without assigning it to a constant.
If you're using MRI and want to write yourself a very small C extension, it would look something like this:
VALUE
force_class_name (VALUE klass, VALUE symbol_name)
{
rb_name_class(klass, SYM2ID(symbol_name));
return klass;
}
void
Init_my_extension ()
{
rb_define_method(rb_cClass, "force_class_name", force_class_name, 1);
}
This is a very heavy approach to the problem. Even if it works it won't be guaranteed to work across various versions of ruby, since it relies on the non-API C function rb_name_class. I'm also not sure what the behavior will be once Ruby gets around to running its own class-naming hooks afterward.
The code snippet for your use case would look like this:
require 'my_extension'
class MySuperclass
def self.inherited(subclass)
super
subclass.force_class_name(:FooBar)
# work with subclass' name
end
end
I would like to make something like this:
class Result<<ActiveRecord::Base
def condensation
#some code here that calculates #winner and #looser and #condresalut
def winner
#winner
end
def looser
#looser
end
def showresault
#condresalut
end
end
end
so that I can call res.condensation.winner and res.condensation.looser and res.condensation.showresault.
What is the best way to do it? Apparently this way it does not work, I got nils.
It is indeed possible to do so. Not sure what the intent is, as that has been asked, but not sure if that question was clarified.
However Jay Fields has a well visited blog entry that shows how to define a method inside a method.
class Class
def def_each(*method_names, &block)
method_names.each do |method_name|
define_method method_name do
instance_exec method_name, &block
end
end
end
end
Your methods themselves inside your definition though are likely better served using the attr_reader technique.
As far as calling nested defined methods:
def testing
def testing2
'it worked'
end
end
puts testing::testing2
Thogh as Alex D reminds me in the comments, the scope operator is a deception.
I don't think you can get there from here.
Ruby allows us to define methods inside methods, but the inner methods are not exposed, or available directly.
The inner methods are only available from within the outer method, so, in your example, winner, looser and showresault are only accessible from inside condensation.
You could create lambdas or procs and return them, en masse, as closures, which would give you access to the internal values inside condensation, but, really, it seems as if you're confusing the use of a class vs. a method and trying to make a method behave like a class with its accessors. Instead, I'd probably create a class within a class, and go from there.
def condensation
#condensation ||= Struct.new(:winner, :looser, :showresult).new
end
def winner
#winner ||= condensation.winner
end
def winner=(winner)
#winner = winner
end
... and so on
I changed resault by result, and I wanted to change showresult with show_result
You can calculate winner like this:
def calculate_winner
# something using winner= method
end
There has got to be a more efficient way to do this in Ruby. I have a list of methods that scrape the same things (title, price) across multiple sites but in slightly different ways based on the code in each store. For example:
def store1_get_title
def store1_get_price
def store2_get_title
def store2_get_price
def store3_get_title
def store3_get_price
When calling all of these functions, I would just like a generic call with say a "namespace" parameter to do invoke any of these methods without having to type out all of them, something like:
for get_all_stores().each do |store|
store::get_title
store::get_price
end
...which would invoke store1_get_title, store1_get_price, store2_get_title, store2_get_price like I want. Is there something like this or a better way to do this?
Hope that makes sense. Thanks for any input!
Edit: these tasks are in rake task code.
This is a perfect use for classes. If you find two stores with the same software powering them (maybe Yahoo commerce or EBay stores) you can make instances of the classes with different parameters.
class Amazon
def get_price; end
def get_title; end
end
class Ebay
def initialize seller; end
def get_price; end
def get_title; end
end
[Amazon.new, Ebay.new("seller1"), Ebay.new("seller2")] each do |store|
store.get_price
store.get_title
end
And you can do this in any other object-oriented language by defining a base class or interface that all of the stores implement/inherit.
I don't understand the logic of your application. Perhaps you should think about a class definition (see Ken Blooms answer).
Nevertheless you could try a dynamic call with send:
def store1_get_title
p __method__
end
def store1_get_price
p __method__
end
def store2_get_title
p __method__
end
def store2_get_price
p __method__
end
def store3_get_title
p __method__
end
def store3_get_price
p __method__
end
all_stores = ['store1', 'store2', 'store3']
all_stores.each do |store|
send("#{store}_get_title")
send("#{store}_get_price")
end
You didn't define what get_all_stores returns. In my example I used Strings. You could add some syntactical sugar and extend String (I don't recommend this)
class String
def get_title()
send("#{self}_get_title")
end
def get_price()
send("#{self}_get_price")
end
end
all_stores.each do |store|
store.get_title
store.get_price
end
One last remark. You wrote
for get_all_stores().each do |store|
each alone should be enough. for is not ruby-like and in combination with each it doen't look reasonable to me.
Some code that I had that used attr_accessor_with_default in a rails model is now giving me a deprecation warning, telling me to "Use Ruby instead!"
So, thinking that maybe there was a new bit in ruby 1.9.2 that made attr_accessor handle defaults, I googled it, but I don't see that. I did see a bunch of methods to override attr_accessor to handle defaults though.
Is that what they mean when they tell me to "Use Ruby?" Or am I supposed to write full getters/setters now? Or is there some new way I can't find?
This apidock page suggests to just do it in the initialize method.
class Something
attr_accessor :pancakes
def initialize
#pancakes = true
super
end
end
Don't forget to call super especially when using ActiveRecord or similar.
attr_accessor :pancakes
def after_initialize
return unless new_record?
self.pancakes = 11
end
This ensures that the value is initialized to some default for new record only.
Since you probably know your data quite well, it can be quite acceptable to assume nil is not a valid value.
This means you can do away with an after_initialize, as this will be executed for every object you create. As several people have pointed out, this is (potentially) disastrous for performance. Also, inlining the method as in the example is deprecated in Rails 3.1 anyway.
To 'use Ruby instead' I would take this approach:
attr_writer :pancakes
def pancakes
return 12 if #pancakes.nil?
#pancakes
end
So trim down the Ruby magic just a little bit and write your own getter. After all this does exactly what you are trying to accomplish, and it's nice and simple enough for anyone to wrap his/her head around.
This is an ooooold question, but the general problem still crops up - and I found myself here.
The other answers are varied and interesting, but I found problems with all of them when initializing arrays (especially as I wanted to be able to use them at a class level before initialize was called on the instance). I had success with:
attr_writer :pancakes
def pancakes
#pancakes ||= []
end
If you use = instead of ||= you will find that the << operator fails for adding the first element to the array. (An anonymous array is created, a value is assigned to it, but it's never assigned back to #pancakes.)
For example:
obj.pancakes
#=> []
obj.pancakes << 'foo'
#=> ['foo']
obj.pancakes
#=> []
#???#!%$##%FRAK!!!
As this is quite a subtle problem and could cause a few head scratches, I thought it was worth mentioning here.
This pattern will need to be altered for a bool, for example if you want to default to false:
attr_writer :pancakes
def pancakes
#pancakes.nil? ? #pancakes = false : #pancakes
end
Although you could argue that the assignment isn't strictly necessary when dealing with a bool.
There's nothing magical in 1.9.2 for initializing instance variables that you set up with attr_accessor. But there is the after_initialize callback:
The after_initialize callback will be called whenever an Active Record object is instantiated, either by directly using new or when a record is loaded from the database. It can be useful to avoid the need to directly override your Active Record initialize method.
So:
attr_accessor :pancakes
after_initialize :init
protected
def init
#pancakes = 11
end
This is safer than something like this:
def pancakes
#pancakes ||= 11
end
because nil or false might be perfectly valid values after initialization and assuming that they're not can cause some interesting bugs.
I'm wondering if just using Rails implementation would work for you:
http://apidock.com/rails/Module/attr_accessor_with_default
def attr_accessor_with_default(sym, default = nil, &block)
raise 'Default value or block required' unless !default.nil? || block
define_method(sym, block_given? ? block : Proc.new { default })
module_eval( def #{sym}=(value) # def age=(value) class << self; attr_reader :#{sym} end # class << self; attr_reader :age end ##{sym} = value # #age = value end # end, __FILE__, __LINE__ + 1)
end
You can specify default values for instances of any class (not only ActiveRecords) after applying patch to Module:
class Zaloop
attr_accessor var1: :default_value, var2: 2
def initialize
self.initialize_default_values
end
end
puts Zaloop.new.var1 # :default_value
Patch for module:
Module.module_eval do
alias _original_attr_accessor attr_accessor
def attr_accessor(*args)
attr_names = extract_default_values args
_original_attr_accessor *attr_names
end
alias _original_attr_reader attr_reader
def attr_reader(*args)
attr_names = extract_default_values args
_original_attr_reader *attr_names
end
def extract_default_values(args)
#default_values ||= {}
attr_names = []
args.map do |arg|
if arg.is_a? Hash
arg.each do |key, value|
define_default_initializer if #default_values.empty?
#default_values[key] = value
attr_names << key
end
else
attr_names << arg
end
end
attr_names
end
def define_default_initializer
default_values = #default_values
self.send :define_method, :initialize_default_values do
default_values.each do |key, value|
instance_variable_set("##{key}".to_sym, value)
end
end
end
def initialize_default_values
# Helper for autocomplete and syntax highlighters
end
end