Can ampersand be used to print a hash in Ruby? - ruby

Didn't really know how to title this question.
Consider an arbitrary hash (ENV is convenient):
ENV.each { |key, val| puts key + ': ' + val }
LC_MESSAGES: en_US.utf-8
LC_COLLATE: en_US.utf-8
PWD: /Users/baller/ive_fallen_and_i_cant_get_up
LC_MONETARY: en_US.utf-8
Is it possible to use the &: shorthand to do this?
foo.each(&:bar)

You can get away with two block variables and have one instead by doing this:
each{|kv| puts kv.join(": ")}

By default, no.
To do what you want, you would need to add a method to the Array class, which prints out the way you want, something like:
class Array
def bar
puts "#{self[0]}: #{self[1]}"
end
end
with that, ENV.each(&:bar) will do what you expect.
That said, I would not recommend this. Adding to a base class is something that should only be done when the utility far outweighs the potential for future conflicts, and the fact that this method is highly specialized for arrays with at least 2 elements in them.
Not related, but concatenating strings via + is measurably slower than using interpolation. It creates extra objects unnecessarily.

In general, this is possible if the elements of the collection respond to that method. For example:
class Foo
attr_reader :bar
def initialize(bar)
#bar = bar
end
end
foos = [Foo.new("one"), Foo.new("two"), Foo.new("three")]
p foos.map(&:bar) #=> ["one", "two", "three"]
This works because &symbol is syntactic sugar for symbol.to_proc, which in turn works some magic to return a block that sends that message to the object it receives as an argument.
While this won't work for your example (because the objects in ENV don't respond to :bar), you can pass a block that's been stored in a variable by using &:
block = lambda { |key, value| puts "#{key}: #{value}" }
ENV.each(&block)

In your example, using &:bar in place of a block will result in #to_proc being called on the given object, in this case, the symbol :bar. The #to_proc implementation of Symbol in Ruby basically expands foo.each(&:bar) to foo.each { |i| i.bar }. Since there are two arguments yielded from a hash, i in this example is an array of the key, value args. This is why you'd have to extend Array (as described by #x1a4) to get your hash to treat &:bar as expected.
As an alternative, you can create your own class that responds to #to_proc or simply implement your block as a Proc:
class Bar
def to_proc
Proc.new { |key, val| puts key + ': ' + val }
end
end
bar = Bar.new
# or
bar = Proc.new { |key, val| puts key + ': ' + val }
With a handle to bar, you can pass &bar in place of a block to hashes like ENV. So given a hash, foo:
foo.each(&bar)
A great post for more reading on this subject: http://weblog.raganwald.com/2008/06/what-does-do-when-used-as-unary.html

Related

Is there any way to use more than one attribute/method with ampersand colon operator in Ruby?

It would be something like this: Given an array of foo objects with an attribute bar which also has an attribute called baz:
foos.each(&:bar.baz)
I can easily get all bars with foos.each(&:bar), but the baz attribute (or however many others could it be) I cannot, as it gives me the error:
TypeError: wrong argument type String (expected Proc)
Use .map:
foos.map(&:bar).each(&:baz)
This will turn your array of foo items into an array of bar items, allowing you to call .each on that instead.
Array#each returns its receiver, so it may not be the best example of what you are looking for. Suppose we have
arr = [['a', 'b'], ['c', 'd']]
and want
arr.map(&:first).map(&:upcase)
#=> ["A", "C"]
To use a single ampersand one could write
arr.map(&Proc.new { |e| e.first.upcase })
#=> ["A", "C"]
& converts the proc to a block, causing
arr.map { |e| e.first.upcase }
to be executed.
Is it possible with ampersand colon operator
No. Primarily because there's no such thing as "ampersand colon operator". This
foos.each(&:bar)
is a shorter way of writing this
sym = :bar
foos.each(&sym)
Also see other answers for alternatives.
The best option is to just use the regular block syntax:
foos.each { |foo| foo.bar.baz }
If you still want to use the &:sym type of syntax, for example because the method names are dynamic, you can check out the Xf gem. It enables you to do:
foos.each(&Xf.pipe(:bar, :baz))
You can replicate the functionality without including the gem simply by borrowing the pipe method:
def pipe(*fns)
Proc.new do |target|
new_value = target
fns.each { |fn| new_value = fn.to_proc.call(new_value) }
new_value
end
end
require 'ostruct'
# openstruct used to emulate objects that would respond to .foo.bar
foos = []
foos << OpenStruct.new(foo: OpenStruct.new(bar: 'hello'))
foos << OpenStruct.new(foo: OpenStruct.new(bar: 'world'))
foos.each(&pipe(:foo, :bar))

Can I reject objects which do not meet my criteria as they are entered into an array?

