Keep if on object - ruby

I'm looking for a clean way to evaluate an object and return object if condition is verified, nil otherwise so that I can use a default value instead. Something like:
result = object.verify?{ |object| object.test? } || default_value
I can see several ways to implement this, but I am hoping that there is a built-in way to do that. Ex:
Going to the Array level
def verify?(&block)
Array(self).filter(block).first
end
Using instance_eval
def verify?(&block)
self.instance_eval{ |object| yield(object) ? object : nil}
end
EDIT
Here's my actual example (though the question isn't tighted to it):
class User < ActiveRecord::Base
def currency
self.billing_information.try(:address).try(:country).try(:currency_code).instance_eval{ |currency| Finance::CURRENCIES.include?(currency) ? currency : nil} || 'EUR'
end
end
I know this is ugly, but I do like the logic of it: if the object I'm looking for exists, go get the next one. The first conditions are existence (with try), then inclusion.

Regarding your actual problem - Rails provides presence_in:
Returns the receiver if it's included in the argument otherwise returns nil.
'EUR'.presence_in %w(EUR USD) #=> "EUR"
'JPY'.presence_in %w(EUR USD) #=> nil
I would probably separate the actual currency from the verified currency (so you can still access the former one):
class User < ActiveRecord::Base
def currency
billing_information.try(:address).try(:country).try(:currency_code)
end
def verified_currency
Finance::CURRENCIES.include?(currency) ? currency : 'EUR'
end
end
And move the logic for checking a currency and providing a default one into Finance:
class User < ActiveRecord::Base
def currency
billing_information.try(:address).try(:country).try(:currency_code)
end
def verified_currency
Finance.verified_currency(currency)
end
end
module Finance
CURRENCIES = %w(EUR USD)
DEFAULT_CURRENCY = 'EUR'
def self.verified_currency(currency)
CURRENCIES.include?(currency) ? currency : DEFAULT_CURRENCY
end
end
This also avoids having to evaluate User#currency twice.
The try-chain can be replaced by delegate:
class User < ActiveRecord::Base
delegate :currency, to: billing_information, allow_nil: true
def verified_currency
Finance.verified_currency(currency)
end
end
class BillingInformation < ActiveRecord::Base
delegate :currency, to: address, allow_nil: true
end
class Address < ActiveRecord::Base
delegate :currency, to: country, allow_nil: true
end

If you want the receiver when the condition is satisfied, then assuming that test? returns a truthy value when the condition is met and a falsy value otherwise:
result = object.tap{|object| break unless object.test?} || default_value
Or, following Stefan's suggestion:
result = object.tap{|object| break default_value unless object.test?}

I think #Stefan's suggestion is best, but if you insist on being obtuse, you could write:
(object.test? && object) || default

If you think you need Object#try version that can handle default values, you can monkey patch it.
require "rails"
class Object
alias_method :old_try, :try
def try(params, default = nil, &block)
old_try(params, &block) || default
end
end
object = {}
default = "I'm default"
p result = object.try(:test, default)
#=> I'm default

I think you're looking for Enumerable#detect:
detect(ifnone = nil) { |obj| block } → obj or nil
Passes each entry in enum to block. Returns the first for which block is not false. If no object matches, calls ifnone and returns its result when it is specified, or returns nil otherwise.
(1..10).detect { |i| i % 5 == 0 and i % 7 == 0 } #=> nil
(1..100).find { |i| i % 5 == 0 and i % 7 == 0 } #=> 35
So you could do result = object.detect { |object| object.test? } || default_value as you've written, or the more idiomatic result = object.detect(default_value) { |object| object.test? }.

Related

How create Ruby Class with same object id

