gorillib 0.5.0 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
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