OpenStruct issue with Ruby 2.3.1 - ruby-on-rails

In Ruby 2.1.5 and 2.2.4, creating a new Collector returns the correct result.
require 'ostruct'
module ResourceResponses
class Collector < OpenStruct
def initialize
super
#table = Hash.new {|h,k| h[k] = Response.new }
end
end
class Response
attr_reader :publish_formats, :publish_block, :blocks, :block_order
def initialize
#publish_formats = []
#blocks = {}
#block_order = []
end
end
end
> Collector.new
=> #<ResourceResponses::Collector>
Collector.new.responses
=> #<ResourceResponses::Response:0x007fb3f409ae98 #block_order=[], #blocks= {}, #publish_formats=[]>
When I upgrade to Ruby 2.3.1, it starts returning back nil instead.
> Collector.new
=> #<ResourceResponses::Collector>
> Collector.new.responses
=> nil
I've done a lot of reading around how OpenStruct is now 10x faster in 2.3 but I'm not seeing what change was made that would break the relationship between Collector and Response. Any help is very appreciated. Rails is at version 4.2.7.1.

Let's have a look at the implementation of method_missing in the current implementation:
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]
elsif len == 0
if #table.key?(mid)
new_ostruct_member!(mid) unless frozen?
#table[mid]
end
else
err = NoMethodError.new "undefined method `#{mid}' for #{self}", mid, args
err.set_backtrace caller(1)
raise err
end
end
The interesting part is the block in the middle that runs when the method name didn't end with an = and when there are no addition arguments:
if #table.key?(mid)
new_ostruct_member!(mid) unless frozen?
#table[mid]
end
As you can see the implementation first checks if the key exists, before actually reading the value.
This breaks your implementation with the hash that returns a new Response.new when a key/value is not set. Because just calling key? doesn't trigger the setting of the default value:
hash = Hash.new { |h,k| h[k] = :bar }
hash.has_key?(:foo)
#=> false
hash
#=> {}
hash[:foo]
#=> :bar
hash
#=> { :foo => :bar }
Ruby 2.2 didn't have this optimization. It just returned #table[mid] without checking #table.key? first.

Related

Is a ':methods' option in 'to_json' substitutable with an ':only' option?

The to_json option has options :only and :methods. The former is intended to accept attributes and the latter methods.
I have a model that has an attribute foo, which is overwritten:
class SomeModel < ActiveRecord::Base
...
def foo
# Overrides the original attribute `foo`
"the overwritten foo value"
end
end
The overwritten foo method seems to be called irrespective of which option I write the foo under.
SomeModel.first.to_json(only: [:foo])
# => "{..., \"foo\":\"the overwritten foo value\", ...}"
SomeModel.first.to_json(methods: [:foo])
# => "{..., \"foo\":\"the overwritten foo value\", ...}"
This seems to suggest it does not matter whether I use :only or :methods.
Is this the case? I feel something wrong with my thinking.
The source code leads to these:
File activemodel/lib/active_model/serialization.rb, line 124
def serializable_hash(options = nil)
options ||= {}
attribute_names = attributes.keys
if only = options[:only]
attribute_names &= Array(only).map(&:to_s)
elsif except = options[:except]
attribute_names -= Array(except).map(&:to_s)
end
hash = {}
attribute_names.each { |n| hash[n] = read_attribute_for_serialization(n) }
Array(options[:methods]).each { |m| hash[m.to_s] = send(m) }
serializable_add_includes(options) do |association, records, opts|
hash[association.to_s] = if records.respond_to?(:to_ary)
records.to_ary.map { |a| a.serializable_hash(opts) }
else
records.serializable_hash(opts)
end
end
hash
end
File activeresource/lib/active_resource/base.rb, line 1394
def read_attribute_for_serialization(n)
attributes[n]
end
and it seems that an :only option calls attributes[n] and :methods option calls send(m). What is the difference?

How to trasform all values in a nested hash?