I need to create a class where if the attribute value is the same it does not generate a new object id, example:
result:
described_class.new('01201201202')
<PixKey:0x00007eff5eab1ff8 #key="01201201202">
if i run it again with the same value it should keep the same object id
0x00007eff5eab1ff8
is similar behavior with the symbol
test:
describe '#==' do
let(:cpf) { described_class.new('01201201202') }
it 'verifies the key equality' do
expect(cpf).to eq described_class.new('01201201202')
end
end
Running the test shows an error, because the obejct id changes:
expected: #<PixKey:0x00007eff5eab1ff8 #key="01201201202">
got: #<PixKey:0x00007eff5eab2070 #key="01201201202">
Class:
class PixKey
def init(key)
#key = key
end
end
The other answers are fine, but they are a little more verbose than needed and they use class variables, which I find to be a confusing concept because of how they are shared among various classes.
class PixKey
#instances = {}
def self.new(id)
#instances[id] ||= super(id)
end
def initialize(id)
#key = id
end
end
p PixKey.new(1)
p PixKey.new(2)
p PixKey.new(2)
p PixKey.new(1)
Running the test shows an error, because the object id changes
Not quite. It shows an error because the objects are not equal. And the error message prints both objects including their id. But the object id is not what's causing the test to fail.
I need to create a class where if the attribute value is the same it does not generate a new object id
That would probably work, but you're likely approaching the problem from the wrong side. In Ruby, equality doesn't mean object identity. Two objects can be equal without being the same object, e.g.
a = 'foo'
b = 'foo'
a.object_id == b.object_id
#=> false
a == b
#=> true
There's no need to tinker with object ids to get your test passing. You just have to implement a custom == method, e.g.:
class PixKey
attr_reader :key
def initialize(key) # <- not "init"
#key = key
end
def ==(other)
self.class == other.class && self.key == other.key
end
end
The == method checks if both objects have the same class (i.e. if both are PixKey instances) and if their key's are equal.
This gives:
a = PixKey.new('01201201202')
b = PixKey.new('01201201202')
a == b
#=> true
Create a class method to create instances and have it look up a hash.
class PixKey
##instances = {}
def PixKey.create(id)
if not ##instances.has_key?(id)
##instances[id] = PixKey.new(id)
end
return ##instances[id]
end
def initialize(id)
#key = id
end
end
a = PixKey.new(123)
b = PixKey.new(123)
c = PixKey.create(123)
d = PixKey.create(123)
puts a
puts b
puts c
puts d
Output:
#<PixKey:0x000000010bc39900>
#<PixKey:0x000000010bc38078>
#<PixKey:0x000000010bc33eb0>
#<PixKey:0x000000010bc33eb0>
Notice the last two instances created with the PixKey.create(id) method return the same instance.
Note that Ruby's new method is just a method on Class and can be overridden like any other. The docs describe the default implementation.
Calls allocate to create a new object of class's class, then invokes that object's initialize method, passing it args. This is the method that ends up getting called whenever an object is constructed using .new.
So, if you want to keep the .new syntax and still get the same objects back, we can override new on the class and call super. This is exactly what OscarRyz' answer does, just with .new and super rather than a separate helper function.
class PixKey
##instances = {}
def PixKey.new(id)
if not ##instances.has_key?(id)
##instances[id] = super(id)
end
return ##instances[id]
end
def initialize(id)
#key = id
end
end
a = PixKey.new(123)
b = PixKey.new(123)
puts a
puts b

Ruby - Using nil parameters to support different types of an class

In Ruby, say I have a class called Song. I would like to know how correcty to write the initialize so that it supports 2 different types of. For example:
song_full = Song.new(fromSomeCloudStorage)
song_preview = Song.new(fromLocalStorage)
Then say I have a Song class, where I always want to assign the #time_stamp, but then depending on whether there is cloud_storage or not, assign #cloud_store_spec
def initialize(cloud_storage = nil, time_stamp = nil, local_storage = nil)
#time_stamp = time_stamp || (Time.now.to_f * 1000).to_i.to_s
#cloud_store_spec = cloud_storage
end
I'm thinking of using nil as I have done, however would the code know which is cloud_storage and which is local_storage. Is it actually possible?
Any help appreciated?
First off, use keyword arguments to allow you to pass the relevant data to the initializer (up to you if you want to include time_stamp as a keyword or regular arg):
def initialize(cloud_storage: nil, local_storage: nil, time_stamp: nil)
#time_stamp = time_stamp || (Time.now.to_f * 1000).to_i.to_s
#cloud_store_spec = cloud_storage
end
This will make things a lot clearer when calling the class, allowing:
song_full = Song.new(cloud_storage: fromSomeCloudStorage)
song_preview = Song.new(local_storage: fromLocalStorage)
In terms of how your code will know whether the code is from cloud or local, if you mean the class's instances, you can just check for the presence of #cloud_store_spec, something like:
def cloud_storage?
#cloud_store_spec.present?
end
Then, from anywhere else in your code, you can call:
song_full = Song.new(cloud_storage: fromSomeCloudStorage)
song_full.cloud_storage? # => true
song_preview = Song.new(local_storage: fromLocalStorage)
song_preview.cloud_storage? # => false
Hope that helps and I'm reading you right :) Let me know how you get on or if you've any questions.
Update for Ruby 1.9.3
As keyword arguments were introduced in Ruby 2.0, for 1.9.3 you can use an options hash:
def initialize(options = {}) # again, timestamp can be a separate arg if you'd prefer
#time_stamp = options[:time_stamp] || (Time.now.to_f * 1000).to_i.to_s
#cloud_store_spec = options[:cloud_storage]
end
The rest of the code will remain the same.
If CloudStorage or LocalStorage can be considered as classes:
class LocalStorage
def initialize; p 'LocalStorage class initialized'; end
end
class CloudStorage
def initialize; p 'CloudStorage class initialized'; end
def play; p "...playing song"; end
end
Maybe you could consider to hard code a Hash, (let apart time_stamp):
class Song
attr_reader :store_spec
def initialize(kind = nil)
storage_kind = {cloud: CloudStorage, preview: LocalStorage}
#store_spec = storage_kind[kind].new
end
def storage_kind
#store_spec.class
end
def play
#store_spec.play
end
end
So you can call for example:
song = Song.new(:cloud) #=> "CloudStorage class initialized"
song.storage_kind #=> CloudStorage
song.play #=> "...playing song"

