What does this method do in Ruby? - ruby-on-rails

This is actually in a Rails helper that I've seen. I see it's trying to create a hash from names, which appears to be anything that includes the Enumerable module. It's creating a hash of keys.. but where is the binding coming from? how do you pass it one? and what is happening with eval(key, binding)?
def locals_hash(names, binding)
names.inject({}) {|memo, key| memo[key.to_sym] = eval(key, binding); memo}
end

In addition to Ken's comment, here's an example:
def locals_hash(names, binding)
names.inject({}) {|memo, key| memo[key.to_sym] = eval(key, binding); memo}
end
def m
a = 3
b = 'foo'
binding
end
locals_hash ['a', 'b'], m
#=> {:a=>3, :b=>"foo"}

Related

Having a hash, set object properties in Ruby

Having a hashmap, such as:
{:foo => 1, :bar => 2}
in Ruby, is there an easy way to assign those values as properties of the object, to automatically cause this to happen:
obj.foo = 1
obj.bar = 2
To be precise, some Ruby-idiomatic way of doing:
hashmap.each { |k,v| obj.send("#{k}=", v) }
obj is an object that doesn't inherit ActiveModel and it's not a Struct and I can't control it's type as it's coming from a third party library.
I'm using Rails, so if the answer comes from Rails, that's acceptable.
What you have there is (almost) the most concise, readable, idiomatic solution already:
hashmap.each { |k,v| obj.send("#{k}=", v) }
There is only one thing left to improve:
hashmap.each { |k,v| obj.public_send("#{k}=", v) }
Use public_send instead of send to make it clear to others that you are using it only to pass a method name dynamically and not to circumvent access restrictions.
Maybe you could create an OpenStruct from your hash, do whatever you need with the OpenStruct and its attributes, then convert it back into a hash?
require 'ostruct'
h = {:foo => 1, :bar => 2}
o = OpenStruct.new(h)
o.foo # Output: => 1
o.bar # Output: => 2
# if necessary, convert it back into a hash
h = o.to_h
From the Ruby docs:
An OpenStruct is a data structure, similar to a Hash, that allows the
definition of arbitrary attributes with their accompanying values.
This is accomplished by using Ruby’s metaprogramming to define methods
on the class itself.
An OpenStruct utilizes Ruby’s method lookup structure to and find and
define the necessary methods for properties. This is accomplished
through the method method_missing and define_method.
This should be a consideration if there is a concern about the
performance of the objects that are created, as there is much more
overhead in the setting of these properties compared to using a Hash
or a Struct.
Doing this on mobile so let's give this a try:
class Something
attr_reader :foo, :bar
def initialize(hash = {})
foo = hash[:foo]
bar = hash[:bar]
end
end
obj = Something.new({foo: 1, bar: 2})
obj.foo = 1
obj.bar =2
class Klass
def initialize(h)
h.each { |iv, val| instance_variable_set("##{iv}", val) }
end
end
k = Klass.new(:foo => 1, :bar => 2)
#=> #<Klass:0x007ff1d9073118 #foo=1, #bar=2>
You can use method_missing if you need to set data dynamically.
class Foo
def method_missing(sym, *args)
super unless instance_variables.include?("##{sym}".to_sym)
instance_variable_get("##{sym}")
end
end
obj = Foo.new
h = {:foo => 1, :bar => 2}
h.each { |k, v| obj.instance_variable_set("##{k}", v) }
obj.foo
# => 1
obj.bar
# => 2
If you are using Rails, a minor improvement on your suggested solution would be to use Object#try from Active support extensions
hashmap.each {|k,v| obj.try "#{k}=", v }
As per documentation,
Invokes the public method whose name goes as first argument just like
public_send does, except that if the receiver does not respond to it
the call returns nil rather than raising an exception.

How to implement injection in Ruby?

I need to be able to use this call:
h = x.inject({}) {|a, b| a[b.one] = b.two; a}
Where x is a sequence of Couple objects (these just contain two number fields, one and two).
I am not sure how to implement the inject method in Couple.
Define an #each method in Couple, then include Enumerable in it.
class Couple
def each
yield "a"
yield "b"
end
include Enumerable
end
couple = Couple.new
couple.inject("") { |str, obj| str + obj }
# => "ab"
http://www.ruby-doc.org/core-1.9.3/Enumerable.html

