gorillib 0.5.0 → 0.5.2

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.
Files changed (69) hide show
  1. data/.gitignore +3 -0
  2. data/.gitmodules +3 -0
  3. data/.travis.yml +11 -0
  4. data/Gemfile +2 -16
  5. data/Rakefile +2 -74
  6. data/away/aliasing_spec.rb +180 -0
  7. data/away/confidence.rb +17 -0
  8. data/away/stub_module.rb +33 -0
  9. data/gorillib.gemspec +31 -246
  10. data/lib/gorillib/collection/model_collection.rb +1 -0
  11. data/lib/gorillib/data_munging.rb +0 -1
  12. data/lib/gorillib/hashlike/slice.rb +2 -0
  13. data/lib/gorillib/model/field.rb +2 -2
  14. data/lib/gorillib/model/serialization.rb +9 -4
  15. data/lib/gorillib/model/serialization/csv.rb +1 -0
  16. data/lib/gorillib/model/serialization/lines.rb +2 -0
  17. data/lib/gorillib/model/serialization/tsv.rb +1 -0
  18. data/lib/gorillib/pathname.rb +1 -1
  19. data/lib/gorillib/pathname/utils.rb +6 -0
  20. data/lib/gorillib/string/inflector.rb +1 -1
  21. data/lib/gorillib/system.rb +1 -0
  22. data/lib/gorillib/system/runner.rb +36 -0
  23. data/lib/gorillib/type/ip_address.rb +2 -2
  24. data/lib/gorillib/version.rb +3 -0
  25. data/old/lib/gorillib/hash/indifferent_access.rb +207 -0
  26. data/old/lib/gorillib/hash/tree_merge.rb +4 -0
  27. data/old/lib/gorillib/hashlike/tree_merge.rb +49 -0
  28. data/old/lib/gorillib/metaprogramming/cattr_accessor.rb +79 -0
  29. data/old/lib/gorillib/metaprogramming/mattr_accessor.rb +61 -0
  30. data/old/lib/gorillib/receiver.rb +402 -0
  31. data/old/lib/gorillib/receiver/active_model_shim.rb +32 -0
  32. data/old/lib/gorillib/receiver/acts_as_hash.rb +195 -0
  33. data/old/lib/gorillib/receiver/acts_as_loadable.rb +42 -0
  34. data/old/lib/gorillib/receiver/locale/en.yml +27 -0
  35. data/old/lib/gorillib/receiver/tree_diff.rb +74 -0
  36. data/old/lib/gorillib/receiver/validations.rb +30 -0
  37. data/old/lib/gorillib/receiver_model.rb +21 -0
  38. data/old/lib/gorillib/struct/acts_as_hash.rb +108 -0
  39. data/old/lib/gorillib/struct/hashlike_iteration.rb +0 -0
  40. data/old/spec/gorillib/hash/indifferent_access_spec.rb +391 -0
  41. data/old/spec/gorillib/metaprogramming/cattr_accessor_spec.rb +43 -0
  42. data/old/spec/gorillib/metaprogramming/mattr_accessor_spec.rb +45 -0
  43. data/old/spec/gorillib/receiver/receiver/acts_as_hash_spec.rb +295 -0
  44. data/old/spec/gorillib/receiver_spec.rb +551 -0
  45. data/old/spec/gorillib/struct/acts_as_hash_fuzz_spec.rb +71 -0
  46. data/old/spec/gorillib/struct/acts_as_hash_spec.rb +422 -0
  47. data/spec/gorillib/array/compact_blank_spec.rb +2 -2
  48. data/spec/gorillib/collection_spec.rb +6 -6
  49. data/spec/gorillib/factories_spec.rb +2 -2
  50. data/spec/gorillib/hashlike_spec.rb +2 -1
  51. data/spec/gorillib/model/defaults_spec.rb +3 -3
  52. data/spec/gorillib/model/serialization/csv_spec.rb +35 -0
  53. data/spec/gorillib/model/serialization/tsv_spec.rb +20 -4
  54. data/spec/gorillib/model/serialization_spec.rb +3 -3
  55. data/spec/spec_helper.rb +6 -1
  56. data/spec/support/factory_test_helpers.rb +2 -2
  57. data/spec/support/gorillib_test_helpers.rb +4 -4
  58. data/spec/support/hashlike_fuzzing_helper.rb +1 -15
  59. data/spec/support/hashlike_helper.rb +5 -1
  60. data/spec/support/model_test_helpers.rb +12 -1
  61. metadata +192 -168
  62. data/notes/HOWTO.md +0 -22
  63. data/notes/bucket.md +0 -155
  64. data/notes/builder.md +0 -170
  65. data/notes/collection.md +0 -81
  66. data/notes/factories.md +0 -86
  67. data/notes/model-overlay.md +0 -209
  68. data/notes/model.md +0 -135
  69. data/notes/structured-data-classes.md +0 -127