What is the "other" object and how does it work?

I have seen other used often in class comparisons, such as
def ==(other)
...
end
or
def eql?(other)
self == other
end
but I still have found no explanation of what it actually is. What's going on here?
And perhaps this is for another question, but what does starting a method with == imply?
In ruby, operators are in fact method calls. If you have two variables a and b and want to check their equality, you generally write a == b, but you could write a.==(b). The last syntax shows what happens during an equality check : ruby calls a's method == and passes it b as an argument.
You can implement custom equality check in your classes by defining the == and/or the eql? methods. In your example, other is simply the name of the argument it receives.
class Person
attr_accessor :name
def initialize name
#name = name
end
end
a = Person.new("John")
b = Person.new("John")
a == b # --> false
class Person
def == other
name == other.name
end
end
a == b # --> true
For your second question, the only methods starting with == you're allowed to implement are == and ===. Check here for the full list of restrictions on method names in ruby: What are the restrictions for method names in Ruby?
other is a parameter to this method, the object, that is being passed.
For example:
class A
def ==(other)
:lala == other
end
end
obj = A.new
obj.==(:foo) # full syntax, just like any other method
# but there's also a shorthand for operators:
obj == :foo # => false
obj == :lala # => true
other is the parameter for == and it represents the object you are comparing with.
Example
x == y
The == method (yes, its just a method!), on your x object, gets called with y as a parameter.
Welcome to Ruby, you'll love it after a while :)
other, in this case, is the object you're comparing to, so this:
class SomeClass
def ==(other)
self == other
end
end
means:
SomeClass.new == nil # here "other" is nil, this returns false
Basically def ==(other) is the implementation of the == operator, for cases where you do something specific in your class regarding comparison using ==.
For numbers you can get the sum of 2 numbers using:
a= x + y
in Ruby everything is object, Right! so x and y are objects and for Number(Object) x it has a defined method + which accept 1 paramter which is y
same for what you are trying to understand, what if you have 2 classes and you want to check if they are equal or not and its your defined class and you want to specify how the are equal for example if their name attribute is equal then you can say:
class Student
def ==(other)
self.name == other.name
end
end
fi = Student.new(name: 'Mark')
sec = Student.new(name: 'Hany')
if (fi == sec)
# do something here

Extending BaseClass method (Polymorphism)

I played around with inheritances and tried to extend a method from the base class from the subclass. Polymorphism. Basically, I tried to extend the base method (status) with an additional option.
However, for some reason it doesn't work as expected. I thought B.new.status(1) would return 2 instead of nil.
If I place super after "y if opt == 2" then the results are vice verse.
Why is that and how can I solve this problem?
Many thanks in advance!
class A
attr_reader :x
def initialize
#x = 2
end
def status(opt)
x if opt == 1
end
end
class B < A
attr_reader :y
def initialize
super
#y = 10
end
def status(opt)
super
y if opt == 2
end
end
B.new.status(1)
=> nil
B.new.status(2)
=> 2
That's because the return value from A's status is not used in B's status. Yes, in A's status, 'x' will be returned, but that isn't used in B's status.
The result of the last statement in a method is automagically returned, but super isn't the last statement in B's status.
If the codition of an if statement is not satisfied, it returns nil. So you should do something like this:
if opt == 2
y
else
super
end

Ruby define_method

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.

Resources