Ruby library like Hashie that can use lambdas as properties - ruby

Is there a Ruby library that works like Hashie except it can take a lambda as a property and call it when that property is accessed?
For example, I'd like something like this:
# Lash = Lambda-able hash
lash = Lash.new(
someProperty: "Some value",
someOtherProperty: ->{ Time.now }
)
lash.someProperty # => "Some value"
lash.someOtherProperty # => 2013-01-25 16:36:45 -0500
lash.someOtherProperty # => 2013-01-25 16:36:46 -0500

Here's my implementation:
class Lash < BasicObject
def self.new hash
::Class.new do
hash.each do |key, value|
method_body = if value.respond_to? :call
->(*args){ self.instance_exec(*args, &value) }
else
->{ value }
end
define_method(key, &method_body)
end
end.new
end
end

I wanted something similar a few days ago and ended up using Hashie 2.0.0.beta which gives you extensions that you can use with your own subclasses of Hash:
require 'hashie'
require 'hashie/hash_extensions'
class Lash < Hash
include Hashie::Extensions::MethodAccess
def [](key)
val = super(key)
if val.respond_to?(:call) and val.arity.zero?
val.call
else
val
end
end
end
This lets you do things like:
l = Lash.new
#=> {}
l.foo = 123
#=> 123
l.bar = ->{ Time.now }
#=> #<Proc:0x007ffab3915f18#(irb):58 (lambda)>
l.baz = ->(x){ 10 * x }
#=> #<Proc:0x007ffab38fb4d8#(irb):59 (lambda)>
l.foo
#=> 123
l.bar
#=> 2013-01-26 15:36:50 +0100
l.baz
#=> #<Proc:0x007ffab38fb4d8#(irb):59 (lambda)>
l.baz[5]
#=> 50
Note: this only works in Hashie 2.0.0.beta which you can install via Bundler by adding this line to your Gemfile:
gem 'hashie', :git => 'git://github.com/intridea/hashie.git'
or, without Bundler, using the specific_install gem:
gem install specific_install
gem specific_install -l git://github.com/intridea/hashie.git

Related

Ruby Script: undefined method `symbolize_keys' error loading YAML files

I have a ruby script for yaml merging as follows
#!/usr/bin/env ruby
require 'yaml'
raise "wrong number of parameters" unless ARGV.length == 2
y1 = YAML.load_file(ARGV[0]).symbolize_keys
y2 = YAML.load_file(ARGV[1]).symbolize_keys
puts y1.merge!(y2).to_yaml
when I execute it:
./test.rb ./src/api/config/config1.yml ./src/api/config/config2.yml
I've got the following error:
./test.rb:5:in `<main>': undefined method `symbolize_keys' for {"root"=>{"cloud.n2"=>{"accessKey"=>"I5VAJUYNR4AAKIZDH777"}}}:Hash (NoMethodError)
Hash#symbolize_keys method comes from activesupport gem (activesupport/lib/active_support/core_ext/hash/keys.rb).
In order to use it, you need to add the following line to your script:
require "active_support"
While the other answers/comments are correct it seems like overkill to require all of ActiveSupport for this. Instead either use:
require 'active_support/core_ext/hash/keys'
Or if you have control over the yml files then just make the keys symbols there and avoid any transformation. For Example
require 'yaml'
yml = <<YML
:root:
:cloud.n2:
:accessKey: "I5VAJUYNR4AAKIZDH777"
YML
YAML.load(yml)
#=> {:root=>{:"cloud.n2"=>{:accessKey=>"I5VAJUYNR4AAKIZDH777"}}}
This does not really the answer your question, but Ruby 2.5.0 introduced Hash#transform_keys (release notes) which also can be used to symbolize keys and is in core Ruby.
{'a' => 1, 'b' => 2}.transform_keys(&:to_sym)
#=> {:a=>1, :b=>2}
There is also a bang version which mutates the hash instead of creating a new one.
As other have already noted, symbolize_keys is an ActiveSupport method. If you are not using ActiveSupport, and/or on a pre-2.5 version of Ruby that does not include transform_keys, you could define it yourself.
class Hash
def transform_keys
return enum_for(:transform_keys) unless block_given?
result = self.class.new
each_key do |key|
result[yield(key)] = self[key]
end
result
end
def transform_keys!
return enum_for(:transform_keys!) unless block_given?
keys.each do |key|
self[yield(key)] = delete(key)
end
self
end
def symbolize_keys
transform_keys{ |key| key.to_sym rescue key }
end
def symbolize_keys!
transform_keys!{ |key| key.to_sym rescue key }
end
end
This is not to say that there are not likely other dependencies on Rails or ActiveSupport that will be required for your script.

