So I have a question of best practice in terms of extending functionality of common Ruby datatypes.
So Rails of course has a great method pluralize which takes a string and pluralizes it. What I'm particularly interested in is that you can call
"square".pluralize
and receive
=> "squares"
in return. What I want to do is a similar sort of thing -- except for a Hash. For the sake of this question, let's say I have a method pluralize_each which pluralizes each key and value of a hash.
def pluralize_each hash
hash.each {|key,value| puts key.pluralize + "=>" + value.pluralize }
end
How can I manipulate this method so that I can call it on just a hash?
For example, I want to be able to call
{"cat" => "dog"}.pluralize_each
#=> {"cats" => "dogs"}
(Let's forget that this particular method isn't fully functional right now, with nested hashes and such.)
Is there any way to do this except for extending the Hash class? I would think that there would be a way.
With a module:
module HashPluralize
def pluralize
each { |key,value| puts "#{key.pluralize} => #{value.pluralize}" }
end
end
Hash.send(:include, HashPluralize)
# Usage example:
{'user' => 'product'}.pluralize
users => products
EDIT: WHY?
#KL-7 legitimately asked a class edit vs module extension. This is my opinion:
If you are writing something organic, modules can be useful in order to optimize refactoring / code organization. Example:
module Pluralization
def self.included(klass)
klass.send :include, Pluralization.const_get(klass.to_s)
rescue TypeError
raise NotImplementedError.new("A pluralization method for the #{klass}" +
" class is not supported at the moment")
end
module Array
def pluralize
'array pluralization implementation'
end
end
module Hash
def pluralize
'hash pluralization implementation'
end
end
end
Array.send :include, Pluralization
Hash.send :include, Pluralization
puts Array.new.pluralize
puts Hash.new.pluralize
This module permits you to define a clean way to define pluralizations for various classes; you could add a configuration, separate the pluralization methods in various files, etc.
Anyway, if you just want to add a pluralize method to Hash class, there is no problem editing the Hash class (just ensure you are not overriding another method, with a method_defined? check f.e.)
class Hash
def pluralize_each
each { |key,value| puts "#{key.pluralize} => #{value.pluralize}" }
end
end
If you want to call your own method on a Hash instance I doubt there's a way other than re-opening the class.
Making a sub-method of Hash is definitely the best way, given Ruby's object-oriented paradigm. But if you wanted to make a global method for some reason, then there's two ways to check the argument class.
One is with "instance_of?" (which tests that the argument is of a specific class) or "kind_of?" (which allows sub-classes; generally a good idea). So for example:
{a: 1, b: 2, c: 3}.kind_of? Hash
is true.
So you can check your arguments in this way. Another option is "duck-typing" to see if the object acts like a hash, for example to see if it has a ".keys" method:
{a: 1, b: 2, c: 3}.respond_to? :keys
I'd stick with inserting methods into the Hash class, however:
class Hash
def ...
end
end
or define a sub-class of Hash and put the method there:
class Hash_with_plural < Hash
def ...
end
end
because it's much cleaner.
Related
First of all, this is really just a golf question. My code works fine as it is. But I feel like there is probably a better (i.e. cooler) way to do this.
So I've got a class that acts a lot like a hash. However, it really internally generates a hash for each call to its hash-ish methods. The private method for generating that hash is calculated(). So my code currently has a lot of method definitions like this:
def each(&block)
return calculated.each(&block)
end
def length()
return calculated.length
end
Is there a concise way to delegate all those method calls to the calculated method?
I figured it out and it's incredibly simple. Just delegate to the name of the method. Here's a working example:
class MyClass
extend Forwardable
delegate %w([] []=) => :build_hash
def build_hash
return {'a'=>1}
end
end
edit: don't do this; I forgot Forwardable existed
You can write a "macro" for this. Well, Ruby doesn't technically have actual "macros" but it's a fairly common pattern nonetheless. Rails in particular uses it extensively - stuff like belongs_to, validates, etc are all class methods which are being used to generate instance-level functionality.
module DelegateToFunc
def delegate_to_func(delegate, delegators)
delegators.each do |func_name|
# Note: in Ruby 2.7 you can use |...| instead of |*args, &blk|
define_method(func_name) do |*args, &blk|
send(delegate).send(func_name, *args, &blk)
end
end
end
end
class SequenceBuilder
extend DelegateToFunc
delegate_to_func(:calculated, [:length, :each])
attr_accessor :min, :max
def initialize(min:, max:)
#min, #max = min, max
end
def calculated
min.upto(max).to_a
end
end
SequenceBuilder.new(min: 5, max: 10).length # => 6
SequenceBuilder.new(min: 1, max: 4).each { |num| print num } # => 1234
I will say, though, that methods generated by metaprogramming can sometimes be hard to track down and can make a program confusing, so try and use them tastefully ...
For example, do you really need your object to expose these hash-like methods? Why not just let the caller read the hash via calculated, and then call the hash methods directly on that?
Assuming I have defined a class with accessors defined using attr_accessor:
class A
attr_accessor :alpha, :beta, :gamma
def initialize
self.alpha = 1
end
end
Is there a built-in method that gives the list of method names passed to an attr_accessor call? Or do I have to define a constant with the symbols, and pass it to attr_accessor?
There's no built-in method. Your solution of storing the method names at creation time will work, as long as you know in advance and can control what the method names are.
In my answer to a different but similar question, I showed how to get the names of the methods dynamically, after the fact, using TracePoint. I've updated it below to include :attr_reader and :attr_writer.
module MethodTracer
TracePoint.trace(:c_call) do |t|
if %i[attr_accessor attr_writer attr_reader].include?(t.method_id)
t.self.extend(MethodTracer)
methods = t.self::Methods ||= []
MethodTracer.send(:define_method, :method_added) {|m| methods << m }
end
end
TracePoint.trace(:c_return) do |t|
if %i[attr_accessor attr_writer attr_reader].include?(t.method_id)
MethodTracer.send(:remove_method, :method_added)
end
end
end
class Foo
attr_accessor :a
attr_reader :b
attr_writer :c
def foo; end
end
Foo::Methods # => [:a, :a=, :b, :c=]
I've stored the method names in the Methods constant, but obviously you can store them wherever is most convenient for you.
Defining/removing method_added on MethodTracer ensures that you don't clobber any Foo.method_added you've defined yourself. This methodology, however, does require that if you define Foo.method_added before your calls to attr_*, you will need to call super inside it. Otherwise you will skip the temporary method_added defined by MethodTracer.
Grep an Instance for Setter Methods
One way to do this would be to grep an instance of the class for setters. For example:
A.new.methods.grep(/\p{alnum}+=\z/)
#=> [:alpha=, :beta=, :gamma=]
No, that's not possible. Methods generated by attr_accessor, attr_reader and attr_writer are indistinguishable from ones written by hand. In fact, they must be indistinguishable from ones written by hand!
Say, you have a simple attr_accessor, but you later want to refactor it to do something more intelligent (e.g. caching). This is a purely internal change, a client must not be able to observe the difference, otherwise it would be a breach of encapsulation!
If you simply want a list of setters, that's easy enough: setters are methods whose name ends with an = sign:
A.public_instance_methods(false).grep(/=$/)
# => [:alpha=, :beta=, :gamma=]
For getters, it's trickier: any method that doesn't take an argument could be a getter, but it could also be a side-effecting method (e.g. Array#clear):
A.public_instance_methods(false).select {|m|
A.public_instance_method(m).arity.zero?
}
# => [:alpha, :beta, :gamma]
I have a class derived from Hash. When I use it in IRB, it dumps the contents of the hash to the console. Instead, I would like it to print the same way as the Object.inspect/.to_s (not sure which) in the form #<MyHash:0x201e4c0>. How do I achieve this?
EDIT I had to remove the secondary question because it led to confusion. The question above is what I need answered. This is what I removed:
Is there a way to call the method of a class higher up in the inheritance hierarchy by any chance?
One of the basic chores when writing a new class that you want to share to others is to define a few basic methods, among them #inspect, #to_s, #==, #<=> and several others. So you yourself have to write your inspect function when making a new class.
So much for your duties. But now, to your question, which can be generally stated as how to utilize methods of deep ancestors in a class, one way would be like this:
class MyHash < Hash
def inspect
Object.instance_method( :inspect ).bind( self ).call
end
end
Here, the requirement is that MyHash be a descendant of Object, otherwise the UnboundMethod instance won't bind to it. (For Object, this is obviously true, but might not be the case in general.)
UPDATED AFTER COMMENTS:
The code above allows MyHash to call Object instance method inspect. To percieve the effect strongly, one has to repeat the procedure with method to_s, which is called by Object#inspect:
class MyHash < Hash
def inspect
Object.instance_method( :inspect ).bind( self ).call
end
end
h = MyHash[ a: 1, b: 2 ]
#=> {:a=>1, :b=>2 }
# The above code did work, but Object#inspect happens to call to_s() instance method
# To see the change
class MyHash
def to_s
Object.instance_method( :to_s ).bind( self ).call
end
end
h.inspect
#=> "#<MyHash:0x90ee01c>"
I wrote code for an Enumerables module:
module Enumerables
def palindrome?
if self.is_a?(Hash)
return false
else
self.join('').gsub(/\W/,"").downcase == self.join('').gsub(/\W/,"").downcase.reverse
end
end
end
The problem is, I have to write these:
class Array
include Enumerables
end
class Hash
include Enumerables
end
to make the code run successfully.
Is there an easy way to make the "palindrome?" method run with different instance types?
The module is not Enumerables but Enumerable so if you have
module Enumerable
def palindrome?
...
end
end
it will work without includes.
If you want to add this method to all objects see texasbruce's answer.
Open any class below Object level and add this method there. Then it will be accessible to almost all built-in types and all user defined types.
You can put it in Object, Kernel(it is a module), BasicObject.
For example,
class Object
def foo
puts "hello"
end
end
[].foo
You could use the ObjectSpace.each_object iterator with a filter to find classes that include Enumerable and extend them dynamically:
# XXX: this iterator yields *every* object in the interpreter!
ObjectSpace.each_object do |obj|
if obj.is_a?(Class) && (obj < Enumerable)
obj.module_eval { include Enumerables }
end
end
[1,2,1].palindrome? # => true
{}.palindrome? # => false
Now the trick is to write something that works for all enumerable types in a meaningful way! Note also that this sort of metaprogramming is fun for kicks but has serious implications if you plan to use it for anything other than "toy" programs.
Basically I have two modules: CoreExtensions::CamelcasedJsonString and …::CamelcasedJsonSymbol. The latter one overrides the Symbol#to_s, so that the method returns a String which is extended with the first module. I don't want every string to be a CamelcasedJsonString. This is the reason why I try to apply the extension instance specific.
My problem is, that Symbol#to_s seems to be overridden again after I included my module (the last spec fails):
require 'rubygems' if RUBY_VERSION < '1.9'
require 'spec'
module CoreExtensions
module CamelcasedJsonString; end
module CamelcasedJsonSymbol
alias to_s_before_core_extension to_s
def to_s(*args)
to_s_before_core_extension(*args).extend(CamelcasedJsonString)
end
end
::Symbol.send :include, CamelcasedJsonSymbol
end
describe Symbol do
subject { :chunky_bacon }
it "should be a CamelcasedJsonSymbol" do
subject.should be_a(CoreExtensions::CamelcasedJsonSymbol)
end
it "should respond to #to_s_before_core_extension" do
subject.should respond_to(:to_s_before_core_extension)
end
specify "#to_s should return a CamelcasedJsonString" do
subject.to_s.should be_a(CoreExtensions::CamelcasedJsonString)
end
end
However the following example works:
require 'rubygems' if RUBY_VERSION < '1.9'
require 'spec'
module CoreExtensions
module CamelcasedJsonString; end
end
class Symbol
alias to_s_before_core_extension to_s
def to_s(*args)
to_s_before_core_extension(*args).extend(CoreExtensions::CamelcasedJsonString)
end
end
describe Symbol do
subject { :chunky_bacon }
it "should respond to #to_s_before_core_extension" do
subject.should respond_to(:to_s_before_core_extension)
end
specify "#to_s should return a CamelcasedJsonString" do
subject.to_s.should be_a(CoreExtensions::CamelcasedJsonString)
end
end
Update: Jan 24, 2010
The background of my problem is that I try to convert a huge nested hash
structure into a JSON string. Each key in this hash is a Ruby Symbol in the
typical underscore notation. The JavaScript library which consumes the JSON
data expects the keys to be strings in camelcase notation. I thought that
overriding the Symbol#to_json method might be the easiest way. But that
didn't work out since Hash#to_json calls first #to_s and afterwards
#to_json on each key. Therefore I thought it might be a solution to extend
all Strings returnd by Symbol#to_s with a module which overrides the
#to_json method of this specific string instance to return a string that has
a #to_json method which returns itself in camelcase notation.
I'm not sure if there is an easy way to monkey patch Hash#to_json.
If someone wants to take a look into the JSON implementation I'm using, here is the link: http://github.com/flori/json/blob/master/lib/json/pure/generator.rb (lines 239 and following are of interest)
Your second monkeypatch works since you are re-opening the Symbol class.
The first one doesn't because all the include does is add the module in the list of included modules. These get called only if the class itself doesn't define a specific method, or if that method calls super. So your code never gets called.
If you want to use a module, you must use the included callback:
module CamelcasedJsonSymbol
def self.included(base)
base.class_eval do
alias_method_chain :to_s, :camelcase_json
end
end
def to_s_with_camelcase_json(*args)
to_s_without_camelcase_json(*args).extend(CamelcasedJsonString)
end
end
I've used active_record alias_method_chain, which you should always do when monkey patching. It encourages you to use the right names and thus avoid collisions, among other things.
That was the technical answer.
On a more pragmatic approach, you should rethink this. Repeatedly extending strings like this is not nice, will be a huge performance drain on most implementations (it clears the whole method cache on MRI, for instance) and is a big code smell.
I don't know enough about the problem to be sure, or suggest other solutions (maybe a Delegate class could be the right thing to return?) but I have a feeling this is not the right way to arrive to your goals.
Since you want to convert the keys of a hash, you could pass an option to #to_json and monkeypatch that instead of #to_s, like:
{ :chunky_bacon => "good" }.to_json(:camelize => true)
My first idea was to monkeypatch Symbol#to_json but that won't work as you point out because Hash will force the keys to strings before calling to_json, because javascript keys must be strings. So you can monkeypatch Hash instead:
module CamelizeKeys
def self.included(base)
base.class_eval do
alias_method_chain :to_json, :camelize_option
end
end
def to_json_with_camelize_option(*args)
if args.empty? || !args.first[:camelize]
to_json_without_camelize_option(*args)
else
pairs = map do |key, value|
"#{key.to_s.camelize.to_json(*args)}: #{value.to_json(*args)}"
end
"{" << pairs.join(",\n") << "}"
end
end
end
That looks kind of complicated. I probably don't understand what it is you're trying to achieve, but what about something like this?
#!/usr/bin/ruby1.8
class Symbol
alias_method :old_to_s, :to_s
def to_s(*args)
if args == [:upcase]
old_to_s.upcase
else
old_to_s(*args)
end
end
end
puts :foo # => foo
puts :foo.to_s(:upcase) # => FOO
and a partial spec:
describe :Symbol do
it "should return the symbol as a string when to_s is called" do
:foo.to_s.should eql 'foo'
end
it "should delegate to the original Symbol.to_s method when to_s is called with unknown arguments" do
# Yeah, wish I knew how to test that
end
it "should return the symbol name as uppercase when to_s(:upcase) is called" do
:foo.to_s(:upcase).should eql "FOO"
end
end