I want to convert all the values in a nested hash to a utf8 compatible string. I initially thought this would be easy and something like deep_apply should be available for me to use, but I am unable to find anything this simple on a quick google and SO search.
I do not want to write (maintain) a method similar to the lines of Change values in a nested hash . Is there a native API implementation or a shorthand available for this or do I have to write my own method?
I ended up implementing my own approach, that is in no way perfect but works well for my use case and should be easy to maintain. Posting it here for reference to anyone who wants to try it out
def deep_apply object, klasses, &blk
if object.is_a? Array
object.map { |obj_ele| deep_apply(obj_ele, klasses, &blk) }
elsif object.is_a? Hash
object.update(object) {|_, value| deep_apply(value, klasses, &blk) }
elsif klasses.any? { |klass| object.is_a? klass }
blk.call(object)
else
object
end
end
usage:
=> pry(main)> deep_apply({a: [1, 2, "sadsad"]}, [String, Integer]) { |v| v.to_s + "asd" }
=> {:a=>["1asd", "2asd", "sadsadasd"]}
Interesting to learn of the deep_merge approach taken in the answer by "The F". Here is another approach which requires adding a few helper methods.
First, the helper methods:
From the top answer here (converting-a-nested-hash-into-a-flat-hash):
def flat_hash(h,f=[],g={})
return g.update({ f=>h }) unless h.is_a? Hash
h.each { |k,r| flat_hash(r,f+[k],g) }
g
end
From a Github repo called ruby-bury (this functionality was proposed to Ruby core, but rejected)
class Hash
def bury *args
if args.count < 2
raise ArgumentError.new("2 or more arguments required")
elsif args.count == 2
self[args[0]] = args[1]
else
arg = args.shift
self[arg] = {} unless self[arg]
self[arg].bury(*args) unless args.empty?
end
self
end
end
And then a method tying it together:
def change_all_values(hash, &blk)
# the next line makes the method "pure functional"
# but can be removed otherwise.
hash = Marshal.load(Marshal.dump(hash))
flat_hash(hash).each { |k,v| hash.bury(*(k + [blk.call(v)])) }
hash
end
A usage example:
irb(main):063:0> a = {a: 1, b: { c: 1 } }
=> {:a=>1, :b=>{:c=>1}}
irb(main):064:0> b = change_all_values(a) { |val| val + 1 }
=> {:a=>2, :b=>{:c=>2}}
irb(main):066:0> a
=> {:a=>1, :b=>{:c=>1}}
There is deep_merge
yourhash.deep_merge(yourhash) {|_,_,v| v.to_s}
Merge the hash with itself, inspect the value and call to_s on it.
This method requires require 'active_support/core_ext/hash' at the top of file if you are not using ruby on rails.
Obviously, you may handle the conversion of v inside the deep_merge as you like to meet your requirements.
In rails console:
2.3.0 :001 > h1 = { a: true, b: { c: [1, 2, 3] } }
=> {:a=>true, :b=>{:c=>[1, 2, 3]}}
2.3.0 :002 > h1.deep_merge(h1) { |_,_,v| v.to_s}
=> {:a=>"true", :b=>{:c=>"[1, 2, 3]"}}
Well, it's quite simple to write it - so why don't write your own and be absolutely sure how does it behave in all situations ;)
def to_utf8(h)
if h.is_a? String
return h.force_encoding('utf-8')
elsif h.is_a? Symbol
return h.to_s.force_encoding('utf-8').to_sym
elsif h.is_a? Numeric
return h
elsif h.is_a? Array
return h.map { |e| to_utf8(e) }.to_s
else
return h.to_s.force_encoding('utf-8')
end
return hash.to_a.map { |e| result.push(to_utf8(e[0], e[1])) }.to_h
end
You may want to check if all behavior and conversions are correct - and change it if necessary.

Caching, when nil or false are acceptable results [duplicate]

