I'm writing some methods for an Artist class, one finds the songs that belong to the artist and the other finds the genres through the songs. I recently found out about the songs.map(&:genre) syntax as opposed to writing songs.map { |song| song.genre } and it got me a little curious if I could use the same styling for my songs method.
def songs
Song.all.select { |song| song.artist == self}
end
def genres
songs.map(&:genre)
end
The unary ampersand in block position simply calls to_proc on whatever object it's given. Symbol#to_proc produces exactly what you just described; it creates a Proc which takes a single argument and calls the method with the given symbol on it, so :genre.to_proc is a procedure which calls the genre method on an arbitrary object.
The built-in Symbol#to_proc is insufficient to do what you're doing for songs; you want to access something and compare it. But, of course, to_proc can do whatever we want, so we could make a class that does this.
class MatchesArtist
attr_reader :artist
def initialize(artist)
#artist = artist
end
def to_proc
Proc.new { |song| song.artist == self.artist }
end
end
def songs
matches = MatchesArtist.new(self)
Song.all.select(&:matches)
end
Now, this can be useful in general, if you're building up combinators using some kind of query language. But for the code you've shown, it's likely that an explicit block is much clearer.
You don't need a whole class with a #to_proc for this, you can just write a method that returns lambda:
def matches_artist
->(song) { song.artist == artist }
end
and then say:
Song.all.select(&matches_artist)
Or even nicer (IMO):
def matches(artist)
->(song) { song.artist == artist }
end
and say:
Song.all.select(&matches(artist))
Or even a lambda that returns a lambda:
matches = ->(artist) { ->(song) { song.artist == artist } }
Song.all.select(&matches[artist]))
Related
I am making a CLI it has 100 objects each has a name and I would like to create an option to search my list of 100 objects to find the object by name. What would be the best implementation to use here.
To start with I am assuming in Ruby I can use .find ? My current WIP is below. Any help is appreciated.
class PokeDEXCLI::Pokemon
attr_accessor :name, :id, :height, :weight
##all = []
def initialize(attr_hash)
attr_hash.each do |key, value|
self.send("#{key}=", value) if self.respond_to?("#{key}=")
end
self.save
end
def save
##all << self
end
def self.all
##all
end
end
My thought was I could search by input to find by doing something like this first?
def self.find_by_name(input)
puts " Would you like to search by pokemon name? Please type in your query."
input = gets.chomp
if ##all.include? input
(this is where I am unsure how to compare input to the :name attribute)
end
So I believe I will use the below snippet. My other question is can I add a default argument to name so it will return nil if there is no match?
class Pokemon
def self.find_by_name(name = nil)
##all.select { |pokemon| pokemon.name.include?(name) }
end
end
The Ruby approach here is to use a Hash:
OBJECTS = {
shoe: 'A shoe',
rose: 'A red flower',
dog: 'A yappy dog'
cat: 'Some orange blur'
}
This doesn't have to be a constant as it is here, you could easily have a variable, but if the data never changes constants are more efficient in terms of impact on performance.
Where you can reference it like this:
OBJECTS[:shoe]
Or based on input:
OBJECTS[gets.chomp.to_sym]
If collection accepts objects with already existing names, then use select method to return collection of matched names
class Pokemon
def self.find_by_name(name)
##all.select { |pokemon| pokemon.name == name }
end
end
If you want to find objects which name contains given string, use same approach with different condition
class Pokemon
def self.find_by_name(name)
##all.select { |pokemon| pokemon.name.include?(name) }
end
end
If collection accepts only unique names, then collection could be implemented by using Hash
class Pokemon
##all = {}
def self.find_by_name(name)
##all[name]
end
def save
##all[name] == self
end
end
this class method could help you if your 'Pokemon' class have attr_accessor 'name'
def self.find_by_name(search_string)
result = []
ObjectSpace.each_object(self) { |pokemon| result << pokemon if pokemon.name.include?(search_string) }
result
end
it will return an array of pokemons with name, including the search string
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>)
I have a Ruby class, and each method on it keeps indices of an array of hashes based on certain conditions.
For example (code has been edited since original posting)
module Dronestream
class Strike
class << self
...
def strike
#strike ||= all
end
def all
response['strike'] # returns an array of hashes, each individual strike
end
def in_country(country)
strike.keep_if { |strike| strike['country'] == country }
self
end
def in_town(town)
strike.keep_if { |strike| strike['town'] == town }
self
end
...
end
end
This way, you can do Dronestream::Strike.in_country('Yemen'), or Dronestream::Strike.in_town('Taizz'), and each returns an array. But I'd like to be able to do Dronestream::Strike.in_country('Yemen').in_town('Taizz'), and have it return only the strikes in that town in Yemen.
But as of now, each separate method returns an array. I know that if I have them return self, they'll have the method I need. But then they won't return an array, and I can't call, for example, first or each on them, like I could an array, which I need to do. I tried to make Strike < Array, but then, first is an instance method on Array, not a class method.
What should I do?
EDIT
Here is a part of my test suite. Following the answer below, the tests pass individually, but then fail.
describe Dronestream::Strike do
let(:strike) { Dronestream::Strike }
before :each do
VCR.insert_cassette 'strike', :record => :new_episodes
#strike = nil
end
after do
VCR.eject_cassette
end
...
# passes when run by itself and when the whole file runs together
describe '#country' do
let(:country_name) { 'Yemen' }
it 'takes a country and returns strikes from that country' do
expect(strike.in_country(country_name).first['country']).to eq(country_name)
end
end
# passes when run by itself, but fails when the whole file runs together
describe '#in_town' do
let(:town_name) { 'Wadi Abida' }
it 'returns an array of strikes for a given town' do
expect(strike.in_town(town_name).first['town'].include?(town_name)).to be_true
end
end
...
end
You can overwrite the method_missing to handle this.
Return self in your in_country or in_town method. Then when called first to it, delivery it to the all array to handle.
the code may be like this:
module Dronestream
class Strike
class << self
...
def all
...
end
def in_country(country)
all.keep_if { |strike| strike['country'] == country }
self
end
def in_town(town)
all.keep_if { |strike| strike['town'] == town }
self
end
...
def method_missing(name,*args,&block)
return all.send(name.to_sym, *args, &block) if all.respond_to? name.to_sym
super
end
end
I have the following test which I must pass:
def test_can_find_by_arbitrary_fields
assert #library.respond_to? :find_by_artist
assert !#library.respond_to?(:find_by_bitrate)
#library.add_song({ :artist => 'Green Day',
:name => 'American Idiot',
:bitrate => 192 })
assert #library.respond_to?(:find_by_bitrate)
end
and I am not sure how I can do it.
I tried doing:
def respond_to?(method)
if self.public_methods.include? method
true
elsif (method == :find_by_bitrate)
define_method :find_by_bitrate, ->(default = nrb) { #songs.select |a| a[:bitrate] == nrb }
false
else
false
end
but it says "define_method is undefined". Are there any ways I can define the find_by_bitrate method?
You may define methods the first time they're called in method_missing.
Whether or not you should is open to some debate, but it's a better option than respond_to?.
class Foo
def method_missing(sym)
puts "Method missing; defining."
self.class.send(:define_method, sym) do
puts "Called #{sym}."
end
end
end
Sanity check:
f = Foo.new
=> #<Foo:0x007fa6aa09d3c0>
f.wat
=> Method wat missing; defining.
f.wat
=> Called wat.
f2 = Foo.new
=> Called wat.
I don't think you should be redefining respond_to? method. The point of the test is (probably) that the #library object should have a find_by_artist method defined and no find_by_bitrate until you add a song with a bitrate. I.e. the add_song method should define method find_by_bitrate when it sees a song with a bitrate (?).
Also, define_method is a private method of Class. Above, you're trying to call it from an instance method. See "Ruby: define_method vs. def", there's more on this stuff.
There's a lot of info missing to properly answer this. The test implies that find_by_artist is always defined even when #library is empty, but that there are dynamic methods available on other attributes (eg: bitrate) that are valid only when library contains a record with such a method.
One should not redefine respond_to? in any case. There is an explicit hook method for answering respond_to? for dynamic methods: Object#respond_to_missing?.
So a simple way to make your test pass is to be sure the #library object has a concrete method #find_by_artist and a respond to hook that checks whether any of it's elements a have the requested attribute. If I assume #library is a collection object Library which keeps an enumeration of songs in #songs
class Library
def find_by_artist artist
#songs.select { |song| song['artist'] == artist }
end
def method_missing meth, arg
m = /^find_by_(.+)$/.match meth.to_s
return super unless attr = m && m[1]
#songs.select { |song| song[attr] == arg }
end
def respond_to_missing? meth, include_private
m = /^find_by_(.+)$/.match meth.to_s
return super unless attr = m && m[1]
#songs.any? { |song| song.has_key? attr }
end
end
This has a performance problem in that respond_to? now incurs a search of all the songs. One could optimize by keeping a set of the union of all attributes contained in #songs and updating it in methods which add/update/delete elements in the collection.
Using Ruby I know you can get pretty creative with how you name your methods. For instance in rails you have .find_by_this_and_that.
How can I do this?
Example:
def get_persons_with_5_things
res = []
persons.each do |person|
if person.number_of_things == %MAGICALLY GET THE NUMBER 5 FROM FUNCTION NAME%
res << person
end
end
return res
end
I'm not even sure how you call this kind of things so any pointers would be appreciated.
I'm a little confused by your example. If you define the method with the hardcoded 5 in the method name, then you don't need to magically figure it out inside the body of the method. If you want to do something dynamic with method missing, it would be something like this:
def method_missing(name, *args)
if name.to_s =~ /get_persons_with_(\d+)_things/
number_of_things = $1.to_i
res = []
persons.each do |person|
if person.number_of_things == number_of_things
res << person
end
end
return res
else
return super(name, *args)
end
end
[EDIT (Jörg W Mittag)]: This is a more Rubyish way of implementing that same method:
def method_missing(name, *args)
return super unless name.to_s =~ /get_persons_with_(\d+)_things/
number_of_things = $1.to_i
return persons.select {|person| person.number_of_things == number_of_things }
end
super without any arguments just passes the original arguments along, no need to pass them explicitly
an early return guarded by a trailing if or unless expression greatly clears up control flow
all the each iterator does, is select items according to a predicate; however, there already is an iterator for selecting items: select
Ruby has different meta programming techniches to do this kind of stuff.
First we need our variable method
class DB
def get_persons_with_x_things(x)
res = []
persons.each do |person|
if person.number_of_things == x
res << person
end
end
return res
end
end
define_method
If there is a finite number of x's. We could use define_method to create all this methods. define_method creates a method. The first argument is the name of the method, the seccond argument or the given block is the stuff, which get's executed when the method is called.
This way, you don't realy create such method's, but It will look for the user if he calls it, as if it existed. But if the user relies on Object#methods and such, he will never see your inifinite number of fake methods.
class DB
99.times do |i|
define_method("get_persons_with_#{i}_things") do
get_persons_with_x_things(i)
end
end
end
method_missing
If there is an infinite numbor of x's method_missing would be better suited for this Task. If someone tries to call a method which does not exist, method_missing is executed instead. The first argument for method_missing is the method name as symbol, the following arguments are the original arguments.
class DB
def method_missing(name, *args)
case name.to_s
when /^get_persons_with_(\d+)_things$/
get_persons_with_x_things($1.to_i)
else
super(name, *args)
end
end
end
method_missing and send
To not use static regexe would be even cooler. But this could have some security implications. The method send I use here, calls a method by it's name.
class DB
def method_missing(name, *args)
name.to_s=~ /\d+/
# always be carefull with $ variables, they are global for this thread, so save everything as fast as you can
new_name= "#{$`}x#{$'}"
number= $1.to_i
if method_defined?(new_name)
send(new_name, number)
else
super(name, *args)
end
end
end
you can do a lot of things like this with method missing:
Ruby Docs
StackOveflow method_missing
Have a look at Ruby's callbacks specially method_missing.