gorillib 0.1.11 → 0.4.0pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +1 -0
- data/.rspec +1 -2
- data/.yardopts +9 -0
- data/{CHANGELOG.textile → CHANGELOG.md} +35 -9
- data/Gemfile +21 -14
- data/Guardfile +19 -0
- data/{LICENSE.textile → LICENSE.md} +43 -29
- data/README.md +47 -52
- data/Rakefile +31 -30
- data/TODO.md +32 -0
- data/VERSION +1 -1
- data/examples/builder/ironfan.rb +133 -0
- data/examples/model/simple.rb +17 -0
- data/gorillib.gemspec +106 -86
- data/lib/alt/kernel/call_stack.rb +56 -0
- data/lib/gorillib/array/wrap.rb +53 -0
- data/lib/gorillib/base.rb +3 -3
- data/lib/gorillib/builder/field.rb +5 -0
- data/lib/gorillib/builder.rb +260 -0
- data/lib/gorillib/collection/has_collection.rb +31 -0
- data/lib/gorillib/collection.rb +129 -0
- data/lib/gorillib/configurable.rb +28 -0
- data/lib/gorillib/datetime/{flat.rb → to_flat.rb} +0 -0
- data/lib/gorillib/exception/confidence.rb +17 -0
- data/lib/gorillib/exception/raisers.rb +78 -0
- data/lib/gorillib/hash/mash.rb +202 -0
- data/lib/gorillib/hashlike/slice.rb +53 -19
- data/lib/gorillib/hashlike.rb +5 -3
- data/lib/gorillib/io/system_helpers.rb +30 -0
- data/lib/gorillib/logger/log.rb +18 -0
- data/lib/gorillib/metaprogramming/concern.rb +124 -0
- data/lib/gorillib/model/active_model_conversion.rb +68 -0
- data/lib/gorillib/model/active_model_naming.rb +87 -0
- data/lib/gorillib/model/active_model_shim.rb +33 -0
- data/lib/gorillib/model/base.rb +341 -0
- data/lib/gorillib/model/defaults.rb +71 -0
- data/lib/gorillib/model/errors.rb +14 -0
- data/lib/gorillib/model/factories.rb +372 -0
- data/lib/gorillib/model/field.rb +146 -0
- data/lib/gorillib/model/named_schema.rb +53 -0
- data/lib/gorillib/{struct/hashlike_iteration.rb → model/overlay.rb} +0 -0
- data/lib/gorillib/model/record_schema.rb +9 -0
- data/lib/gorillib/model/serialization.rb +23 -0
- data/lib/gorillib/model/validate.rb +22 -0
- data/lib/gorillib/model.rb +23 -0
- data/lib/gorillib/pathname.rb +78 -0
- data/lib/gorillib/{serialization.rb → serialization/to_wire.rb} +0 -0
- data/lib/gorillib/some.rb +11 -9
- data/lib/gorillib/string/constantize.rb +21 -14
- data/lib/gorillib/string/inflections.rb +6 -76
- data/lib/gorillib/string/inflector.rb +192 -0
- data/lib/gorillib/string/simple_inflector.rb +267 -0
- data/lib/gorillib/type/extended.rb +52 -0
- data/lib/gorillib/utils/capture_output.rb +28 -0
- data/lib/gorillib/utils/console.rb +131 -0
- data/lib/gorillib/utils/nuke_constants.rb +9 -0
- data/lib/gorillib/utils/stub_module.rb +33 -0
- data/spec/examples/builder/ironfan_spec.rb +37 -0
- data/spec/extlib/hash_spec.rb +64 -0
- data/spec/extlib/mash_spec.rb +312 -0
- data/spec/{array → gorillib/array}/compact_blank_spec.rb +2 -2
- data/spec/{array → gorillib/array}/extract_options_spec.rb +2 -2
- data/spec/gorillib/builder_spec.rb +187 -0
- data/spec/gorillib/collection_spec.rb +20 -0
- data/spec/gorillib/configurable_spec.rb +62 -0
- data/spec/{datetime → gorillib/datetime}/parse_spec.rb +3 -3
- data/spec/{datetime/flat_spec.rb → gorillib/datetime/to_flat_spec.rb} +4 -4
- data/spec/{enumerable → gorillib/enumerable}/sum_spec.rb +5 -5
- data/spec/gorillib/exception/raisers_spec.rb +60 -0
- data/spec/{hash → gorillib/hash}/compact_spec.rb +2 -2
- data/spec/{hash → gorillib/hash}/deep_compact_spec.rb +3 -3
- data/spec/{hash → gorillib/hash}/deep_merge_spec.rb +2 -2
- data/spec/{hash → gorillib/hash}/keys_spec.rb +2 -2
- data/spec/{hash → gorillib/hash}/reverse_merge_spec.rb +2 -2
- data/spec/{hash → gorillib/hash}/slice_spec.rb +2 -2
- data/spec/{hash → gorillib/hash}/zip_spec.rb +2 -2
- data/spec/{hashlike → gorillib/hashlike}/behave_same_as_hash_spec.rb +6 -3
- data/spec/{hashlike → gorillib/hashlike}/deep_hash_spec.rb +2 -2
- data/spec/{hashlike → gorillib/hashlike}/hashlike_behavior_spec.rb +32 -30
- data/spec/{hashlike → gorillib/hashlike}/hashlike_via_accessors_spec.rb +3 -3
- data/spec/{hashlike_spec.rb → gorillib/hashlike_spec.rb} +3 -3
- data/spec/{logger → gorillib/logger}/log_spec.rb +2 -2
- data/spec/{metaprogramming → gorillib/metaprogramming}/aliasing_spec.rb +3 -3
- data/spec/{metaprogramming → gorillib/metaprogramming}/class_attribute_spec.rb +3 -3
- data/spec/{metaprogramming → gorillib/metaprogramming}/delegation_spec.rb +3 -3
- data/spec/{metaprogramming → gorillib/metaprogramming}/singleton_class_spec.rb +3 -3
- data/spec/gorillib/model/record/defaults_spec.rb +108 -0
- data/spec/gorillib/model/record/factories_spec.rb +321 -0
- data/spec/gorillib/model/record/overlay_spec.rb +46 -0
- data/spec/gorillib/model/serialization_spec.rb +48 -0
- data/spec/gorillib/model_spec.rb +281 -0
- data/spec/{numeric → gorillib/numeric}/clamp_spec.rb +2 -2
- data/spec/{object → gorillib/object}/blank_spec.rb +2 -2
- data/spec/{object → gorillib/object}/try_dup_spec.rb +2 -2
- data/spec/{object → gorillib/object}/try_spec.rb +3 -2
- data/spec/gorillib/pathname_spec.rb +114 -0
- data/spec/{string → gorillib/string}/constantize_spec.rb +2 -2
- data/spec/{string → gorillib/string}/human_spec.rb +2 -2
- data/spec/{string → gorillib/string}/inflections_spec.rb +4 -3
- data/spec/{string → gorillib/string}/inflector_test_cases.rb +0 -0
- data/spec/{string → gorillib/string}/truncate_spec.rb +4 -10
- data/spec/gorillib/type/extended_spec.rb +120 -0
- data/spec/gorillib/utils/capture_output_spec.rb +71 -0
- data/spec/spec_helper.rb +8 -11
- data/spec/support/gorillib_test_helpers.rb +66 -0
- data/spec/support/hashlike_fuzzing_helper.rb +31 -33
- data/spec/support/hashlike_helper.rb +3 -3
- data/spec/support/model_test_helpers.rb +81 -0
- data/spec/support/shared_examples/included_module.rb +20 -0
- metadata +177 -158
- data/lib/gorillib/array/average.rb +0 -13
- data/lib/gorillib/array/sorted_median.rb +0 -11
- data/lib/gorillib/array/sorted_percentile.rb +0 -11
- data/lib/gorillib/array/sorted_sample.rb +0 -12
- data/lib/gorillib/dsl_object.rb +0 -64
- data/lib/gorillib/hash/indifferent_access.rb +0 -207
- data/lib/gorillib/hash/tree_merge.rb +0 -4
- data/lib/gorillib/hashlike/tree_merge.rb +0 -49
- data/lib/gorillib/metaprogramming/cattr_accessor.rb +0 -79
- data/lib/gorillib/metaprogramming/mattr_accessor.rb +0 -61
- data/lib/gorillib/receiver/active_model_shim.rb +0 -32
- data/lib/gorillib/receiver/acts_as_hash.rb +0 -195
- data/lib/gorillib/receiver/acts_as_loadable.rb +0 -42
- data/lib/gorillib/receiver/locale/en.yml +0 -27
- data/lib/gorillib/receiver/tree_diff.rb +0 -74
- data/lib/gorillib/receiver/validations.rb +0 -30
- data/lib/gorillib/receiver.rb +0 -402
- data/lib/gorillib/receiver_model.rb +0 -21
- data/lib/gorillib/struct/acts_as_hash.rb +0 -108
- data/notes/fancy_hashes_and_receivers.textile +0 -120
- data/notes/hash_rdocs.textile +0 -97
- data/spec/array/average_spec.rb +0 -24
- data/spec/array/sorted_median_spec.rb +0 -18
- data/spec/array/sorted_percentile_spec.rb +0 -24
- data/spec/array/sorted_sample_spec.rb +0 -28
- data/spec/dsl_object_spec.rb +0 -99
- data/spec/hash/indifferent_access_spec.rb +0 -391
- data/spec/metaprogramming/cattr_accessor_spec.rb +0 -43
- data/spec/metaprogramming/mattr_accessor_spec.rb +0 -45
- data/spec/receiver/acts_as_hash_spec.rb +0 -295
- data/spec/receiver_spec.rb +0 -551
- data/spec/struct/acts_as_hash_fuzz_spec.rb +0 -71
- data/spec/struct/acts_as_hash_spec.rb +0 -422
|
@@ -1,74 +0,0 @@
|
|
|
1
|
-
module Receiver
|
|
2
|
-
def tree_diff(other)
|
|
3
|
-
diff_hsh = {}
|
|
4
|
-
other = other.symbolize_keys if other.respond_to?(:symbolize_keys)
|
|
5
|
-
each do |k, v|
|
|
6
|
-
case
|
|
7
|
-
when v.is_a?(Array) && other[k].is_a?(Array)
|
|
8
|
-
val = v.tree_diff(other[k])
|
|
9
|
-
diff_hsh[k] = val unless val.blank?
|
|
10
|
-
when v.respond_to?(:tree_diff) && other[k].respond_to?(:to_hash)
|
|
11
|
-
val = v.tree_diff(other[k])
|
|
12
|
-
diff_hsh[k] = val unless val.blank?
|
|
13
|
-
else
|
|
14
|
-
diff_hsh[k] = v unless v == other[k]
|
|
15
|
-
end
|
|
16
|
-
end
|
|
17
|
-
other_hsh = other.dup.delete_if{|k, v| has_key?(k) }
|
|
18
|
-
diff_hsh.merge!(other_hsh)
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
module ActsAsHash
|
|
22
|
-
def <=>(other)
|
|
23
|
-
return 1 if other.blank?
|
|
24
|
-
each_key do |k|
|
|
25
|
-
if has_key?(k) && other.has_key?(k)
|
|
26
|
-
cmp = self[k] <=> other[k]
|
|
27
|
-
return cmp unless cmp == 0
|
|
28
|
-
end
|
|
29
|
-
end
|
|
30
|
-
0
|
|
31
|
-
end
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
class Array
|
|
36
|
-
def tree_diff(other)
|
|
37
|
-
arr = dup
|
|
38
|
-
if other.length > arr.length then arr = arr + ([nil] * (other.length - arr.length)) end
|
|
39
|
-
diff_ary = arr.zip(other).map do |arr_el, other_el|
|
|
40
|
-
if arr_el.respond_to?(:tree_diff) && other_el.respond_to?(:to_hash)
|
|
41
|
-
arr_el.tree_diff(other_el)
|
|
42
|
-
else
|
|
43
|
-
(arr_el == other_el) ? nil : [arr_el, other_el]
|
|
44
|
-
end
|
|
45
|
-
end.reject(&:blank?)
|
|
46
|
-
end
|
|
47
|
-
end
|
|
48
|
-
|
|
49
|
-
class Hash
|
|
50
|
-
# Returns a hash that represents the difference between two hashes.
|
|
51
|
-
#
|
|
52
|
-
# Examples:
|
|
53
|
-
#
|
|
54
|
-
# {1 => 2}.tree_diff(1 => 2) # => {}
|
|
55
|
-
# {1 => 2}.tree_diff(1 => 3) # => {1 => 2}
|
|
56
|
-
# {}.tree_diff(1 => 2) # => {1 => 2}
|
|
57
|
-
# {1 => 2, 3 => 4}.tree_diff(1 => 2) # => {3 => 4}
|
|
58
|
-
def tree_diff(other)
|
|
59
|
-
diff_hsh = self.dup
|
|
60
|
-
each do |k, v|
|
|
61
|
-
case
|
|
62
|
-
when v.is_a?(Array) && other[k].is_a?(Array)
|
|
63
|
-
diff_hsh[k] = v.tree_diff(other[k])
|
|
64
|
-
diff_hsh.delete(k) if diff_hsh[k].blank?
|
|
65
|
-
when v.respond_to?(:tree_diff) && other[k].respond_to?(:to_hash)
|
|
66
|
-
diff_hsh[k] = v.tree_diff(other[k])
|
|
67
|
-
diff_hsh.delete(k) if diff_hsh[k].blank?
|
|
68
|
-
else diff_hsh.delete(k) if v == other[k]
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
other_hsh = other.dup.delete_if{|k, v| has_key?(k) || has_key?(k.to_s) }
|
|
72
|
-
diff_hsh.merge!(other_hsh)
|
|
73
|
-
end
|
|
74
|
-
end
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
module Receiver
|
|
2
|
-
|
|
3
|
-
# An array of strings describing any ways this fails validation
|
|
4
|
-
def validation_errors
|
|
5
|
-
errors = []
|
|
6
|
-
if (ma = missing_attrs).present?
|
|
7
|
-
errors << "Missing values for {#{ma.join(",")}}"
|
|
8
|
-
end
|
|
9
|
-
errors
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
# returns a list of required but missing attributes
|
|
13
|
-
def missing_attrs
|
|
14
|
-
missing = []
|
|
15
|
-
self.class.required_rcvrs.each do |name, info|
|
|
16
|
-
missing << name if (not attr_set?(name))
|
|
17
|
-
end
|
|
18
|
-
missing
|
|
19
|
-
end
|
|
20
|
-
|
|
21
|
-
# methods become class-level
|
|
22
|
-
module ClassMethods
|
|
23
|
-
|
|
24
|
-
# class method gives info for all receiver attributes with required => true
|
|
25
|
-
def required_rcvrs
|
|
26
|
-
receiver_attrs.select{|name, info| info[:required] }
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
end
|
data/lib/gorillib/receiver.rb
DELETED
|
@@ -1,402 +0,0 @@
|
|
|
1
|
-
require 'gorillib/object/blank'
|
|
2
|
-
require 'gorillib/object/try'
|
|
3
|
-
require 'gorillib/object/try_dup'
|
|
4
|
-
require 'gorillib/array/extract_options'
|
|
5
|
-
require 'gorillib/metaprogramming/class_attribute'
|
|
6
|
-
|
|
7
|
-
# dummy type for receiving True or False
|
|
8
|
-
class Boolean ; end unless defined?(Boolean)
|
|
9
|
-
|
|
10
|
-
# Receiver lets you describe complex (even recursive!) actively-typed data models that
|
|
11
|
-
# * are creatable or assignable from static data structures
|
|
12
|
-
# * perform efficient type conversion when assigning from a data structure,
|
|
13
|
-
# * but with nothing in the way of normal assignment or instantiation
|
|
14
|
-
# * and no requirements on the initializer
|
|
15
|
-
#
|
|
16
|
-
# class Tweet
|
|
17
|
-
# include Receiver
|
|
18
|
-
# rcvr_accessor :id, Integer
|
|
19
|
-
# rcvr_accessor :user_id, Integer
|
|
20
|
-
# rcvr_accessor :created_at, Time
|
|
21
|
-
# end
|
|
22
|
-
# p Tweet.receive(:id => "7", :user_id => 9, :created_at => "20101231010203" )
|
|
23
|
-
# # => #<Tweet @id=7, @user_id=9, @created_at=2010-12-31 07:02:03 UTC>
|
|
24
|
-
#
|
|
25
|
-
# You can override receive behavior in a straightforward and predictable way:
|
|
26
|
-
#
|
|
27
|
-
# class TwitterUser
|
|
28
|
-
# include Receiver
|
|
29
|
-
# rcvr_accessor :id, Integer
|
|
30
|
-
# rcvr_accessor :screen_name, String
|
|
31
|
-
# rcvr_accessor :follower_ids, Array, :of => Integer
|
|
32
|
-
# # accumulate unique follower ids
|
|
33
|
-
# def receive_follower_ids(arr)
|
|
34
|
-
# @follower_ids = (@follower_ids||[]) + arr.map(&:to_i)
|
|
35
|
-
# @follower_ids.uniq!
|
|
36
|
-
# end
|
|
37
|
-
# end
|
|
38
|
-
#
|
|
39
|
-
# The receiver pattern works naturally with inheritance:
|
|
40
|
-
#
|
|
41
|
-
# class TweetWithUser < Tweet
|
|
42
|
-
# rcvr_accessor :user, TwitterUser
|
|
43
|
-
# after_receive do |hsh|
|
|
44
|
-
# self.user_id = self.user.id if self.user
|
|
45
|
-
# end
|
|
46
|
-
# end
|
|
47
|
-
# p TweetWithUser.receive(:id => 8675309, :created_at => "20101231010203", :user => { :id => 24601, :screen_name => 'bob', :follower_ids => [1, 8, 3, 4] })
|
|
48
|
-
# => #<TweetWithUser @id=8675309, @created_at=2010-12-31 07:02:03 UTC, @user=#<TwitterUser @id=24601, @screen_name="bob", @follower_ids=[1, 8, 3, 4]>, @user_id=24601>
|
|
49
|
-
#
|
|
50
|
-
# TweetWithUser was able to add another receiver, applicable only to itself and its subclasses.
|
|
51
|
-
#
|
|
52
|
-
# The receive method works well with sparse data -- you can accumulate
|
|
53
|
-
# attributes without trampling formerly set values:
|
|
54
|
-
#
|
|
55
|
-
# tw = Tweet.receive(:id => "7", :user_id => 9 )
|
|
56
|
-
# p tw
|
|
57
|
-
# # => #<Tweet @id=7, @user_id=9>
|
|
58
|
-
#
|
|
59
|
-
# tw.receive!(:created_at => "20101231010203" )
|
|
60
|
-
# p tw
|
|
61
|
-
# # => #<Tweet @id=7, @user_id=9, @created_at=2010-12-31 07:02:03 UTC>
|
|
62
|
-
#
|
|
63
|
-
# Note the distinction between an explicit nil field and a missing field:
|
|
64
|
-
#
|
|
65
|
-
# tw.receive!(:user_id => nil, :created_at => "20090506070809" )
|
|
66
|
-
# p tw
|
|
67
|
-
# # => #<Tweet @id=7, @user_id=nil, @created_at=2009-05-06 12:08:09 UTC>
|
|
68
|
-
#
|
|
69
|
-
# There are helpers for default and required attributes:
|
|
70
|
-
#
|
|
71
|
-
# class Foo
|
|
72
|
-
# include Receiver
|
|
73
|
-
# rcvr_accessor :is_reqd, String, :required => true
|
|
74
|
-
# rcvr_accessor :also_reqd, String, :required => true
|
|
75
|
-
# rcvr_accessor :has_default, String, :default => 'hello'
|
|
76
|
-
# end
|
|
77
|
-
# foo_obj = Foo.receive(:is_reqd => "hi")
|
|
78
|
-
# # => #<Foo:0x00000100bd9740 @is_reqd="hi" @has_default="hello">
|
|
79
|
-
# foo_obj.missing_attrs
|
|
80
|
-
# # => [:also_reqd]
|
|
81
|
-
#
|
|
82
|
-
module Receiver
|
|
83
|
-
|
|
84
|
-
RECEIVER_BODIES = {} unless defined?(RECEIVER_BODIES)
|
|
85
|
-
RECEIVER_BODIES[Symbol] = %q{ v.blank? ? nil : v.to_sym }
|
|
86
|
-
RECEIVER_BODIES[Integer] = %q{ v.blank? ? nil : v.to_i }
|
|
87
|
-
RECEIVER_BODIES[Float] = %q{ v.blank? ? nil : v.to_f }
|
|
88
|
-
RECEIVER_BODIES[String] = %q{ v.to_s }
|
|
89
|
-
RECEIVER_BODIES[Time] = %q{ v.nil? ? nil : Time.parse(v.to_s).utc rescue nil }
|
|
90
|
-
RECEIVER_BODIES[Date] = %q{ v.nil? ? nil : Date.parse(v.to_s) rescue nil }
|
|
91
|
-
RECEIVER_BODIES[Array] = %q{ case when v.nil? then nil when v.blank? then [] else Array(v) end }
|
|
92
|
-
RECEIVER_BODIES[Hash] = %q{ case when v.nil? then nil when v.blank? then {} else v end }
|
|
93
|
-
RECEIVER_BODIES[Boolean] = %q{ case when v.nil? then nil when v.to_s.strip.blank? then false else v.to_s.strip != "false" end }
|
|
94
|
-
RECEIVER_BODIES[NilClass] = %q{ raise ArgumentError, "This field must be nil, but [#{v}] was given" unless (v.nil?) ; nil }
|
|
95
|
-
RECEIVER_BODIES[Object] = %q{ v } # accept and love the object just as it is
|
|
96
|
-
|
|
97
|
-
#
|
|
98
|
-
# Give each base class a receive method
|
|
99
|
-
#
|
|
100
|
-
RECEIVER_BODIES.each do |k,b|
|
|
101
|
-
if k.is_a?(Class) && b.is_a?(String)
|
|
102
|
-
k.class_eval <<-STR, __FILE__, __LINE__ + 1
|
|
103
|
-
def self.receive(v)
|
|
104
|
-
#{b}
|
|
105
|
-
end
|
|
106
|
-
STR
|
|
107
|
-
elsif k.is_a?(Class)
|
|
108
|
-
k.class_eval do
|
|
109
|
-
define_singleton_method(:receive, &b)
|
|
110
|
-
end
|
|
111
|
-
end
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
TYPE_ALIASES = {
|
|
115
|
-
:null => NilClass,
|
|
116
|
-
:boolean => Boolean,
|
|
117
|
-
:string => String, :bytes => String,
|
|
118
|
-
:symbol => Symbol,
|
|
119
|
-
:int => Integer, :integer => Integer, :long => Integer,
|
|
120
|
-
:time => Time, :date => Date,
|
|
121
|
-
:float => Float, :double => Float,
|
|
122
|
-
:hash => Hash, :map => Hash,
|
|
123
|
-
:array => Array,
|
|
124
|
-
} unless defined?(TYPE_ALIASES)
|
|
125
|
-
|
|
126
|
-
#
|
|
127
|
-
# modify object in place with new typecast values.
|
|
128
|
-
#
|
|
129
|
-
def receive! hsh={}
|
|
130
|
-
raise ArgumentError, "Can't receive (it isn't hashlike): {#{hsh.inspect}}" unless hsh.respond_to?(:[]) && hsh.respond_to?(:has_key?)
|
|
131
|
-
_receiver_fields.each do |attr|
|
|
132
|
-
if hsh.has_key?(attr.to_sym) then val = hsh[attr.to_sym]
|
|
133
|
-
elsif hsh.has_key?(attr.to_s) then val = hsh[attr.to_s]
|
|
134
|
-
else next ; end
|
|
135
|
-
_receive_attr attr, val
|
|
136
|
-
end
|
|
137
|
-
impose_defaults!(hsh)
|
|
138
|
-
replace_options!(hsh)
|
|
139
|
-
run_after_receivers(hsh)
|
|
140
|
-
self
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
# true if the attr is a receiver variable and it has been set
|
|
144
|
-
def attr_set?(attr)
|
|
145
|
-
receiver_attrs.has_key?(attr) && self.instance_variable_defined?("@#{attr}")
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
protected
|
|
149
|
-
|
|
150
|
-
def unset!(attr)
|
|
151
|
-
self.send(:remove_instance_variable, "@#{attr}") if self.instance_variable_defined?("@#{attr}")
|
|
152
|
-
end
|
|
153
|
-
|
|
154
|
-
def _receive_attr attr, val
|
|
155
|
-
self.send("receive_#{attr}", val)
|
|
156
|
-
end
|
|
157
|
-
|
|
158
|
-
def _receiver_fields
|
|
159
|
-
self.class.receiver_attr_names
|
|
160
|
-
end
|
|
161
|
-
|
|
162
|
-
def _receiver_defaults
|
|
163
|
-
self.class.receiver_defaults
|
|
164
|
-
end
|
|
165
|
-
|
|
166
|
-
def _after_receivers
|
|
167
|
-
self.class.after_receivers
|
|
168
|
-
end
|
|
169
|
-
|
|
170
|
-
def impose_defaults!(hsh)
|
|
171
|
-
_receiver_defaults.each do |attr, val|
|
|
172
|
-
next if attr_set?(attr)
|
|
173
|
-
self.instance_variable_set "@#{attr}", val.try_dup
|
|
174
|
-
end
|
|
175
|
-
end
|
|
176
|
-
|
|
177
|
-
# class Foo
|
|
178
|
-
# include Receiver
|
|
179
|
-
# include Receiver::ActsAsHash
|
|
180
|
-
# rcvr_accessor :attribute, String, :default => 'okay' :replace => { 'bad' => 'good' }
|
|
181
|
-
# end
|
|
182
|
-
#
|
|
183
|
-
# f = Foo.receive({:attribute => 'bad'})
|
|
184
|
-
# => #<Foo:0x10156c820 @attribute="good">
|
|
185
|
-
#
|
|
186
|
-
def replace_options!(hsh)
|
|
187
|
-
self.receiver_attrs.each do |attr, info|
|
|
188
|
-
val = self.instance_variable_get("@#{attr}")
|
|
189
|
-
if info[:replace] and info[:replace].has_key? val
|
|
190
|
-
self.instance_variable_set "@#{attr}", info[:replace][val]
|
|
191
|
-
end
|
|
192
|
-
end
|
|
193
|
-
end
|
|
194
|
-
|
|
195
|
-
def run_after_receivers(hsh)
|
|
196
|
-
_after_receivers.each do |after_receiver|
|
|
197
|
-
self.instance_exec(hsh, &after_receiver)
|
|
198
|
-
end
|
|
199
|
-
end
|
|
200
|
-
|
|
201
|
-
public
|
|
202
|
-
|
|
203
|
-
module ClassMethods
|
|
204
|
-
|
|
205
|
-
#
|
|
206
|
-
# Returns a new instance with the given hash used to set all rcvrs.
|
|
207
|
-
#
|
|
208
|
-
# All args up to the last one are passed to the initializer.
|
|
209
|
-
# The last arg must be a hash -- its attributes are set on the newly-created object
|
|
210
|
-
#
|
|
211
|
-
# @param hsh [Hash] attr-value pairs to set on the newly created object.
|
|
212
|
-
# @param *args [Array] arguments to pass to the constructor
|
|
213
|
-
# @return [Object] a new instance
|
|
214
|
-
def receive *args
|
|
215
|
-
hsh = args.pop || {}
|
|
216
|
-
raise ArgumentError, "Can't receive (it isn't hashlike): {#{hsh.inspect}} -- the hsh should be the *last* arg" unless hsh.respond_to?(:[]) && hsh.respond_to?(:has_key?)
|
|
217
|
-
obj = self.new(*args)
|
|
218
|
-
obj.receive!(hsh)
|
|
219
|
-
end
|
|
220
|
-
|
|
221
|
-
#
|
|
222
|
-
# define a receiver attribute.
|
|
223
|
-
# automatically generates an attr_accessor on the class if none exists
|
|
224
|
-
#
|
|
225
|
-
# @option [Boolean] :required - Adds an error on validation if the attribute is never set
|
|
226
|
-
# @option [Object] :default - After any receive! operation, attribute is set to this value unless attr_set? is true
|
|
227
|
-
# @option [Class] :of - For collections (Array, Hash, etc), the type of the collection's items
|
|
228
|
-
#
|
|
229
|
-
def rcvr name, type, info={}
|
|
230
|
-
name = name.to_sym
|
|
231
|
-
type = type_to_klass(type)
|
|
232
|
-
body = receiver_body_for(type, info)
|
|
233
|
-
if body.is_a?(String)
|
|
234
|
-
class_eval(%Q{
|
|
235
|
-
def receive_#{name}(v)
|
|
236
|
-
self.instance_variable_set("@#{name}", (#{body}))
|
|
237
|
-
end}, __FILE__, __LINE__ + 1)
|
|
238
|
-
else
|
|
239
|
-
define_method("receive_#{name}") do |*args|
|
|
240
|
-
v = body.call(*args)
|
|
241
|
-
self.instance_variable_set("@#{name}", v)
|
|
242
|
-
v
|
|
243
|
-
end
|
|
244
|
-
end
|
|
245
|
-
# careful here: don't modify parent's class_attribute in-place
|
|
246
|
-
self.receiver_attrs = self.receiver_attrs.dup
|
|
247
|
-
self.receiver_attr_names += [name] unless receiver_attr_names.include?(name)
|
|
248
|
-
self.receiver_attrs[name] = info.merge({ :name => name, :type => type })
|
|
249
|
-
end
|
|
250
|
-
|
|
251
|
-
# make a block to run after each time .receive! is invoked
|
|
252
|
-
def after_receive &block
|
|
253
|
-
self.after_receivers += [block]
|
|
254
|
-
end
|
|
255
|
-
|
|
256
|
-
# defines a receiver attribute, an attr_reader and an attr_writer
|
|
257
|
-
# attr_reader is skipped if the getter method is already defined;
|
|
258
|
-
# attr_writer is skipped if the setter method is already defined;
|
|
259
|
-
def rcvr_accessor name, type, info={}
|
|
260
|
-
attr_reader(name) unless method_defined?(name)
|
|
261
|
-
attr_writer(name) unless method_defined?("#{name}=")
|
|
262
|
-
rcvr name, type, info
|
|
263
|
-
end
|
|
264
|
-
# defines a receiver attribute and an attr_reader
|
|
265
|
-
# attr_reader is skipped if the getter method is already defined.
|
|
266
|
-
def rcvr_reader name, type, info={}
|
|
267
|
-
attr_reader(name) unless method_defined?(name)
|
|
268
|
-
rcvr name, type, info
|
|
269
|
-
end
|
|
270
|
-
# defines a receiver attribute and an attr_writer
|
|
271
|
-
# attr_writer is skipped if the setter method is already defined.
|
|
272
|
-
def rcvr_writer name, type, info={}
|
|
273
|
-
attr_writer(name) unless method_defined?("#{name}=")
|
|
274
|
-
rcvr name, type, info
|
|
275
|
-
end
|
|
276
|
-
|
|
277
|
-
#
|
|
278
|
-
# Defines a receiver for attributes sent to receive! that are
|
|
279
|
-
# * not defined as receivers
|
|
280
|
-
# * attribute name does not start with '_'
|
|
281
|
-
#
|
|
282
|
-
# @example
|
|
283
|
-
# class Foo ; include Receiver
|
|
284
|
-
# rcvr_accessor :bob, String
|
|
285
|
-
# rcvr_remaining :other_params
|
|
286
|
-
# end
|
|
287
|
-
# foo_obj = Foo.receive(:bob => 'hi, bob", :joe => 'hi, joe')
|
|
288
|
-
# # => <Foo @bob='hi, bob' @other_params={ :joe => 'hi, joe' }>
|
|
289
|
-
def rcvr_remaining name, info={}
|
|
290
|
-
rcvr_reader name, Hash, info
|
|
291
|
-
after_receive do |hsh|
|
|
292
|
-
remaining_vals_hsh = hsh.reject{|k,v| (receiver_attrs.include?(k)) || (k.to_s =~ /^_/) }
|
|
293
|
-
self._receive_attr name, remaining_vals_hsh
|
|
294
|
-
end
|
|
295
|
-
end
|
|
296
|
-
|
|
297
|
-
# a hash from attribute names to their default values if given
|
|
298
|
-
def receiver_defaults
|
|
299
|
-
defs = {}
|
|
300
|
-
receiver_attrs.each do |name, info|
|
|
301
|
-
defs[name] = info[:default] if info.has_key?(:default)
|
|
302
|
-
end
|
|
303
|
-
defs
|
|
304
|
-
end
|
|
305
|
-
|
|
306
|
-
# returns an in-order traversal of the
|
|
307
|
-
#
|
|
308
|
-
def tuple_keys
|
|
309
|
-
return @tuple_keys if @tuple_keys
|
|
310
|
-
@tuple_keys = self
|
|
311
|
-
@tuple_keys = receiver_attrs.map do |attr, info|
|
|
312
|
-
info[:type].try(:tuple_keys) || attr
|
|
313
|
-
end.flatten
|
|
314
|
-
end
|
|
315
|
-
|
|
316
|
-
def consume_tuple(tuple)
|
|
317
|
-
obj = self.new
|
|
318
|
-
receiver_attrs.each do |attr, info|
|
|
319
|
-
if info[:type].respond_to?(:consume_tuple)
|
|
320
|
-
val = info[:type].consume_tuple(tuple)
|
|
321
|
-
else
|
|
322
|
-
val = tuple.shift
|
|
323
|
-
end
|
|
324
|
-
# obj.send("receive_#{attr}", val)
|
|
325
|
-
obj.send("#{attr}=", val)
|
|
326
|
-
end
|
|
327
|
-
obj
|
|
328
|
-
end
|
|
329
|
-
|
|
330
|
-
protected
|
|
331
|
-
def receiver_body_for type, info
|
|
332
|
-
type = type_to_klass(type)
|
|
333
|
-
# Note that Array and Hash only need (and only get) special treatment when
|
|
334
|
-
# they have an :of => SomeType option.
|
|
335
|
-
case
|
|
336
|
-
when info[:of] && (type == Array)
|
|
337
|
-
receiver_type = info[:of]
|
|
338
|
-
lambda{|v| v.nil? ? nil : v.map{|el| receiver_type.receive(el) } }
|
|
339
|
-
when info[:of] && (type == Hash)
|
|
340
|
-
receiver_type = info[:of]
|
|
341
|
-
lambda{|v| v.nil? ? nil : v.inject({}){|h, (el,val)| h[el] = receiver_type.receive(val); h } }
|
|
342
|
-
when Receiver::RECEIVER_BODIES.include?(type)
|
|
343
|
-
Receiver::RECEIVER_BODIES[type]
|
|
344
|
-
when type.is_a?(Class)
|
|
345
|
-
lambda{|v| v.blank? ? nil : type.receive(v) }
|
|
346
|
-
else
|
|
347
|
-
raise("Can't receive #{type} #{info}")
|
|
348
|
-
end
|
|
349
|
-
end
|
|
350
|
-
|
|
351
|
-
def type_to_klass(type)
|
|
352
|
-
case
|
|
353
|
-
when type.is_a?(Class) then return type
|
|
354
|
-
when TYPE_ALIASES.has_key?(type) then TYPE_ALIASES[type]
|
|
355
|
-
# when (type.is_a?(Symbol) && type.to_s =~ /^[A-Z]/) then type.to_s.constantize
|
|
356
|
-
else raise ArgumentError, "Can\'t handle type #{type}: is it a Class or one of the TYPE_ALIASES?"
|
|
357
|
-
end
|
|
358
|
-
end
|
|
359
|
-
end
|
|
360
|
-
|
|
361
|
-
def to_tuple
|
|
362
|
-
tuple = []
|
|
363
|
-
self.each_value do |val|
|
|
364
|
-
if val.respond_to?(:to_tuple)
|
|
365
|
-
tuple += val.to_tuple
|
|
366
|
-
else
|
|
367
|
-
tuple << val
|
|
368
|
-
end
|
|
369
|
-
end
|
|
370
|
-
tuple
|
|
371
|
-
end
|
|
372
|
-
|
|
373
|
-
module ClassMethods
|
|
374
|
-
# By default, the hashlike methods iterate over the receiver attributes.
|
|
375
|
-
# If you want to filter our add to the keys list, override this method
|
|
376
|
-
#
|
|
377
|
-
# @example
|
|
378
|
-
# def self.members
|
|
379
|
-
# super + [:firstname, :lastname] - [:fullname]
|
|
380
|
-
# end
|
|
381
|
-
#
|
|
382
|
-
def members
|
|
383
|
-
receiver_attr_names
|
|
384
|
-
end
|
|
385
|
-
end
|
|
386
|
-
|
|
387
|
-
# set up receiver attributes, and bring in methods from the ClassMethods module at class-level
|
|
388
|
-
def self.included base
|
|
389
|
-
base.class_eval do
|
|
390
|
-
unless method_defined?(:receiver_attrs)
|
|
391
|
-
class_attribute :receiver_attrs
|
|
392
|
-
class_attribute :receiver_attr_names
|
|
393
|
-
class_attribute :after_receivers
|
|
394
|
-
self.receiver_attrs = {} # info about the attr
|
|
395
|
-
self.receiver_attr_names = [] # ordered set of attr names
|
|
396
|
-
self.after_receivers = [] # blocks to execute following receive!
|
|
397
|
-
extend ClassMethods
|
|
398
|
-
end
|
|
399
|
-
end
|
|
400
|
-
end
|
|
401
|
-
|
|
402
|
-
end
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
require 'gorillib/hashlike'
|
|
2
|
-
require 'gorillib/hashlike/tree_merge'
|
|
3
|
-
require 'gorillib/receiver'
|
|
4
|
-
require 'gorillib/receiver/acts_as_hash'
|
|
5
|
-
require 'gorillib/receiver/acts_as_loadable'
|
|
6
|
-
require 'gorillib/receiver/active_model_shim'
|
|
7
|
-
|
|
8
|
-
module Gorillib
|
|
9
|
-
module ReceiverModel
|
|
10
|
-
def self.included base
|
|
11
|
-
base.class_eval do
|
|
12
|
-
include Receiver
|
|
13
|
-
include Receiver::ActsAsHash
|
|
14
|
-
include Receiver::ActsAsLoadable
|
|
15
|
-
include Gorillib::Hashlike
|
|
16
|
-
include Gorillib::Hashlike::TreeMerge
|
|
17
|
-
include Receiver::ActiveModelShim
|
|
18
|
-
end
|
|
19
|
-
end
|
|
20
|
-
end
|
|
21
|
-
end
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
module Gorillib
|
|
2
|
-
module Struct
|
|
3
|
-
#
|
|
4
|
-
# Make a Struct behave mostly like a hash.
|
|
5
|
-
#
|
|
6
|
-
# By default, the hashlike methods iterate over the receiver attributes:
|
|
7
|
-
# instance #keys delegates to self.class.keys which calls
|
|
8
|
-
# receiver_attr_names. If you want to filter our add to the keys list, you
|
|
9
|
-
# can just override the class-level keys method (and call super, or not):
|
|
10
|
-
#
|
|
11
|
-
# def self.keys
|
|
12
|
-
# super + [:firstname, :lastname] - [:fullname]
|
|
13
|
-
# end
|
|
14
|
-
#
|
|
15
|
-
# in addition to the below, by including Enumerable, this also adds
|
|
16
|
-
#
|
|
17
|
-
# :each_cons, :each_entry, :each_slice, :each_with_index, :each_with_object,
|
|
18
|
-
# :map, :collect, :collect_concat, :entries, :to_a, :flat_map, :inject, :reduce,
|
|
19
|
-
# :group_by, :chunk, :cycle, :partition, :reverse_each, :slice_before, :drop,
|
|
20
|
-
# :drop_while, :take, :take_while, :detect, :find, :find_all, :find_index, :grep,
|
|
21
|
-
# :all?, :any?, :none?, :one?, :first, :count, :zip :max, :max_by, :min, :min_by,
|
|
22
|
-
# :minmax, :minmax_by, :sort, :sort_by
|
|
23
|
-
#
|
|
24
|
-
# As opposed to hash, does *not* define
|
|
25
|
-
#
|
|
26
|
-
# default, default=, default_proc, default_proc=, shift, flatten, compare_by_identity
|
|
27
|
-
# compare_by_identity? rehash
|
|
28
|
-
#
|
|
29
|
-
# @example
|
|
30
|
-
# StructUsingHashlike = Struct.new(:a, :b, :c, :z) do
|
|
31
|
-
# include Gorillib::Struct::ActsAsHash
|
|
32
|
-
# include Gorillib::Hashlike
|
|
33
|
-
# end
|
|
34
|
-
# foo = StructUsingHashlike.new(1,2,3)
|
|
35
|
-
# foo.to_hash # => { :a => 1, :b => 2, :c => 3, :z => nil }
|
|
36
|
-
#
|
|
37
|
-
module ActsAsHash
|
|
38
|
-
|
|
39
|
-
# Hashlike#delete
|
|
40
|
-
#
|
|
41
|
-
# Deletes and returns the value from +hsh+ whose key is equal to +key+. If the
|
|
42
|
-
# optional code block is given and the key is not found, pass in the key and
|
|
43
|
-
# return the result of +block+.
|
|
44
|
-
#
|
|
45
|
-
# In a normal hash, a default value can be set; none is provided here.
|
|
46
|
-
#
|
|
47
|
-
# @example
|
|
48
|
-
# hsh = { :a => 100, :b => 200 }
|
|
49
|
-
# hsh.delete(:a) # => 100
|
|
50
|
-
# hsh.delete(:z) # => nil
|
|
51
|
-
# hsh.delete(:z){|el| "#{el} not found" } # => "z not found"
|
|
52
|
-
#
|
|
53
|
-
# @overload hsh.delete(key) -> val
|
|
54
|
-
# @param key [Object] key to remove
|
|
55
|
-
# @return [Object, Nil] the removed object, nil if missing
|
|
56
|
-
#
|
|
57
|
-
# @overload hsh.delete(key){|key| block } -> val
|
|
58
|
-
# @param key [Object] key to remove
|
|
59
|
-
# @yield [Object] called (with key) if key is missing
|
|
60
|
-
# @yieldparam key
|
|
61
|
-
# @return [Object, Nil] the removed object, or if missing, the return value
|
|
62
|
-
# of the block
|
|
63
|
-
#
|
|
64
|
-
def delete(key, &block)
|
|
65
|
-
if has_key?(key)
|
|
66
|
-
val = self[key]
|
|
67
|
-
self[key] = nil
|
|
68
|
-
self.send(:remove_instance_variable, "@#{key}") if instance_variables.include?("@#{key}")
|
|
69
|
-
val
|
|
70
|
-
elsif block_given?
|
|
71
|
-
block.call(key)
|
|
72
|
-
else
|
|
73
|
-
nil
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
# Hashlike#keys
|
|
78
|
-
#
|
|
79
|
-
# Returns a new array populated with the keys from this hashlike.
|
|
80
|
-
#
|
|
81
|
-
# @see Hashlike#values.
|
|
82
|
-
#
|
|
83
|
-
# @example
|
|
84
|
-
# hsh = { :a => 100, :b => 200, :c => 300, :d => 400 }
|
|
85
|
-
# hsh.keys # => [:a, :b, :c, :d]
|
|
86
|
-
#
|
|
87
|
-
# @return [Array] list of keys
|
|
88
|
-
#
|
|
89
|
-
def keys
|
|
90
|
-
@keys ||= members.map{|key| convert_key(key) }
|
|
91
|
-
end
|
|
92
|
-
|
|
93
|
-
def empty?
|
|
94
|
-
keys.select{|key| not self[key].nil? }.empty?
|
|
95
|
-
end
|
|
96
|
-
|
|
97
|
-
def convert_key(key)
|
|
98
|
-
return key if key.is_a?(Fixnum)
|
|
99
|
-
key.respond_to?(:to_sym) ? key.to_sym : key
|
|
100
|
-
end
|
|
101
|
-
|
|
102
|
-
def size
|
|
103
|
-
length
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
end
|
|
107
|
-
end
|
|
108
|
-
end
|