@@ -0,0 +1,32 @@
1
+ # require 'active_model'
2
+
3
+ require 'active_model/deprecated_error_methods'
4
+ require 'active_model/errors'
5
+ require 'active_model/naming'
6
+ require 'active_model/validator'
7
+ require 'active_model/translation'
8
+ require 'active_model/validations'
9
+ require 'active_support/i18n'
10
+ I18n.load_path << File.join(File.expand_path(File.dirname(__FILE__)), 'locale/en.yml')
11
+
12
+ module Receiver
13
+ module ActiveModelShim
14
+
15
+ def to_model
16
+ self
17
+ end
18
+
19
+ def new_record?() true end
20
+ def destroyed?() false end
21
+ def errors
22
+ @_errors ||= ActiveModel::Errors.new(self)
23
+ end
24
+
25
+ def self.included(base)
26
+ base.class_eval do
27
+ extend ActiveModel::Naming
28
+ include ActiveModel::Validations
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,195 @@
1
+ require 'gorillib/hashlike'
2
+
3
+ module Receiver
4
+ #
5
+ # Makes a Receiver thingie behave mostly like a hash.
6
+ #
7
+ # By default, the hashlike methods iterate over the receiver attributes:
8
+ # instance #keys delegates to self.class.keys which calls
9
+ # receiver_attr_names. If you want to filter our add to the keys list, you
10
+ # can just override the class-level keys method (and call super, or not):
11
+ #
12
+ # def self.keys
13
+ # super + [:firstname, :lastname] - [:fullname]
14
+ # end
15
+ #
16
+ # All methods are defined naturally on [], []= and has_key? -- if you enjoy
17
+ #
18
+ #
19
+ # in addition to the below, by including Enumerable, this also adds
20
+ #
21
+ # :each_cons, :each_entry, :each_slice, :each_with_index, :each_with_object,
22
+ # :map, :collect, :collect_concat, :entries, :to_a, :flat_map, :inject, :reduce,
23
+ # :group_by, :chunk, :cycle, :partition, :reverse_each, :slice_before, :drop,
24
+ # :drop_while, :take, :take_while, :detect, :find, :find_all, :find_index, :grep,
25
+ # :all?, :any?, :none?, :one?, :first, :count, :zip :max, :max_by, :min, :min_by,
26
+ # :minmax, :minmax_by, :sort, :sort_by
27
+ #
28
+ # As opposed to hash, does *not* define
29
+ #
30
+ # default, default=, default_proc, default_proc=, shift, flatten, compare_by_identity
31
+ # compare_by_identity? rehash
32
+ #
33
+ module ActsAsHash
34
+
35
+ module InstanceMethods
36
+
37
+ # Hashlike#[]
38
+ #
39
+ # Element Reference -- Retrieves the value stored for +key+.
40
+ #
41
+ # In a normal hash, a default value can be set; none is provided here.
42
+ #
43
+ # Delegates to self.send(key)
44
+ #
45
+ # @example
46
+ # hsh = { :a => 100, :b => 200 }
47
+ # hsh[:a] # => 100
48
+ # hsh[:c] # => nil
49
+ #
50
+ # @param key [Object] key to retrieve
51
+ # @return [Object] the value stored for key, nil if missing
52
+ #
53
+ def [](key)
54
+ key = convert_key(key)
55
+ self.send(key)
56
+ end
57
+
58
+ # Hashlike#[]=
59
+ # Hashlike#store
60
+ #
61
+ # Element Assignment -- Associates the value given by +val+ with the key
62
+ # given by +key+.
63
+ #
64
+ # key should not have its value changed while it is in use as a key. In a
65
+ # normal hash, a String passed as a key will be duplicated and frozen. No such
66
+ # guarantee is provided here
67
+ #
68
+ # Delegates to self.send("key=", val)
69
+ #
70
+ # @example
71
+ # hsh = { :a => 100, :b => 200 }
72
+ # hsh[:a] = 9
73
+ # hsh[:c] = 4
74
+ # hsh # => { :a => 9, :b => 200, :c => 4 }
75
+ #
76
+ # hsh[key] = val -> val
77
+ # hsh.store(key, val) -> val
78
+ #
79
+ # @param key [Object] key to associate
80
+ # @param val [Object] value to associate it with
81
+ # @return [Object]
82
+ #
83
+ def []=(key, val)
84
+ key = convert_key(key)
85
+ self.send("#{key}=", val)
86
+ end
87
+
88
+ # Hashlike#delete
89
+ #
90
+ # Deletes and returns the value from +hsh+ whose key is equal to +key+. If the
91
+ # optional code block is given and the key is not found, pass in the key and
92
+ # return the result of +block+.
93
+ #
94
+ # In a normal hash, a default value can be set; none is provided here.
95
+ #
96
+ # @example
97
+ # hsh = { :a => 100, :b => 200 }
98
+ # hsh.delete(:a) # => 100
99
+ # hsh.delete(:z) # => nil
100
+ # hsh.delete(:z){|el| "#{el} not found" } # => "z not found"
101
+ #
102
+ # @overload hsh.delete(key) -> val
103
+ # @param key [Object] key to remove
104
+ # @return [Object, Nil] the removed object, nil if missing
105
+ #
106
+ # @overload hsh.delete(key){|key| block } -> val
107
+ # @param key [Object] key to remove
108
+ # @yield [Object] called (with key) if key is missing
109
+ # @yieldparam key
110
+ # @return [Object, Nil] the removed object, or if missing, the return value
111
+ # of the block
112
+ #
113
+ def delete(key, &block)
114
+ key = convert_key(key)
115
+ if has_key?(key)
116
+ val = self[key]
117
+ self.send(:remove_instance_variable, "@#{key}")
118
+ val
119
+ elsif block_given?
120
+ block.call(key)
121
+ else
122
+ nil
123
+ end
124
+ end
125
+
126
+ # Hashlike#keys
127
+ #
128
+ # Returns a new array populated with the keys from this hashlike.
129
+ #
130
+ # @see Hashlike#values.
131
+ #
132
+ # @example
133
+ # hsh = { :a => 100, :b => 200, :c => 300, :d => 400 }
134
+ # hsh.keys # => [:a, :b, :c, :d]
135
+ #
136
+ # @return [Array] list of keys
137
+ #
138
+ def keys
139
+ members & instance_variables.map{|s| convert_key(s[1..-1]) }
140
+ end
141
+
142
+ def members
143
+ self.class.members
144
+ end
145
+
146
+ #
147
+ # Returns a hash with each key set to its associated value.
148
+ #
149
+ # @example
150
+ # my_hshlike = MyHashlike.new
151
+ # my_hshlike[:a] = 100; my_hshlike[:b] = 200
152
+ # my_hshlike.to_hash # => { :a => 100, :b => 200 }
153
+ #
154
+ # @return [Hash] a new Hash instance, with each key set to its associated value.
155
+ #
156
+ def to_hash
157
+ {}.tap do |hsh|
158
+ each_pair do |key, val|
159
+ hsh[key] = val.respond_to?(:to_hash) ? val.to_hash : val
160
+ end
161
+ end
162
+ end
163
+
164
+ end
165
+
166
+ module ClassMethods
167
+ # By default, the hashlike methods iterate over the receiver attributes.
168
+ # If you want to filter our add to the keys list, override this method
169
+ #
170
+ # @example
171
+ # def self.keys
172
+ # super + [:firstname, :lastname] - [:fullname]
173
+ # end
174
+ #
175
+ def keys
176
+ receiver_attr_names
177
+ end
178
+ end
179
+
180
+ protected
181
+
182
+ def convert_key(key)
183
+ raise ArgumentError, "Keys for #{self.class} must be symbols, strings or respond to #to_sym" unless key.respond_to?(:to_sym)
184
+ key.to_sym
185
+ end
186
+
187
+ def self.included base
188
+ base.class_eval do
189
+ include Gorillib::Hashlike
190
+ extend ClassMethods
191
+ include InstanceMethods
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,42 @@
1
+ module Receiver
2
+ #
3
+ # adds methods to load and store from json, yaml or magic
4
+ #
5
+ # This will require 'json' UNLESS you have already included something (so if
6
+ # you want to say require 'yajl' then do that first).
7
+ #
8
+ module ActsAsLoadable
9
+
10
+ module ClassMethods
11
+ def receive_json stream
12
+ receive(JSON.load(stream))
13
+ end
14
+
15
+ def receive_yaml stream
16
+ receive(YAML.load(stream))
17
+ end
18
+
19
+ #
20
+ # The file is loaded with
21
+ # * YAML if the filename ends in .yaml or .yml
22
+ # * JSON otherwise
23
+ #
24
+ def receive_from_file filename
25
+ stream = File.open(filename)
26
+ (filename =~ /.ya?ml$/) ? receive_yaml(stream) : receive_json(stream)
27
+ end
28
+ end
29
+
30
+ def merge_from_file! filename
31
+ other_obj = self.class.receive_from_file(filename)
32
+ tree_merge! other_obj
33
+ end
34
+
35
+ # put all the things in ClassMethods at class level
36
+ def self.included base
37
+ require 'yaml'
38
+ require 'json' unless defined?(JSON)
39
+ base.extend ClassMethods
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,27 @@
1
+ en:
2
+ errors:
3
+ # The default format to use in full error messages.
4
+ format: "%{attribute} %{message}"
5
+
6
+ # The values :model, :attribute and :value are always available for interpolation
7
+ # The value :count is available when applicable. Can be used for pluralization.
8
+ messages:
9
+ inclusion: "is not included in the list"
10
+ exclusion: "is reserved"
11
+ invalid: "is invalid"
12
+ confirmation: "doesn't match confirmation"
13
+ accepted: "must be accepted"
14
+ empty: "can't be empty"
15
+ blank: "can't be blank"
16
+ too_long: "is too long (maximum is %{count} characters)"
17
+ too_short: "is too short (minimum is %{count} characters)"
18
+ wrong_length: "is the wrong length (should be %{count} characters)"
19
+ not_a_number: "is not a number"
20
+ not_an_integer: "must be an integer"
21
+ greater_than: "must be greater than %{count}"
22
+ greater_than_or_equal_to: "must be greater than or equal to %{count}"
23
+ equal_to: "must be equal to %{count}"
24
+ less_than: "must be less than %{count}"
25
+ less_than_or_equal_to: "must be less than or equal to %{count}"
26
+ odd: "must be odd"
27
+ even: "must be even"
@@ -0,0 +1,74 @@
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
@@ -0,0 +1,30 @@
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
@@ -0,0 +1,21 @@
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
@@ -0,0 +1,108 @@
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