Better way of writing multiple methods with similar definition ruby - ruby

How could I improve this code so there is no duplicate code and it would allow me to add new similar methods dynamically?
def fabric_ids=(property_name)
#fabric_ids = [] if #fabric_ids.blank?
#fabric_ids << $property_values[property_name]
end
def work_ids=(property_name)
#work_ids = [] if #work_ids.blank?
#work_ids << $property_values[property_name]
end
def type_ids=(property_name)
#type_ids = [] if #type_ids.blank?
#type_ids << $property_values[property_name]
end

You can define your methods dynamically, using Module#define_method, like this:
%w(fabric_ids work_ids type_ids).each do |name|
define_method("#{name}=") do |property_name|
instance_variable_set(name, []) if instance_variable_get(name).blank?
instance_variable_get(name) << $property_values[property_name]
end
end

You might want to consider organizing this as hash. Here's one way to do that.
Code
ITEMS = [:fabric, :work, :kind]
class MyClass
attr_accessor :ids
def initialize
#ids = Hash.new { |h,k| h[k] = [] }
end
ITEMS.each do |item|
define_method("#{item.to_s}_properties") { properties_by_key(item) }
define_method("add_#{item.to_s}_properties") { |*property_names|
add_properties_by_key(item, *property_names) }
end
private
def add_properties_by_key(key, *property_names)
property_names.each { |name| self.ids[key] << $property_values[name] }
end
def properties_by_key(key)
self.ids[key]
end
end
p MyClass.instance_methods(false)
# [:ids, :ids=,
# :fabric_properties, :add_fabric_properties,
# :work_properties, :add_work_properties,
# :kind_properties, :add_kind_properties]
Example
$property_values = { color: "blue", weight: "heavy", cost: "average" }
my_class = MyClass.new
my_class.add_fabric_properties(:color, :weight, :cost)
my_class.add_work_properties(:weight, :cost)
my_class.add_kind_properties(:color)
p my_class.fabric_properties #=> ["blue", "heavy", "average"]
p my_class.work_properties #=> ["heavy", "average"]
p my_class.kind_properties #=> ["blue"]
my_class.add_kind_properties(:cost)
p my_class.kind_properties #=> ["blue", "average"]

Related

Ruby: Convert nested hash to object?