Calling a module function in a ruby module

I want to call a module function to define a constant in a utility module in ruby. However, when I try this I get an error message. Here comes the code and the error:
module M
ABC = fun
module_function
def self.fun
"works"
end
end
Error message:
NameError: undefined local variable or method `fun' for M:Module
Any ideas? I also tried it without self and with M.fun but no success...
It is just that the method is not defined when you assign fun to ABC. Just change the order:
module M
def self.fun
"works"
end
ABC = fun
end
M::ABC
#=> "works"
If you dislike the order (constants below methods), you might want to consider to have the method itself to memorize its return value. A common pattern looks like:
module M
def self.fun
#cached_fun ||= begin
sleep 4 # complex calculation
Time.now # return value
end
end
end
M.fun
# returns after 4 seconds => 2017-03-03 23:48:57 +0100
M.fun
# returns immediately => 2017-03-03 23:48:57 +0100
Test this in you irb console:
$ irb
2.3.3 :001 > module M
2.3.3 :002?> def self.fun
2.3.3 :003?> "worked"
2.3.3 :004?> end
2.3.3 :005?>
2.3.3 :006 > ABC = fun
2.3.3 :007?> end
=> "worked"
2.3.3 :008 > M
=> M
2.3.3 :009 > M::ABC
=> "worked"
2.3.3 :010 >
The fact is that now you defined self.fun before using it.
In your code you used the method before defining it.

pry inspect method not working

I have the following code from understanding computation book. The intention is to change the inspect behavior.
class Number < Struct.new(:value)
def inspect
"<<#{self}>>"
end
def to_s
value.to_s
end
end
It works as expected when I use irb:
irb(main):014:0> Number.new(1)
=> <<1>>
but it does not when I use pry:
[8] pry(main)> n = Number.new(1)
=> #<struct Number value=1>
The Pry is version 0.10.3 on Ruby 2.0.0. Why does it not work?
Pry doesn't just use inspect to display the return value. It calls a proc called print object that is defined in configuration. In lib/pry.rb, you can find that it is set to:
class Pry
# The default print
DEFAULT_PRINT = proc do |output, value, _pry_|
_pry_.pager.open do |pager|
pager.print _pry_.config.output_prefix
Pry::ColorPrinter.pp(value, pager, Pry::Terminal.width! - 1)
end
end
end
In order to use inspect as in irb, set it like this as instructed here:
Pry.config.print = proc {|output, value| output.puts "=> #{value.inspect}"}
Then you will get:
pry(main)> n = Number.new(1)
=> <<1>>
I use Pry version 0.10.4.
I've just added the following lines in my .pryrc file (I think, that is a good
place for such code):
if defined?(BigDecimal)
BigDecimal.class_eval do
def inspect
"<#{to_s('+3F')}>"
end
end
end
And result:
balance: <+100.0>,
commission_amount: <+0.15>
Sawa is right in that Pry uses its own printer, but if you look closer at the source you can see that it actually uses Ruby's PP behind the scenes, and PP defines its own behaviour for pretty printing Structs:
class Struct # :nodoc:
def pretty_print(q) # :nodoc:
q.group(1, sprintf("#<struct %s", PP.mcall(self, Kernel, :class).name), '>') {
q.seplist(PP.mcall(self, Struct, :members), lambda { q.text "," }) {|member|
q.breakable
q.text member.to_s
q.text '='
q.group(1) {
q.breakable ''
q.pp self[member]
}
}
}
end
def pretty_print_cycle(q) # :nodoc:
q.text sprintf("#<struct %s:...>", PP.mcall(self, Kernel, :class).name)
end
end
It's worth checking out the documentation (though it is only brief) if you are interested in learning more about this.
So in your struct you could also define your own pretty_print and pretty_print_cycle methods, which would mean Pry could print these how you want, without having to override their DEFAULT_PRINT proc.

Responding to one method but not to other in the same scope

With the following code
class User < ActiveRecord::Base
# ...
# code filtered from here
# ...
# both methods below are public methods
def humanized_roles
roles.collect {|e| e.name.humanize }.join(', ')
end
def role=( arg = nil)
self.roles = []
self.add_role arg.to_sym
end
end
This is happening
User.new.respond_to? :humanized_roles
# => false
User.new.respond_to? "role=".to_sym
# => true
rvm, ruby, rails versions
rvm 1.26.3
ruby 1.9.3p448 (2013-06-27 revision 41675) [x86_64-linux]
Rails 3.2.14
Am I missing something obvious?
In the interest of product release, I circumvented this issue and used a helper method to accept user object as parameter, instead of user.humanize_roles
Thank you everyone for your valuable time & responses.

Using a Ruby Module for Defaults

I'd like to use a Ruby Module to store a set of configuration defaults.
I'm having some problems using the values and hope someone could help.
This may not be the best way to do this but this is what I've come up with so far.
Here is a module to hold value for a persons Resume => resume.rb
module Resume
require 'ostruct'
attr_reader :personal, :education
#personal = OpenStruct.new
#education = Array.new
def self.included(base)
set_personal
set_education
end
def self.set_personal
#personal.name = "Joe Blogs"
#personal.email_address = 'joe.blogs#gmail.com'
#personal.phone_number = '5555 55 55 555'
end
def self.set_education
#education << %w{ School\ 1 Description\ 1 }
#education << %w{ School\ 2 Description\ 2 }
end
end
From irb it works fine:
% irb -I .
1.9.3-p194 :001 > require 'resume'
=> true
1.9.3-p194 :002 > include Resume
=> Object
1.9.3-p194 :003 > puts Resume.personal.name
Joe Blogs
=> nil
However when I include this into a class it throws and error => build.rb
require 'resume'
class Build
include Resume
def build
puts Resume.personal.name
end
end
From irb:
% irb -I .
1.9.3-p194 :001 > require 'build'
=> true
1.9.3-p194 :002 > b = Build.new
=> #<Build:0x00000001d0ebb0>
1.9.3-p194 :003 > b.build
NoMethodError: undefined method `personal' for Resume:Module
from /home/oolyme/projects/lh_resume_builder_ruby/build.rb:7:in `build'
from (irb):3
from /home/oolyme/.rvm/rubies/ruby-1.9.3-p194/bin/irb:16:in `<main>'
I've tried a few variations to output the include module variables in the Build class instance but all error out.
attr_accessor creates a couple of instance methods, meaning that they will be available on an instance of Build. But you clearly want class instance methods. Change definition of your module to this:
module Resume
require 'ostruct'
def self.personal
#personal
end
def self.education
#education
end
def self.included(base)
#personal = OpenStruct.new
#education = Array.new
set_personal
set_education
end
def self.set_personal
#personal.name = "Joe Blogs"
#personal.email_address = 'joe.blogs#gmail.com'
#personal.phone_number = '5555 55 55 555'
end
def self.set_education
#education << %w{ School\ 1 Description\ 1 }
#education << %w{ School\ 2 Description\ 2 }
end
end
And it'll work
b = Build.new
b.build # >> Joe Blogs

Resources