Using injection in Ruby? [duplicate]

This question already has an answer here:
How to implement injection in Ruby?
(1 answer)
Closed 8 years ago.
I have a class called Hsh which basically simulates a hash. It has an array of Couple objects (which hold fields named one and two, one is an int another is a string name of that int).
I am supposed to be able to accept the following call:
h = x.inject({}) {|a, b| a[b.one] = b.two; a}
Where x is the Hsh object.
I am not sure how to implement the inject method within Hsh? Like, what would I write in:
def inject ????
??
??
end
All it's supposed to do is create a hash map.
you shouldn't really need to implement it, just implement Hsh#eachand include Enumerable, you'll get inject for free.
For your specific example something like this should work:
def inject accumulator
#I assume Hsh has some way to iterate over the elements
each do |elem|
accumulator = yield accumulator, elem
end
accumulator
end
But the real implementation of inject is a bit different (e.g. works without providing an accumulator, takes a symbol instead of a block etc)
require 'ostruct'
class Hsh
include Enumerable
def initialize
#arr = (0..9).map{ |i| OpenStruct.new(:one => i, :two => "#{i}")}
end
def each(&block)
#arr.each(&block)
end
end
p Hsh.new.inject({}) {|a, b| a[b.one] = b.two; a}
#=> {5=>"5", 0=>"0", 6=>"6", 1=>"1", 7=>"7", 2=>"2", 8=>"8", 3=>"3", 9=>"9", 4=>"4"}
In this particular case Hsh is actually an array, so unless you use it for something else such a complex code doesn't make sense, it can be done much easier:
p (0..9).map{ |i| OpenStruct.new(:one => i, :two => "#{i}")} \
.inject({}) {|a, b| a[b.one] = b.two; a}
#=> {5=>"5", 0=>"0", 6=>"6", 1=>"1", 7=>"7", 2=>"2", 8=>"8", 3=>"3", 9=>"9", 4=>"4"}
I have used OpenStruct instead of classes. See if this works for you
require 'ostruct'
class Hsh
attr_accessor :arr
def initialize
obj = OpenStruct.new
obj.one = 1
obj.two = "two"
#arr = [obj]
end
def inject(hash)
#arr.each do |arr|
yield hash, arr
end
hash
end
end
x = Hsh.new
p x.inject({}) {|a, b| a[b.one] = b.two} #prints {1 => "two"}

In Ruby, how can I get instance variables in a hash instead of an array?