I am trying to convert a hash that includes nested hashes to object, such that attributes (including nested attributes) can be accessed using dot syntax.
So far first hash object is converted successfully by this code:
class Hashit
def initialize(hash)
hash.each do |k,v|
self.instance_variable_set("##{k}", v)
self.class.send(:define_method, k, proc{self.instance_variable_get("##{k}")})
self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("##{k}", v)})
end
end
end
The problem is, this approach doesn't work for nested hashes:
h = Hashit.new({a: '123r', b: {c: 'sdvs'}})
=> #<Hashit:0x00000006516c78 #a="123r", #b={:c=>"sdvs"}>
Note that in the output, #b={:c=>"sdvs"} wasn't converted; it's still a hash.
How can I convert a nested hash to an object?
You can use OpenStruct
http://ruby-doc.org/stdlib-2.0.0/libdoc/ostruct/rdoc/OpenStruct.html
user = OpenStruct.new({name: "Jimmy Cool", age: "25"})
user.name #Jimmy Cool
user.age #25
Another way is to use JSON and OpenStruct, which are standard ruby libs:
irb:
> require 'JSON'
=> true
> r = JSON.parse({a: { b: { c: 1 }}}.to_json, object_class: OpenStruct)
=> #<OpenStruct a=#<OpenStruct b=#<OpenStruct c=1>>>
> r.a.b.c
=> 1
You need to add recursivity:
class Hashit
def initialize(hash)
hash.each do |k,v|
self.instance_variable_set("##{k}", v.is_a?(Hash) ? Hashit.new(v) : v)
self.class.send(:define_method, k, proc{self.instance_variable_get("##{k}")})
self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("##{k}", v)})
end
end
end
h = Hashit.new({a: '123r', b: {c: 'sdvs'}})
# => #<Hashit:0x007fa6029f4f70 #a="123r", #b=#<Hashit:0x007fa6029f4d18 #c="sdvs">>
Ruby has an inbuilt data structure OpenStruct to solve something like this. Still, there is a problem. It is not recursive. So, you can extend OpenStruct class like this:
# Keep this in lib/open_struct.rb
class OpenStruct
def initialize(hash = nil)
#table = {}
if hash
hash.each_pair do |k, v|
k = k.to_sym
#table[k] = v.is_a?(Hash) ? OpenStruct.new(v) : v
end
end
end
def method_missing(mid, *args) # :nodoc:
len = args.length
if mname = mid[/.*(?==\z)/m]
if len != 1
raise ArgumentError, "wrong number of arguments (#{len} for 1)", caller(1)
end
modifiable?[new_ostruct_member!(mname)] = args[0].is_a?(Hash) ? OpenStruct.new(args[0]) : args[0]
elsif len == 0 # and /\A[a-z_]\w*\z/ =~ mid #
if #table.key?(mid)
new_ostruct_member!(mid) unless frozen?
#table[mid]
end
else
begin
super
rescue NoMethodError => err
err.backtrace.shift
raise
end
end
end
end
and remember to require 'open_struct.rb' next time you want to use OpenStruct.
Now you can do something like this:
person = OpenStruct.new
person.name = "John Smith"
person.age = 70
person.more_info = {interests: ['singing', 'dancing'], tech_skills: ['Ruby', 'C++']}
puts person.more_info.interests
puts person.more_info.tech_skills
You could check the type on v when you initialize the object and call new to get a new Hashit when it is a another hash.
class Hashit
def initialize(hash)
hash.each do |k,v|
self.instance_variable_set("##{k}", v.is_a?(Hash) ? Hashit.new(v) : v)
self.class.send(:define_method, k, proc{self.instance_variable_get("##{k}")})
self.class.send(:define_method, "#{k}=", proc{|v| self.instance_variable_set("##{k}", v)})
end
end
end
and the resulting snippet from before would be:
h = Hashit.new({a: '123r', b: {c: 'sdvs'}})
=> #<Hashit:0x007fa71421a850 #a="123r", #b=#<Hashit:0x007fa71421a5a8 #c="sdvs">>
If I understand the question correctly, this should do it:
class Hashit
def initialize(hash)
convert_to_obj(hash)
end
private
def convert_to_obj(h)
h.each do |k,v|
self.class.send(:attr_accessor, k)
instance_variable_set("##{k}", v)
convert_to_obj(v) if v.is_a? Hash
end
end
end
h = Hashit.new( { a: '123r',
b: { c: 'sdvs', d: { e: { f: 'cat' }, g: {h: 'dog'} } } })
#=> #<Hashit:0x000001018eee58 #a="123r",
# #b={:c=>"sdvs", :d=>{:e=>{:f=>"cat"}, :g=>{:h=>"dog"}}},
# #c="sdvs", #d={:e=>{:f=>"cat"}, :g=>{:h=>"dog"}},
# #e={:f=>"cat"}, #f="cat", #g={:h=>"dog"}, #h="dog">
h.instance_variables
#=> [:#a, :#b, :#c, :#d, :#e, :#f, :#g, :#h]
Hashit.instance_methods(false)
#=> [:a, :a=, :b, :b=, :c, :c=, :d, :d=, :e, :e=, :f, :f=, :g, :g=, :h, :h=]
h.d
#=> {:e=>{:f=>"cat"}}
h.d = "cat"
h.d
#=> "cat"

How to define a method to_proc in Array so I can use it in a particular situations