This question already has answers here:
How can I memoize a method that may return true, false, or nil in Ruby?
(2 answers)
Closed 8 years ago.
In some ruby classes, it is useful to cache the results of an expensive operation using the ||= operator, as in the following snippet:
class CacheableCalculations
def foobar
#foobar ||= lambda { some_expensive_calculation }.call
end
end
The issue arrises when the returned value is either nil or false, as this test shows:
class Example
attr_accessor :counter
def initialize(value)
#counter = 0
#value = value
end
def fancy_calculation
#foo ||= lambda { #counter += 1; #value }.call
end
end
first = Example.new(true)
5.times { first.fancy_calculation }
puts first.counter # <== 1, expected
second = Example.new(false)
5.times { second.fancy_calculation }
puts second.counter # <== 5, not caching
third = Example.new(nil)
5.times { third.fancy_calculation }
puts third.counter # <== 5, not caching
Is there any pros or cons with using the defined? operator instead, as in the following block of code?
class Example
attr_accessor :counter
def initialize(value)
#counter = 0
#value = value
end
def fancy_calculation
(defined? #foo) ? #foo : (#foo = lambda { #counter += 1; #value }.call)
end
end
This is still one 1 line, but is quite repetitive.
Is there a better way of easily returning cached results, regardless of what the value is?
The problem with the way it is written is that the ternary operator ?: has higher precedence than assignment = so it is parsed as
def fancy_calculation
((defined? #foo) ? #foo : #foo) = lambda { #counter += 1; #value }.call # NO
end
which you don't want, because #foo is always assigned to.
Instead, do this
def fancy_calculation
defined?(#foo) ? #foo : (#foo = lambda { #counter += 1; #value }.call)
end
This is probably about as succinct as can be without using a separate package/function specifically for memoization.
What you are trying to achieve is called memoization. There used to be a method doing what you need in Rails but at some point they extracted to a separate memoist gem. Check it out: https://github.com/matthewrudy/memoist
There is an alternative one as well: https://github.com/dkubb/memoizable

Reimplementing Enumerable Map method in Ruby

I'm practicing for an internship interview at a ruby shop. One of the job questions I'm expecting is to reimplement an enumerable method.
I'm trying to implement map right now and I'm having trouble figuring out how to implement the case where a block is not given.
class Array
def mapp()
out = []
if block_given?
self.each { |e| out << yield(e) }
else
<-- what goes here? -->
end
out
end
end
Using my current implementation. If I run:
[1,2,3,4,5,6].mapp{|each| each+1} #returns => [2,3,4,5,6,7]
However, I'm not sure how to get cases where a block isn't passed in:
[1,2,3,4].mapp("cat") # should return => ["cat", "cat", "cat", "cat"]
If someone could point me in the right direction. I'd really appreciate it. I tried looking through the source code but it seems to do things very differently than what i'm used to.
static VALUE
enum_flat_map(VALUE obj)
{
VALUE ary;
RETURN_SIZED_ENUMERATOR(obj, 0, 0, enum_size);
ary = rb_ary_new();
rb_block_call(obj, id_each, 0, 0, flat_map_i, ary);
return ary;
}
I suppose that by [1,2,3,4].mapp("cat") you mean [1,2,3,4].mapp{"cat"}.
That said, map without a block returns an enumerator:
[1,2,3,4].map
=> #<Enumerator: [1, 2, 3, 4]:map>
That is the same output of to_enum
[1,2,3,4].to_enum
=> #<Enumerator: [1, 2, 3, 4]:each>
So in your code, you just want to call to_enum:
class Array
def mapp()
out = []
if block_given?
self.each { |e| out << yield(e) }
else
out = to_enum :mapp
end
out
end
end
return to_enum :mapp unless block_given?
Should be sufficient.
See the Implementation of map of the Rubinius implementation which is entirely in Ruby:
https://github.com/rubinius/rubinius/blob/master/kernel/bootstrap/array19.rb
# -*- encoding: us-ascii -*-
class Array
# Creates a new Array from the return values of passing
# each element in self to the supplied block.
def map
return to_enum :map unless block_given?
out = Array.new size
i = #start
total = i + #total
tuple = #tuple
out_tuple = out.tuple
j = 0
while i < total
out_tuple[j] = yield tuple.at(i)
i += 1
j += 1
end
out
end
end
With rubinius there is a ruby implementation written in ruby wherever possible. You can look at their code for enumerable.#collect
Interesting is the difference between
their ruby 1.9 implementation and
their ruby 1.8 implementation.
class Array
def map!
return to_enum :map! unless block_given?
self.each_with_index { |e, index| self[index] = yield(e) }
end
end
Check out the documentation for Object#to_enum

How do I make get attr_accessor_with_default work with a collection?

I want to give one of my models an attribute accessor that defaults to an array of eight zeros. This is the first syntax I tried:
attr_accessor_with_default:weekly_magnitude_list, [0,0,0,0,0,0,0,0]
The above didn't do what I expected because all instances of the model end up sharing the same Array object. The blog (http://barelyenough.org/blog/2007/09/things-to-be-suspicious-of-attr_accessor_with_default-with-a-collection/) that clued me into that suggested a different syntax, basically wrapping the default value in a block.
attr_accessor_with_default(:weekly_magnitude_list) {[0,0,0,0,0,0,0,0]}
That doesn't work (for me, in Rails 3). Any time I call the accessor, I seem to be getting a completely new Array object. That effectively means I can't write to it.
Does anybody know the correct way to do this?
For your pleasure, I've included the output of a simple test demonstrating this:
class Container
attr_accessor_with_default :naive_collection, [0,0]
attr_accessor_with_default(:block_collection) {[0,0]}
end
> c = Container.new
=> #<Container:0x7f3610f717a8>
> c.naive_collection[0] = "foo"
=> "foo"
> Container.new.naive_collection
=> ["foo", 0]
# expected [0,0]
> c.block_collection[0] = "foo"
=> "foo"
> c.block_collection
=> [0, 0]
# expected ["foo", 0]
I just stumbled onto this question while running into the same problem.
For reference, the docs specify the block form is dynamically evaluated in the instance scope. To continue the example, its usefulness is really quite limited, but at least works the way you might expect:
class Container
attr_accessor_with_default(:block_collection) { name.underscore }
end
> c = Container.new(:name => "TestName")
> c.block_collection # => "test_name"
> c.block_collection = "something else" # => "something else"
> c.name => "TestName"
Here's the really quirky part though...
class Container
attr_accessor_with_default :naive_collection, [0, 0]
end
# This works as expected
> c = Container.new
> c.naive_collection = ["foo", "bar"] # => ["foo", "bar"]
> Container.new.naive_collection # => [0, 0]
> c.naive_collection[0] = 0 # => [0, "bar"]
> Container.new.naive_collection # => [0, 0]
# But this doesn't
> c2 = Container.new
> c2.naive_collection # => [0, 0]
> c2.naive_collection[0] = "problem!" # => ["problem!", 0]
> Container.new.naive_collection # => ["problem!", 0]
Digging into the source a bit, I see that attr_accessor_with_default defines an instance method that returns the default value or executes the block. Which is fine.
It then goes on to execute this within a module_eval:
def #{sym}=(value)
class << self;
attr_reader :#{sym}
end
##{sym} = value
end
Which is just ridiculously convoluted. I may end up ticketing this as a bug if I can't figure out the motivation for this behavior.
Update:
I managed to figure out what was going wrong here.
def attr_accessor_with_default(sym, default = Proc.new)
define_method(sym, block_given? ? default : Proc.new { default })
module_eval(<<-EVAL, __FILE__, __LINE__ + 1)
def #{sym}=(value)
class << self; attr_accessor :#{sym} end
##{sym} = value
end
EVAL
end
Initially, the default value exists as a proc.
Once you invoke the setter, the getter and setter methods are overwritten by attr_accessor methods and the instance variable is initialized.
The problem is that the default proc in the getter returns the class-level default value. So when you do something like:
> c2.naive_collection[0] = "problem!" # => ["problem!", 0]
you're actually changing the default value for the class.
I think this method should probably be implemented as:
class Module
def attr_accessor_with_default(sym, default = Proc.new)
module_eval(<<-EVAL, __FILE__, __LINE__ + 1)
def #{sym}
class << self; attr_reader :#{sym} end
##{sym} = #{ default.dup }
end
def #{sym}=(value)
class << self; attr_accessor :#{sym} end
##{sym} = value
end
EVAL
end
end
I'll ticket it and offer up a patch.
Update again:
https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/6496

Resources