I have a Ruby class. I want to get an instance variable from an argument to a method in that class. I can do get all of the instance variables as an array:
self.instance_variables
However, I want to get the instance variable named arg, specifically:
class MyClass
def get_instance_variable(arg)
hash_of_instance_variables[arg]
end
end
object.get_instance_variable('my_instance_var')
How do I compute hash_of_instance_variables?
To create a hash of all instance variables you can use the following code:
class Object
def instance_variables_hash
Hash[instance_variables.map { |name| [name, instance_variable_get(name)] } ]
end
end
But as cam mentioned in his comment, you should use instance_variable_get method instead:
object.instance_variable_get :#my_instance_var
Question is quite old but found rails solution for this: instance_values
This is first answer in google so maybe it will help someone.
class MyClass
def variables_to_hash
h = {}
instance_variables.each{|a|
s = a.to_s
n = s[1..s.size]
v = instance_variable_get a
h[n] = v
}
h
end
end
For Ruby 2.6+, you can pass a block to the to_h method, leading to very DRY syntax:
# Some instance variables
instance_variable_set(:#a, 'dog')
#=> "dog"
instance_variable_set(:#b, 'cat')
#=> "cat"
# Array of instance variable names
instance_variables
#=> [:#a, :#b]
# Hash of instance variable names and values, including leading #
instance_variables.to_h { |k| [k, instance_variable_get(k)] }
#=> { :#a => "dog", :#b => "cat" }
# Hash of instance variable names and values, excluding leading #
instance_variables.to_h { |k| [k[1..-1].to_sym, instance_variable_get(k)] }
#=> { :a => "dog", :b => "cat" }
Ruby on Rails has a couple of built-in ways to do this that you might find meet your needs.
user = User.new(first: 'brian', last: 'case')
attributes (Rails API: ActiveModel::AttributeMethods)
user.attributes
{"first"=>"brian", "last"=>"case"}
serializable_hash (Rails API: Active Model Serialization)
user.serializeable_hash
{"first"=>"brian", "last"=>"case"}

Uniq by object attribute in Ruby

What's the most elegant way to select out objects in an array that are unique with respect to one or more attributes?
These objects are stored in ActiveRecord so using AR's methods would be fine too.
Use Array#uniq with a block:
#photos = #photos.uniq { |p| p.album_id }
Add the uniq_by method to Array in your project. It works by analogy with sort_by. So uniq_by is to uniq as sort_by is to sort. Usage:
uniq_array = my_array.uniq_by {|obj| obj.id}
The implementation:
class Array
def uniq_by(&blk)
transforms = []
self.select do |el|
should_keep = !transforms.include?(t=blk[el])
transforms << t
should_keep
end
end
end
Note that it returns a new array rather than modifying your current one in place. We haven't written a uniq_by! method but it should be easy enough if you wanted to.
EDIT: Tribalvibes points out that that implementation is O(n^2). Better would be something like (untested)...
class Array
def uniq_by(&blk)
transforms = {}
select do |el|
t = blk[el]
should_keep = !transforms[t]
transforms[t] = true
should_keep
end
end
end
Do it on the database level:
YourModel.find(:all, :group => "status")
You can use this trick to select unique by several attributes elements from array:
#photos = #photos.uniq { |p| [p.album_id, p.author_id] }
I had originally suggested using the select method on Array. To wit:
[1, 2, 3, 4, 5, 6, 7].select{|e| e%2 == 0}
gives us [2,4,6] back.
But if you want the first such object, use detect.
[1, 2, 3, 4, 5, 6, 7].detect{|e| e>3} gives us 4.
I'm not sure what you're going for here, though.
I like jmah's use of a Hash to enforce uniqueness. Here's a couple more ways to skin that cat:
objs.inject({}) {|h,e| h[e.attr]=e; h}.values
That's a nice 1-liner, but I suspect this might be a little faster:
h = {}
objs.each {|e| h[e.attr]=e}
h.values
Use Array#uniq with a block:
objects.uniq {|obj| obj.attribute}
Or a more concise approach:
objects.uniq(&:attribute)
The most elegant way I have found is a spin-off using Array#uniq with a block
enumerable_collection.uniq(&:property)
…it reads better too!
If I understand your question correctly, I've tackled this problem using the quasi-hacky approach of comparing the Marshaled objects to determine if any attributes vary. The inject at the end of the following code would be an example:
class Foo
attr_accessor :foo, :bar, :baz
def initialize(foo,bar,baz)
#foo = foo
#bar = bar
#baz = baz
end
end
objs = [Foo.new(1,2,3),Foo.new(1,2,3),Foo.new(2,3,4)]
# find objects that are uniq with respect to attributes
objs.inject([]) do |uniqs,obj|
if uniqs.all? { |e| Marshal.dump(e) != Marshal.dump(obj) }
uniqs << obj
end
uniqs
end
You can use a hash, which contains only one value for each key:
Hash[*recs.map{|ar| [ar[attr],ar]}.flatten].values
Rails also has a #uniq_by method.
Reference: Parameterized Array#uniq (i.e., uniq_by)
I like jmah and Head's answers. But do they preserve array order? They might in later versions of ruby since there have been some hash insertion-order-preserving requirements written into the language specification, but here's a similar solution that I like to use that preserves order regardless.
h = Set.new
objs.select{|el| h.add?(el.attr)}
ActiveSupport implementation:
def uniq_by
hash, array = {}, []
each { |i| hash[yield(i)] ||= (array << i) }
array
end
Now if you can sort on the attribute values this can be done:
class A
attr_accessor :val
def initialize(v); self.val = v; end
end
objs = [1,2,6,3,7,7,8,2,8].map{|i| A.new(i)}
objs.sort_by{|a| a.val}.inject([]) do |uniqs, a|
uniqs << a if uniqs.empty? || a.val != uniqs.last.val
uniqs
end
That's for a 1-attribute unique, but the same thing can be done w/ lexicographical sort ...
If you are not married with arrays, we can also try eliminating duplicates through sets
set = Set.new
set << obj1
set << obj2
set.inspect
Note that in case of custom objects, we need to override eql? and hash methods

Resources