Lets say that I have the class:
class Car
attr_accessor :brand, :color
def initialize(br, col)
#brand = br
#color = col
end
end
first_car = Car.new 'Mercedes', 'Yellow'
second_car = Car.new 'Jaguar', 'Orange'
third_car = Car.new 'Bentley', 'Pink'
array_of_cars = [first_car, second_car]
So, the idea is to define a method to_proc in the class Array so this can happen:
array_of_cars.map(&[:brand, :color]) #=> [['Mercedes', 'Yellow'], ['Jaguar', 'Orange'], ['Bentley', 'Pink']]
or how could I somehow do something like this:
[:brand, :color].to_proc.call(first_car) #=> ['Mercedes', 'Yellow']
The standard Symbol#to_proc is, more or less, a e.send(self) call and you just want to send an array full of symbols to each element so just say exactly that with something like this:
class Array
def to_proc
proc { |e| self.map { |m| e.send(m) } }
end
end
I'd probably not patch Array for this, I'd just use a local lambda if I wanted something easier to read:
brand_and_color = lambda { |e| [:brand, :color].map { |s| e.send(s) } }
array_of_cars.map(&brand_and_color)
class Array
def to_proc(arr)
self.map do |elem|
arr.map { |attr| elem.send(attr) }
end
end
end
Like mu is too short mentioned, I wouldn't patch Array like this.
But, this will get the work done for you.
Invoke it as such:
array_of_cars.to_proc([:brand, :color])
Maybe use a normal method?
module ConvertInstanceToHash
def to_proc(obj, keys)
hash = {}
obj.instance_variables.each {|var| hash[var.to_s.delete("#")] = obj.instance_variable_get(var)}
hash = hash.inject({}){|memo,(k,v)| memo[k.to_sym] = v; memo}
hash.values_at(*keys).compact
end
end
irb(main):009:0> first_car = Car.new 'Mercedes', 'Yellow'
=> #<Car:0x000000045fb870 #brand="Mercedes", #color="Yellow">
irb(main):010:0> to_proc(first_car, [:brand, :color])
=> ["Mercedes", "Yellow"]

Ruby.Metaprogramming. class_eval