I know there are a number of ways to create new elements in an existing ruby array.
e.g.
myArray = []
myArray + other_array
myArray << obj
myArray[index] = obj
I'm also pretty sure I could use .collect, .map, .concat, .fill, .replace, .insert, .join, .pack and .push as well to add to or otherwise modify the contents of myArray.
However, I want to ensure that myArray only ever includes valid HTTP/HTTPS URLs.
Can anyone explain how I can enforce that kind of behaviour?
I would create a module that allows you to specify an acceptance block for an array, and then override all the methods you mention (and more, like concat) to pre-filter the argument before calling super. For example:
module LimitedAcceptance
def only_allow(&block)
#only_allow = block
end
def <<( other )
super if #only_allow[ other ]
end
def +( other_array )
super( other_array.select(&#only_allow) )
end
end
require 'uri'
my_array = []
my_array.extend LimitedAcceptance
my_array.only_allow do |item|
uri = item.is_a?(String) && URI.parse(item) rescue nil
uri.class <= URI::HTTP
end
my_array << "http://phrogz.net/"
my_array << "ftp://no.way"
my_array += %w[ ssh://bar http://ruby-lang.org http:// ]
puts my_array
#=> http://phrogz.net/
#=> http://ruby-lang.org
Create a class to encapsulate behavior you want. Then you can create your << method doing the verifications you want.
Put all logic that handle this data in methods in this domain class. Probably you will discover code floating around the use of this data to move to the new class.
My 2 cents.
Use this to insert. (untested).
def insert_to_array(first_array, second_array)
second_array.each do |i| {
if URI.parse(i).class == URI::HTTP
first_array.insert(i)
end
}
first_array
end

Ruby: How to chain multiple method calls together with "send"

There has to be a built in way of doing this, right?
class Object
def send_chain(arr)
o=self
arr.each{|a| o=o.send(a) }
return o
end
end
I just ran across this and it really begs for inject:
def send_chain(arr)
arr.inject(self) {|o, a| o.send(a) }
end
Building upon previous answers, in case you need to pass arguments to each method, you can use this:
def send_chain(arr)
Array(arr).inject(self) { |o, a| o.send(*a) }
end
You can then use the method like this:
arr = [:to_i, [:+, 4], :to_s, [:*, 3]]
'1'.send_chain(arr) # => "555"
This method accepts single arguments as well.
No, there isn't a built in way to do this. What you did is simple and concise enough, not to mention dangerous. Be careful when using it.
On another thought, this can be extended to accept arguments as well:
class Object
def send_chain(*args)
o=self
args.each do |arg|
case arg
when Symbol, String
o = o.send arg # send single symbol or string without arguments
when Array
o = o.send *arg # smash the inner array into method name + arguments
else
raise ArgumentError
end
end
return o
end
end
this would let you pass a method name with its arguments in an array, like;
test = MyObject.new
test.send_chain :a_method, [:a_method_with_args, an_argument, another_argument], :another_method
How about this versatile solution without polluting the Object class:
def chain_try(arr)
[arr].flatten.inject(self_or_instance, :try)
end
or
def chain_send(arr)
[arr].flatten.inject(self_or_instance, :send)
end
This way it can take a Symbol, a String or an Array with a mix of both even.🤔
example usage:
chain_send([:method1, 'method2', :method3])
chain_send(:one_method)
chain_send('one_method')

How can you make a ruby function accept either an individual item or collection of items and do the same thing?

I'm trying to write a function that would apply the function to just one item if it's not a collection, or else apply that function to each of the collections elements. For example:
replace spaces with underscores
foo 'a bear' => 'a_bear'
foo ['a bear', 'a bee'] => ['a_bear', 'a_bee']
Is this possible?
It depends on how you define "collection". The most natural option would probably be either "any Enumerable" or even "anything with an each method". However this leads to a problem because Strings are Enumerable, too - in ruby 1.8, at least.
If you only need it to work with arrays, it's easy:
def foo(x)
if x.is_a? Array
x.map {|item| foo(item)}
else
# Do something with x
end
end
Personally I would use variable args:
def foo(*args)
args.each { |arg| puts arg }
end
foo("bar") # bar
foo("bar", "foobar") # bar \n foobar
foo(*%w(bar foobar)) # bar \n foobar
a = ["bar", "foobar"]
foo(*a) # bar \n foobar
foo("baz", *a) # baz \n bar \n foobar
a = "bar"
foo(*a) # bar
If you don't know whether or not your argument is a string or an array then just prepend it with a *.
I find this gives the maximum flexibility when dealing with arrays which might instead be a single value as I can enter them as just arguments if I am initializing the array or safely pass in the variable if I know it will either be an array or a single argument. It will choke on hashes though.
You may be interested in the splat operator
def foo(x)
[*x].map {|item| item.gsub(" ", "_")}
end
Unfortunately, this'd return foo("a bear") as ["a_bear"], rather than "a_bear" without the array.
Not sure if I'm misreading the question or not. The below will make it so a function will treat either a single element or an array of elements the same way. Just array-ifies the argument if it's not already an array, and undoes that at the end if necessary.
def foo(x)
x = [x] unless x.is_a? Array
# do array stuff to x
return result.size > 1 ? result : result.first
end

how to name an object reference (handle) dynamically in ruby

So I have a class like this:
def Word
end
and im looping thru an array like this
array.each do |value|
end
And inside that loop I want to instantiate an object, with a handle of the var
value = Word.new
Im sure there is an easy way to do this - I just dont know what it is!
Thanks!
To assign things to a dynamic variable name, you need to use something like eval:
array.each do |value|
eval "#{value} = Word.new"
end
but check this is what you want - you should avoid using eval to solve things that really require different data structures, since it's hard to debug errors created with eval, and can easily cause undesired behaviour. For example, what you might really want is a hash of words and associated objects, for example
words = {}
array.each do |value|
words[value] = Word.new
end
which won't pollute your namespace with tons of Word objects.
Depending on the data structure you want to work with, you could also do this:
# will give you an array:
words = array.map { |value| Word.new(value) }
# will give you a hash (as in Peter's example)
words = array.inject({}) { |hash, value| hash.merge value => Word.new }
# same as above, but more efficient, using monkey-lib (gem install monkey-lib)
words = array.construct_hash { |value| [value, Word.new ] }

Resources