Having a hash, set object properties in Ruby - ruby-on-rails

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.

Related

Collecting hashes into OpenStruct creates "table" entry

Why this (evaluated in Rails console)
[{:a => :b}].collect {|x| OpenStruct.new(x)}.to_json
adds a "table" record in there?
"[{\"table\":{\"a\":\"b\"}}]
I want just this:
"[{\"a\":\"b\"}]
Does it mean that Rails' to_json method handles OpenStruct in a different way? When I try it in the irb, it's not there:
require 'ostruct'
[{:a => :b}].collect {|x| OpenStruct.new(x)}.inspect
Because #table is a instance variable of OpenStruct and Object#as_json returns Hash of instance variables.
In my project, I implemented OpenStruct#as_json to override the behaviour.
require "ostruct"
class OpenStruct
def as_json(options = nil)
#table.as_json(options)
end
end
Use marshal_dump, although this somewhat defeats the purpose of converting it to an OpenStruct beforehand:
[{:a => :b}].collect {|x| OpenStruct.new(x).marshal_dump }.to_json
=> "[{\"a\":\"b\"}]"
The shorter way would be:
[{:a => :b}].to_json
"[{\"a\":\"b\"}]"
Alternatively you could moneky patch OpenStruct#as_json as shown in hiroshi's answer:
require "ostruct"
class OpenStruct
def as_json(options = nil)
#table.as_json(options)
end
end
I get around the problem by subclassing OpenStruct like so:
class DataStruct < OpenStruct
def as_json(*args)
super.as_json['table']
end
end
then you can easily convert to JSON like so:
o = DataStruct.new(a:1, b:DataStruct.new(c:3))
o.to_json
# => "{\"a\":1,\"b\":{\"c\":3}}"
Neat huh? So in answer to your question, you'd write this instead:
[{:a => :b}].collect {|x| DataStruct.new(x)}.to_json
giving you:
=> "[{\"a\":\"b\"}]"
UPDATE FOR RUBY 2.7 (Feb 5, 2021)
require 'json'
require 'ostruct'
class OpenStruct
def to_json
to_hash.to_json
end
def to_hash
to_h.map { |k, v|
v.respond_to?(:to_hash) ? [k, v.to_hash] : [k, v]
}.to_h
end
end
o = OpenStruct.new(a:1, b:OpenStruct.new(c:3))
p o.to_json
I found the other responses to be a tad confusing having landed here to just figure out how to turn my OpenStruct into a Hash or JSON. To clarify, you can just call marshal_dump on your OpenStruct.
$ OpenStruct.new(hello: :world).to_json
=> "{\"table\":{\"hello\":\"world\"}}"
$ OpenStruct.new(hello: :world).marshal_dump
=> {:hello=>:world}
$ OpenStruct.new(hello: :world).marshal_dump.to_json
=> "{\"hello\":\"world\"}"
I personally would be hesitant to monkey-patch OpenStruct unless you're doing it on a subclass, as it may have unintended consequences.
With ruby 2.1.2 you can use the following to get JSON without the table root element:
[{:a => :b}].collect {|x| OpenStruct.new(x).to_h}.to_json
=> "[{\"a\":\"b\"}]"
openstruct_array.map(&:to_h).as_json
The issue here is that internally it's doing a as_json which creates a Hash with the table key (because as_json serializes all of the instance variables of the object too and #table is an instance var of OpenStruct) and then it's doing a to_json on that which stringifies it.
So, the easiest way is to first just use to_h (which doesn't serialize the instance variables) and then to_json on that. So:
OpenStruct.new(x).to_h.json or in your case open_struct_array.map(&:to_h).to_json

Adding data of an unknown type to a hash

Ruby seems like a language that would be especially well suited to solving this problem, but I'm not finding an elegant way to do it. What I want is a method that will accept a value and add it to a hash like so, with specific requirements for how it is added if the key already exists:
Adding 'foo' to :key1
{:key1 => 'foo'}
Adding 'bar' to :key1
{:key1=> 'foobar'}
Adding ['foo'] to :key2
{:key2 = ['foo']}
Adding ['bar'] to :key2
{:key2 => [['foo'], ['bar']]
Adding {:k1 => 'foo'} to :key3
{:key3 => {:k1 => 'foo'}}
Adding {:k2 => 'bar'} to :key3
{:key3 => {:k1 => 'foo', :k2 => 'bar'}}
Right now I can do this but it looks sloppy and not like idiomatic Ruby. What is a good way to do this?
To make it more Ruby-like you might want to extend the Hash class to provide this kind of functionality across the board, or make your own subclass for this specific purpose. For instance:
class FancyHash < Hash
def add(key, value)
case (self[key])
when nil
self[key] = value
when Array
self[key] = [ self[key], value ]
when Hash
self[key].merge!(value)
else
raise "Adding value to unsupported #{self[key].class} structure"
end
end
end
This will depend on your exact interpretation of what "adding" means, as your examples do seem somewhat simplistic and don't address what happens when you add a hash to a pre-existing array, among other things.
The idea is that you define a handler that accommodates as many possibilities as reasonable and throw an exception if you can't manage.
If you want to utilize the polymorphic feature of oop, you might want to do:
class Object; def add_value v; v end end
class String; def add_value v; self+v end end # or concat(v) for destructive
class Array; def add_value v; [self, v] end end # or replace([self.dup, v]) for destructive
class Hash; def add_value v; merge(v) end end # or merge!(v) for destructive
class Hash
def add k, v; self[k] = self[k].add_value(v) end
end

What's the difference between "=" & "=>" and "#variable", "##variable" and ":variable" in ruby?

I know these are the basics of rails but i still don't know the full difference between = sign and => and the difference between #some_variable, ##some_variable and :some_variable in rails.
Thanks.
OK.
The difference between the = and the => operators is that the first is assignment, the second represents an association in a hash (associative array). So { :key => 'val' } is saying "create an associative array, with :key being the key, and 'val' being the value". If you want to sound like a Rubyist, we call this the "hashrocket". (Believe it or not, this isn't the most strange operator in Ruby; we also have the <=>, or "spaceship operator".)
You may be confused because there is a bit of a shortcut you can use in methods, if the last parameter is a hash, you can omit the squiggly brackets ({}). so calling render :partial => 'foo' is basically calling the render method, passing in a hash with a single key/value pair. Because of this, you often see a hash as the last parameter to sort of have a poor man's optional parameters (you see something similar done in JavaScript too).
In Ruby, any normal word is a local variable. So foo inside a method is a variable scoped to the method level. Prefixing a variable with # means scope the variable to the instance. So #foo in a method is an instance level.
## means a class variable, meaning that ## variables are in scope of the class, and all instances.
: means symbol. A symbol in Ruby is a special kind of string that implies that it will be used as a key. If you are coming from C#/Java, they are similar in use to the key part of an enum. There are some other differences too, but basically any time you are going to treat a string as any sort of key, you use a symbol instead.
Wow, a that's a lot of different concepts together.
1) = is plain old assignment.
a = 4;
puts a
2) => is used to declare hashes
hash = {'a' => 1, 'b' => 2, 'c' => 3}
puts hash['b'] # prints 2
3) #var lets you access object instance variable.
class MyObject
def set_x(x)
#x = x
end
def get_x
#x
end
end
o = MyObject.new
o.set_x 3
puts o.get_x # prints 3
4) ##var lets you access class ('static') variables.
class MyObject
def set_x(x)
##x = x # now you can access '##x' from other MyObject instance
end
def get_x
##x
end
end
o1 = MyObject.new
o1.set_x 3
o2 = MyObject.new
puts o2.get_x # prints 3, even though 'set_x' was invoked on different object
5) I usually think of :var as special 'label' class. Example 2 can be rephrased like this
hash = {:a => 1, :b => 2, :c => 3}
puts hash[:b] # prints 2

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