There seem to be a mistake in my code. However I just can't find it out.
class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s
attr_reader attr_name
attr_writer attr_name
attr_reader attr_name + "_history"
class_eval %Q{
##{attr_name}_history=[1,2,3]
}
end
end
class Foo
attr_accessor_with_history :bar
end
f = Foo.new
f.bar = 1
f.bar = 2
puts f.bar_history.to_s
I would expect it to return an array [1,2,3]. However, it doesn't return anything.
You shouldn't be opening Class to add new methods. That's what modules are for.
module History
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s
attr_accessor attr_name
class_eval %Q{
def #{attr_name}_history
[1, 2, 3]
end
}
end
end
class Foo
extend History
attr_accessor_with_history :bar
end
f = Foo.new
f.bar = 1
f.bar = 2
puts f.bar_history.inspect
# [1, 2, 3]
And here's the code you probably meant to write (judging from the names).
module History
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s
class_eval %Q{
def #{attr_name}
##{attr_name}
end
def #{attr_name}= val
##{attr_name}_history ||= []
##{attr_name}_history << #{attr_name}
##{attr_name} = val
end
def #{attr_name}_history
##{attr_name}_history
end
}
end
end
class Foo
extend History
attr_accessor_with_history :bar
end
f = Foo.new
f.bar = 1
f.bar = 2
puts f.bar_history.inspect
# [nil, 1]
Solution:
class Class
def attr_accessor_with_history(attr_name)
ivar = "##{attr_name}"
history_meth = "#{attr_name}_history"
history_ivar = "##{history_meth}"
define_method(attr_name) { instance_variable_get ivar }
define_method "#{attr_name}=" do |value|
instance_variable_set ivar, value
instance_variable_set history_ivar, send(history_meth) << value
end
define_method history_meth do
value = instance_variable_get(history_ivar) || []
value.dup
end
end
end
Tests:
describe 'Class#attr_accessor_with_history' do
let(:klass) { Class.new { attr_accessor_with_history :bar } }
let(:instance) { instance = klass.new }
it 'acs as attr_accessor' do
instance.bar.should be_nil
instance.bar = 1
instance.bar.should == 1
instance.bar = 2
instance.bar.should == 2
end
it 'remembers history of setting' do
instance.bar_history.should == []
instance.bar = 1
instance.bar_history.should == [1]
instance.bar = 2
instance.bar_history.should == [1, 2]
end
it 'is not affected by mutating the history array' do
instance.bar_history << 1
instance.bar_history.should == []
instance.bar = 1
instance.bar_history << 2
instance.bar_history.should == [1]
end
end
You will find a solution for your problem in Sergios answer. Here an explanation, what's going wrong in your code.
With
class_eval %Q{
##{attr_name}_history=[1,2,3]
}
you execute
#bar_history = [1,2,3]
You execute this on class level, not in object level.
The variable #bar_history is not available in a Foo-object, but in the Foo-class.
With
puts f.bar_history.to_s
you access the -never on object level defined- attribute #bar_history.
When you define a reader on class level, you have access to your variable:
class << Foo
attr_reader :bar_history
end
p Foo.bar_history #-> [1, 2, 3]
#Sergio Tulentsev's answer works, but it promotes a problematic practice of using string eval which is in general fraught with security risks and other surprises when the inputs aren't what you expect. For example, what happens to Sergio's version if one calls (no don't try it):
attr_accessor_with_history %q{foo; end; system "rm -rf /"; def foo}
It is often possible to do ruby meta-programming more carefully without string eval. In this case, using simple interpolation and define_method of closures with instance_variable_[get|set], and send:
module History
def attr_accessor_with_history(attr_name)
getter_sym = :"#{attr_name}"
setter_sym = :"#{attr_name}="
history_sym = :"#{attr_name}_history"
iv_sym = :"##{attr_name}"
iv_hist = :"##{attr_name}_history"
define_method getter_sym do
instance_variable_get(iv_sym)
end
define_method setter_sym do |val|
instance_variable_set( iv_hist, [] ) unless send(history_sym)
send(history_sym).send( :'<<', send(getter_sym) )
instance_variable_set( iv_sym, val #)
end
define_method history_sym do
instance_variable_get(iv_hist)
end
end
end
Here is what should be done. The attr_writer need be defined withing class_eval instead in Class.
class Class
def attr_accessor_with_history(attr_name)
attr_name = attr_name.to_s
attr_reader attr_name
#attr_writer attr_name ## moved into class_eval
attr_reader attr_name + "_history"
class_eval %Q{
def #{attr_name}=(value)
##{attr_name}_history=[1,2,3]
end
}
end
end

thread scope in Ruby

Is there a way to scope variables to the thread without having to pass everything around, given a class with the following methods:
def initialize
#server = TCPServer.new('localhost',456)
end
def start
threads = []
while (upload = #server.accept)
threads << Thread.new(upload) do |connection|
some_method_here(connection)
end
end
threads.each {|t| t.join }
end
def some_method_here(connection)
variable = "abc"
another_method(connection,variable)
end
def another_method(connection,variable)
puts variable.inspect
connection.close
end
if I get you right you want to use thread local variables (see the ruby rdoc for Thread#[])
From the rdoc:
a = Thread.new { Thread.current["name"] = "A"; Thread.stop }
b = Thread.new { Thread.current[:name] = "B"; Thread.stop }
c = Thread.new { Thread.current["name"] = "C"; Thread.stop }
Thread.list.each {|x| puts "#{x.inspect}: #{x[:name]}" }
produces:
#<Thread:0x401b3b3c sleep>: C
#<Thread:0x401b3bc8 sleep>: B
#<Thread:0x401b3c68 sleep>: A
#<Thread:0x401bdf4c run>:
So your example would use
Thread.current[:variable] = "abc"
Thread.current[:variable] # => "abc"
wherever you were using just variable before
Thread.current[:variable_name] = ... ?
You can try an around_filter in ApplicationController
around_filter :apply_scope
def apply_scope
Document.where(:user_id => current_user.id).scoping do
yield
end

Instance Eval Within Block

I have a Builder class that lets you add to one of it's instance variables:
class Builder
def initialize
#lines = []
end
def lines
block_given? ? yield(self) : #lines
end
def add_line( text )
#lines << text
end
end
Now, how do I change this
my_builder = Builder.new
my_builder.lines { |b|
b.add_line "foo"
b.add_line "bar"
}
p my_builder.lines # => ["foo", "bar"]
Into this?
my_builder = Builder.new
my_builder.lines {
add_line "foo"
add_line "bar"
}
p my_builder.lines # => ["foo", "bar"]
class Builder
def initialize
#lines = []
end
def lines(&block)
block_given? ? instance_eval(&block) : #lines
end
def add_line( text )
#lines << text
end
end
my_builder = Builder.new
my_builder.lines {
add_line "foo"
add_line "bar"
}
p my_builder.lines # => ["foo", "bar"]
You can also use the method use in ruby best practice using the length of arguments with arity:
class Foo
attr_accessor :list
def initialize
#list=[]
end
def bar(&blk)
blk.arity>0 ? blk.call(self) : instance_eval(&blk)
end
end
x=Foo.new
x.bar do
list << 1
list << 2
list << 3
end
x.bar do |foo|
foo.list << 4
foo.list << 5
foo.list << 6
end
puts x.list.inspect

Resources