gorillib 0.1.11 → 0.4.0pre

Sign up to get free protection for your applications and to get access to all the features.
Files changed (143) hide show
  1. data/.gitignore +1 -0
  2. data/.rspec +1 -2
  3. data/.yardopts +9 -0
  4. data/{CHANGELOG.textile → CHANGELOG.md} +35 -9
  5. data/Gemfile +21 -14
  6. data/Guardfile +19 -0
  7. data/{LICENSE.textile → LICENSE.md} +43 -29
  8. data/README.md +47 -52
  9. data/Rakefile +31 -30
  10. data/TODO.md +32 -0
  11. data/VERSION +1 -1
  12. data/examples/builder/ironfan.rb +133 -0
  13. data/examples/model/simple.rb +17 -0
  14. data/gorillib.gemspec +106 -86
  15. data/lib/alt/kernel/call_stack.rb +56 -0
  16. data/lib/gorillib/array/wrap.rb +53 -0
  17. data/lib/gorillib/base.rb +3 -3
  18. data/lib/gorillib/builder/field.rb +5 -0
  19. data/lib/gorillib/builder.rb +260 -0
  20. data/lib/gorillib/collection/has_collection.rb +31 -0
  21. data/lib/gorillib/collection.rb +129 -0
  22. data/lib/gorillib/configurable.rb +28 -0
  23. data/lib/gorillib/datetime/{flat.rb → to_flat.rb} +0 -0
  24. data/lib/gorillib/exception/confidence.rb +17 -0
  25. data/lib/gorillib/exception/raisers.rb +78 -0
  26. data/lib/gorillib/hash/mash.rb +202 -0
  27. data/lib/gorillib/hashlike/slice.rb +53 -19
  28. data/lib/gorillib/hashlike.rb +5 -3
  29. data/lib/gorillib/io/system_helpers.rb +30 -0
  30. data/lib/gorillib/logger/log.rb +18 -0
  31. data/lib/gorillib/metaprogramming/concern.rb +124 -0
  32. data/lib/gorillib/model/active_model_conversion.rb +68 -0
  33. data/lib/gorillib/model/active_model_naming.rb +87 -0
  34. data/lib/gorillib/model/active_model_shim.rb +33 -0
  35. data/lib/gorillib/model/base.rb +341 -0
  36. data/lib/gorillib/model/defaults.rb +71 -0
  37. data/lib/gorillib/model/errors.rb +14 -0
  38. data/lib/gorillib/model/factories.rb +372 -0
  39. data/lib/gorillib/model/field.rb +146 -0
  40. data/lib/gorillib/model/named_schema.rb +53 -0
  41. data/lib/gorillib/{struct/hashlike_iteration.rb → model/overlay.rb} +0 -0
  42. data/lib/gorillib/model/record_schema.rb +9 -0
  43. data/lib/gorillib/model/serialization.rb +23 -0
  44. data/lib/gorillib/model/validate.rb +22 -0
  45. data/lib/gorillib/model.rb +23 -0
  46. data/lib/gorillib/pathname.rb +78 -0
  47. data/lib/gorillib/{serialization.rb → serialization/to_wire.rb} +0 -0
  48. data/lib/gorillib/some.rb +11 -9
  49. data/lib/gorillib/string/constantize.rb +21 -14
  50. data/lib/gorillib/string/inflections.rb +6 -76
  51. data/lib/gorillib/string/inflector.rb +192 -0
  52. data/lib/gorillib/string/simple_inflector.rb +267 -0
  53. data/lib/gorillib/type/extended.rb +52 -0
  54. data/lib/gorillib/utils/capture_output.rb +28 -0
  55. data/lib/gorillib/utils/console.rb +131 -0
  56. data/lib/gorillib/utils/nuke_constants.rb +9 -0
  57. data/lib/gorillib/utils/stub_module.rb +33 -0
  58. data/spec/examples/builder/ironfan_spec.rb +37 -0
  59. data/spec/extlib/hash_spec.rb +64 -0
  60. data/spec/extlib/mash_spec.rb +312 -0
  61. data/spec/{array → gorillib/array}/compact_blank_spec.rb +2 -2
  62. data/spec/{array → gorillib/array}/extract_options_spec.rb +2 -2
  63. data/spec/gorillib/builder_spec.rb +187 -0
  64. data/spec/gorillib/collection_spec.rb +20 -0
  65. data/spec/gorillib/configurable_spec.rb +62 -0
  66. data/spec/{datetime → gorillib/datetime}/parse_spec.rb +3 -3
  67. data/spec/{datetime/flat_spec.rb → gorillib/datetime/to_flat_spec.rb} +4 -4
  68. data/spec/{enumerable → gorillib/enumerable}/sum_spec.rb +5 -5
  69. data/spec/gorillib/exception/raisers_spec.rb +60 -0
  70. data/spec/{hash → gorillib/hash}/compact_spec.rb +2 -2
  71. data/spec/{hash → gorillib/hash}/deep_compact_spec.rb +3 -3
  72. data/spec/{hash → gorillib/hash}/deep_merge_spec.rb +2 -2
  73. data/spec/{hash → gorillib/hash}/keys_spec.rb +2 -2
  74. data/spec/{hash → gorillib/hash}/reverse_merge_spec.rb +2 -2
  75. data/spec/{hash → gorillib/hash}/slice_spec.rb +2 -2
  76. data/spec/{hash → gorillib/hash}/zip_spec.rb +2 -2
  77. data/spec/{hashlike → gorillib/hashlike}/behave_same_as_hash_spec.rb +6 -3
  78. data/spec/{hashlike → gorillib/hashlike}/deep_hash_spec.rb +2 -2
  79. data/spec/{hashlike → gorillib/hashlike}/hashlike_behavior_spec.rb +32 -30
  80. data/spec/{hashlike → gorillib/hashlike}/hashlike_via_accessors_spec.rb +3 -3
  81. data/spec/{hashlike_spec.rb → gorillib/hashlike_spec.rb} +3 -3
  82. data/spec/{logger → gorillib/logger}/log_spec.rb +2 -2
  83. data/spec/{metaprogramming → gorillib/metaprogramming}/aliasing_spec.rb +3 -3
  84. data/spec/{metaprogramming → gorillib/metaprogramming}/class_attribute_spec.rb +3 -3
  85. data/spec/{metaprogramming → gorillib/metaprogramming}/delegation_spec.rb +3 -3
  86. data/spec/{metaprogramming → gorillib/metaprogramming}/singleton_class_spec.rb +3 -3
  87. data/spec/gorillib/model/record/defaults_spec.rb +108 -0
  88. data/spec/gorillib/model/record/factories_spec.rb +321 -0
  89. data/spec/gorillib/model/record/overlay_spec.rb +46 -0
  90. data/spec/gorillib/model/serialization_spec.rb +48 -0
  91. data/spec/gorillib/model_spec.rb +281 -0
  92. data/spec/{numeric → gorillib/numeric}/clamp_spec.rb +2 -2
  93. data/spec/{object → gorillib/object}/blank_spec.rb +2 -2
  94. data/spec/{object → gorillib/object}/try_dup_spec.rb +2 -2
  95. data/spec/{object → gorillib/object}/try_spec.rb +3 -2
  96. data/spec/gorillib/pathname_spec.rb +114 -0
  97. data/spec/{string → gorillib/string}/constantize_spec.rb +2 -2
  98. data/spec/{string → gorillib/string}/human_spec.rb +2 -2
  99. data/spec/{string → gorillib/string}/inflections_spec.rb +4 -3
  100. data/spec/{string → gorillib/string}/inflector_test_cases.rb +0 -0
  101. data/spec/{string → gorillib/string}/truncate_spec.rb +4 -10
  102. data/spec/gorillib/type/extended_spec.rb +120 -0
  103. data/spec/gorillib/utils/capture_output_spec.rb +71 -0
  104. data/spec/spec_helper.rb +8 -11
  105. data/spec/support/gorillib_test_helpers.rb +66 -0
  106. data/spec/support/hashlike_fuzzing_helper.rb +31 -33
  107. data/spec/support/hashlike_helper.rb +3 -3
  108. data/spec/support/model_test_helpers.rb +81 -0
  109. data/spec/support/shared_examples/included_module.rb +20 -0
  110. metadata +177 -158
  111. data/lib/gorillib/array/average.rb +0 -13
  112. data/lib/gorillib/array/sorted_median.rb +0 -11
  113. data/lib/gorillib/array/sorted_percentile.rb +0 -11
  114. data/lib/gorillib/array/sorted_sample.rb +0 -12
  115. data/lib/gorillib/dsl_object.rb +0 -64
  116. data/lib/gorillib/hash/indifferent_access.rb +0 -207
  117. data/lib/gorillib/hash/tree_merge.rb +0 -4
  118. data/lib/gorillib/hashlike/tree_merge.rb +0 -49
  119. data/lib/gorillib/metaprogramming/cattr_accessor.rb +0 -79
  120. data/lib/gorillib/metaprogramming/mattr_accessor.rb +0 -61
  121. data/lib/gorillib/receiver/active_model_shim.rb +0 -32
  122. data/lib/gorillib/receiver/acts_as_hash.rb +0 -195
  123. data/lib/gorillib/receiver/acts_as_loadable.rb +0 -42
  124. data/lib/gorillib/receiver/locale/en.yml +0 -27
  125. data/lib/gorillib/receiver/tree_diff.rb +0 -74
  126. data/lib/gorillib/receiver/validations.rb +0 -30
  127. data/lib/gorillib/receiver.rb +0 -402
  128. data/lib/gorillib/receiver_model.rb +0 -21
  129. data/lib/gorillib/struct/acts_as_hash.rb +0 -108
  130. data/notes/fancy_hashes_and_receivers.textile +0 -120
  131. data/notes/hash_rdocs.textile +0 -97
  132. data/spec/array/average_spec.rb +0 -24
  133. data/spec/array/sorted_median_spec.rb +0 -18
  134. data/spec/array/sorted_percentile_spec.rb +0 -24
  135. data/spec/array/sorted_sample_spec.rb +0 -28
  136. data/spec/dsl_object_spec.rb +0 -99
  137. data/spec/hash/indifferent_access_spec.rb +0 -391
  138. data/spec/metaprogramming/cattr_accessor_spec.rb +0 -43
  139. data/spec/metaprogramming/mattr_accessor_spec.rb +0 -45
  140. data/spec/receiver/acts_as_hash_spec.rb +0 -295
  141. data/spec/receiver_spec.rb +0 -551
  142. data/spec/struct/acts_as_hash_fuzz_spec.rb +0 -71
  143. data/spec/struct/acts_as_hash_spec.rb +0 -422
@@ -0,0 +1,202 @@
1
+ class Hash
2
+
3
+ # Convert to Mash. This class has semantics of ActiveSupport's
4
+ # HashWithIndifferentAccess and we only have it so that people can write
5
+ # params[:key] instead of params['key'].
6
+ #
7
+ # @return [Mash] This hash as a Mash for string or symbol key access.
8
+ def to_mash
9
+ hash = Mash.new(self)
10
+ hash.default = default
11
+ hash
12
+ end
13
+
14
+ ##
15
+ # Create a hash with *only* key/value pairs in receiver and +allowed+
16
+ #
17
+ # { :one => 1, :two => 2, :three => 3 }.only(:one) #=> { :one => 1 }
18
+ #
19
+ # @param allowed [Array[String, Symbol]] The hash keys to include.
20
+ #
21
+ # @return [Hash] A new hash with only the selected keys.
22
+ #
23
+ # @api public
24
+ def only(*allowed)
25
+ hash = {}
26
+ allowed.each {|k| hash[k] = self[k] if self.has_key?(k) }
27
+ hash
28
+ end
29
+
30
+ ##
31
+ # Create a hash with all key/value pairs in receiver *except* +rejected+
32
+ #
33
+ # { :one => 1, :two => 2, :three => 3 }.except(:one)
34
+ # #=> { :two => 2, :three => 3 }
35
+ #
36
+ # @param rejected [Array[String, Symbol]] The hash keys to exclude.
37
+ #
38
+ # @return [Hash] A new hash without the selected keys.
39
+ #
40
+ # @api public
41
+ def except(*rejected)
42
+ hash = self.dup
43
+ rejected.each {|k| hash.delete(k) }
44
+ hash
45
+ end
46
+ end
47
+
48
+ # This class has dubious semantics and we only have it so that people can write
49
+ # params[:key] instead of params['key'].
50
+ class Mash < Hash
51
+
52
+ # @param constructor [Object]
53
+ # The default value for the mash. Defaults to an empty hash.
54
+ #
55
+ # @overload [Alternatives]
56
+ # If constructor is a Hash, a new mash will be created based on the keys of
57
+ # the hash and no default value will be set.
58
+ def initialize(constructor = {})
59
+ if constructor.is_a?(Hash)
60
+ super()
61
+ update(constructor)
62
+ else
63
+ super(constructor)
64
+ end
65
+ end
66
+
67
+ # @param key<Object> The default value for the mash. Defaults to nil.
68
+ #
69
+ # @overload [Alternatives]
70
+ # If key is a Symbol and it is a key in the mash, then the default value will
71
+ # be set to the value matching the key.
72
+ def default(key = nil)
73
+ if key.is_a?(Symbol) && include?(key = key.to_s)
74
+ self[key]
75
+ else
76
+ super
77
+ end
78
+ end
79
+
80
+ alias_method :regular_writer, :[]= unless method_defined?(:regular_writer)
81
+ alias_method :regular_update, :update unless method_defined?(:regular_update)
82
+
83
+ # @param key<Object> The key to set.
84
+ # @param value<Object>
85
+ # The value to set the key to.
86
+ #
87
+ # @see Mash#convert_key
88
+ # @see Mash#convert_value
89
+ def []=(key, value)
90
+ regular_writer(convert_key(key), convert_value(value))
91
+ end
92
+
93
+ # @param other_hash<Hash>
94
+ # A hash to update values in the mash with. The keys and the values will be
95
+ # converted to Mash format.
96
+ #
97
+ # @return [Mash] The updated mash.
98
+ def update(other_hash)
99
+ other_hash.each_pair { |key, value| regular_writer(convert_key(key), convert_value(value)) }
100
+ self
101
+ end
102
+
103
+ alias_method :merge!, :update
104
+
105
+ # @param key<Object> The key to check for. This will be run through convert_key.
106
+ #
107
+ # @return [Boolean] True if the key exists in the mash.
108
+ def key?(key)
109
+ super(convert_key(key))
110
+ end
111
+
112
+ # def include? def has_key? def member?
113
+ alias_method :include?, :key?
114
+ alias_method :has_key?, :key?
115
+ alias_method :member?, :key?
116
+
117
+ # @param <Object> key The key to fetch. This will be run through convert_key.
118
+ # @param <Array> extras Default value.
119
+ #
120
+ # @return [Object] The value at key or the default value.
121
+ def fetch(key, *extras)
122
+ super(convert_key(key), *extras)
123
+ end
124
+
125
+ # @param indices [Array]
126
+ # The keys to retrieve values for. These will be run through +convert_key+.
127
+ #
128
+ # @return [Array] The values at each of the provided keys
129
+ def values_at(*indices)
130
+ indices.collect {|key| self[convert_key(key)]}
131
+ end
132
+
133
+ # @param hash<Hash> The hash to merge with the mash.
134
+ #
135
+ # @return [Mash] A new mash with the hash values merged in.
136
+ def merge(hash)
137
+ self.dup.update(hash)
138
+ end
139
+
140
+ # @param key<Object>
141
+ # The key to delete from the mash.\
142
+ def delete(key)
143
+ super(convert_key(key))
144
+ end
145
+
146
+ # @param [Array[(String, Symbol)]] rejected The mash keys to exclude.
147
+ #
148
+ # @return [Mash] A new mash without the selected keys.
149
+ #
150
+ # @example
151
+ # { :one => 1, :two => 2, :three => 3 }.except(:one)
152
+ # #=> { "two" => 2, "three" => 3 }
153
+ def except(*rejected)
154
+ super(*rejected.map {|k| convert_key(k)})
155
+ end
156
+
157
+ # Used to provide the same interface as Hash.
158
+ #
159
+ # @return [Mash] This mash unchanged.
160
+ def stringify_keys!; self end
161
+
162
+ # @return [Hash] The mash as a Hash with symbolized keys.
163
+ def symbolize_keys
164
+ h = Hash.new(default)
165
+ each { |key, val| h[key.to_sym] = val }
166
+ h
167
+ end
168
+
169
+ # @return [Hash] The mash as a Hash with string keys.
170
+ def to_hash
171
+ Hash.new(default).merge(self)
172
+ end
173
+
174
+ protected
175
+ # @param key [Object] The key to convert.
176
+ #
177
+ # @return [Object]
178
+ # The converted key. If the key was a symbol, it will be converted to a
179
+ # string.
180
+ #
181
+ # @api private
182
+ def convert_key(key)
183
+ key.kind_of?(Symbol) ? key.to_s : key
184
+ end
185
+
186
+ # @param value [Object] The value to convert.
187
+ #
188
+ # @return [Object]
189
+ # The converted value. A Hash or an Array of hashes, will be converted to
190
+ # their Mash equivalents.
191
+ #
192
+ # @api private
193
+ def convert_value(value)
194
+ if value.class == Hash
195
+ value.to_mash
196
+ elsif value.is_a?(Array)
197
+ value.collect { |e| convert_value(e) }
198
+ else
199
+ value
200
+ end
201
+ end
202
+ end
@@ -1,13 +1,14 @@
1
1
  module Gorillib
2
2
  module Hashlike
3
3
  module Slice
4
- # Slice a hash to include only the given allowed_keys. This is useful for
5
- # limiting an options hash to valid keys before passing to a method:
4
+ # Slice a hash to include only the given allowed_keys.
6
5
  #
6
+ # @return the sliced hash
7
+ #
8
+ # @example limit an options hash to valid keys before passing to a method:
7
9
  # def search(criteria = {})
8
10
  # assert_valid_keys(:mass, :velocity, :time)
9
11
  # end
10
- #
11
12
  # search(options.slice(:mass, :velocity, :time))
12
13
  #
13
14
  # If you have an array of keys you want to limit to, you should splat them:
@@ -21,8 +22,10 @@ module Gorillib
21
22
  hash
22
23
  end unless method_defined?(:slice)
23
24
 
24
- # Replaces the hash with only the given allowed_keys.
25
- # Returns a hash containing the removed key/value pairs
25
+ # Replace the hash with only the given allowed_keys.
26
+ #
27
+ # @return a hash containing the removed key/value pairs
28
+ #
26
29
  # @example
27
30
  # hsh = {:a => 1, :b => 2, :c => 3, :d => 4}
28
31
  # hsh.slice!(:a, :b)
@@ -37,21 +40,9 @@ module Gorillib
37
40
  omit
38
41
  end unless method_defined?(:slice!)
39
42
 
40
- # This also works, and doesn't require #replace method, but is uglier and
41
- # wasn't written by Railsians. I'm not sure that slice! is interesting if
42
- # you're a duck-typed Hash but not is_a?(Hash), so we'll just leave it at the
43
- # active_record implementation.
44
- #
45
- # def slice!(*allowed_keys)
46
- # allowed_keys = allowed_keys.map!{|key| convert_key(key) } if respond_to?(:convert_key)
47
- # omit_keys = self.keys - allowed_keys
48
- # omit = slice(*omit_keys)
49
- # omit_keys.each{|k| delete(k) }
50
- # omit
51
- # end
52
-
53
43
  # Removes the given allowed_keys from the hash
54
- # Returns a hash containing the removed key/value pairs
44
+ #
45
+ # @return a hash containing the removed key/value pairs
55
46
  #
56
47
  # @example
57
48
  # hsh = {:a => 1, :b => 2, :c => 3, :d => 4}
@@ -62,6 +53,49 @@ module Gorillib
62
53
  def extract!(*allowed_keys)
63
54
  slice!(*self.keys - allowed_keys)
64
55
  end unless method_defined?(:extract!)
56
+
57
+ # Return a hash that includes everything but the given keys.
58
+ #
59
+ # @example Exclude a blacklist of parameters
60
+ # @person.update_attributes(params[:person].except(:admin))
61
+ #
62
+ # If the receiver responds to +convert_key+, the method is called on each of the
63
+ # arguments. This allows +except+ to play nice with hashes with indifferent access
64
+ # for instance:
65
+ #
66
+ # @example mash, hash, it does the right thing:
67
+ # {:a => 1}.to_mash.except(:a) # => {}
68
+ # {:a => 1}.to_mash.except('a') # => {}
69
+ #
70
+ def except(*keys)
71
+ dup.except!(*keys)
72
+ end
73
+
74
+ # Replaces the hash without the given keys.
75
+ def except!(*keys)
76
+ keys.each{|key| delete(key) }
77
+ self
78
+ end
79
+
80
+ def only(*allowed)
81
+ dup.only!(*allowed)
82
+ end
83
+
84
+ # Create a hash with *only* key/value pairs in receiver and +allowed+
85
+ #
86
+ # @example Limit a set of parameters to everything but a few known toggles:
87
+ # { :one => 1, :two => 2, :three => 3 }.only(:one) #=> { :one => 1 }
88
+ #
89
+ # @param [Array[String, Symbol]] allowed The hash keys to include.
90
+ #
91
+ # @return [Hash] A new hash with only the selected keys.
92
+ #
93
+ # @api public
94
+ def only!(*allowed)
95
+ allowed = allowed.to_set
96
+ select!{|key, val| allowed.include?(key) }
97
+ end
98
+
65
99
  end
66
100
  end
67
101
  end
@@ -199,7 +199,7 @@ module Gorillib
199
199
  # hsh.values_at(:c, :a, :c, :z, :a)
200
200
  # # => [300, 100, 300, nil, 100]
201
201
  #
202
- # @param *allowed_keys [Object] the keys to retrieve.
202
+ # @param allowed_keys [Object] the keys to retrieve.
203
203
  # @return [Array] the values, in order according to allowed_keys.
204
204
  #
205
205
  def values_at(*allowed_keys)
@@ -304,7 +304,7 @@ module Gorillib
304
304
  # hsh.values_of(:c, :a, :c, :z, :a)
305
305
  # # => [300, 100, 300, nil, 100]
306
306
  #
307
- # @param *allowed_keys [Object] the keys to retrieve.
307
+ # @param allowed_keys [Object] the keys to retrieve.
308
308
  # @return [Array] the values, in order according to allowed_keys.
309
309
  #
310
310
  def values_of(*allowed_keys)
@@ -794,7 +794,9 @@ module Gorillib
794
794
  # hsh.flatten(nil)
795
795
  # # => [1, 2, 3, 4, 1, 2, 3, 4, 5, 6]
796
796
  #
797
- # @param level [Integer] the level of recursion to flatten, 0 by default.
797
+ # @overload flatten
798
+ # @overload flatten(level)
799
+ # @param level [Integer] the level of recursion to flatten, 0 by default.
798
800
  # @return [Array] the flattened key-value array.
799
801
  #
800
802
  def flatten(*args)
@@ -0,0 +1,30 @@
1
+ module Gorillib::CheckedPopen
2
+ module_function
3
+
4
+ def checked_popen(command, mode, fail_action, io_class=IO)
5
+ check_child_exit_status do
6
+ io_class.popen(command, mode) do |process|
7
+ yield(process)
8
+ end
9
+ end
10
+ rescue Errno::EPIPE
11
+ fail_action.call
12
+ end
13
+
14
+ # @private
15
+ NO_EXIT_STATUS = OpenStruct.new(:exitstatus => 0)
16
+
17
+ def check_child_exit_status
18
+ result = yield
19
+ status = $? || NO_EXIT_STATUS
20
+ unless [0, 172].include?(status.exitstatus)
21
+ raise ArgumentError, "Command exited with status '#{status.exitstatus}'"
22
+ end
23
+ result
24
+ end
25
+
26
+ end
27
+
28
+ ::IO.class_eval do
29
+ include Gorillib::CheckedPopen
30
+ end
@@ -7,6 +7,24 @@ require 'logger'
7
7
  #
8
8
  ::Log = Logger.new($stderr) unless defined?(::Log)
9
9
 
10
+ # unless defined?(Log)
11
+ # require 'log4r'
12
+ # Log = Log4r::Logger.new('wukong')
13
+ # Log.outputters = Log4r::Outputter.stderr
14
+ # # require 'logger'
15
+ # # Log = Logger.new(STDERR)
16
+ # end
17
+
18
+ # require 'log_buddy'; LogBuddy.init :log_to_stdout => false, :logger => Log
19
+ # LogBuddy::Utils.module_eval do
20
+ # def arg_and_blk_debug(arg, blk)
21
+ # result = eval(arg, blk.binding)
22
+ # result_str = obj_to_string(result, :quote_strings => true)
23
+ # LogBuddy.debug(%[#{arg} = #{result_str}])
24
+ # end
25
+ # end
26
+
27
+
10
28
  def Log.dump *args
11
29
  self.debug([
12
30
  args.map(&:inspect),
@@ -0,0 +1,124 @@
1
+ module Gorillib
2
+ # A typical module looks like this:
3
+ #
4
+ # module M
5
+ # def self.included(base)
6
+ # base.extend ClassMethods
7
+ # scope :disabled, where(:disabled => true)
8
+ # end
9
+ #
10
+ # module ClassMethods
11
+ # ...
12
+ # end
13
+ # end
14
+ #
15
+ # By using <tt>Gorillib::Concern</tt> the above module could instead be written as:
16
+ #
17
+ # require 'active_support/concern'
18
+ #
19
+ # module M
20
+ # extend Gorillib::Concern
21
+ #
22
+ # included do
23
+ # scope :disabled, where(:disabled => true)
24
+ # end
25
+ #
26
+ # module ClassMethods
27
+ # ...
28
+ # end
29
+ # end
30
+ #
31
+ # Moreover, it gracefully handles module dependencies. Given a +Foo+ module and a +Bar+
32
+ # module which depends on the former, we would typically write the following:
33
+ #
34
+ # module Foo
35
+ # def self.included(base)
36
+ # base.class_eval do
37
+ # def self.method_injected_by_foo
38
+ # ...
39
+ # end
40
+ # end
41
+ # end
42
+ # end
43
+ #
44
+ # module Bar
45
+ # def self.included(base)
46
+ # base.method_injected_by_foo
47
+ # end
48
+ # end
49
+ #
50
+ # class Host
51
+ # include Foo # We need to include this dependency for Bar
52
+ # include Bar # Bar is the module that Host really needs
53
+ # end
54
+ #
55
+ # But why should +Host+ care about +Bar+'s dependencies, namely +Foo+? We could try to hide
56
+ # these from +Host+ directly including +Foo+ in +Bar+:
57
+ #
58
+ # module Bar
59
+ # include Foo
60
+ # def self.included(base)
61
+ # base.method_injected_by_foo
62
+ # end
63
+ # end
64
+ #
65
+ # class Host
66
+ # include Bar
67
+ # end
68
+ #
69
+ # Unfortunately this won't work, since when +Foo+ is included, its <tt>base</tt> is the +Bar+ module,
70
+ # not the +Host+ class. With <tt>Gorillib::Concern</tt>, module dependencies are properly resolved:
71
+ #
72
+ # require 'active_support/concern'
73
+ #
74
+ # module Foo
75
+ # extend Gorillib::Concern
76
+ # included do
77
+ # class_eval do
78
+ # def self.method_injected_by_foo
79
+ # ...
80
+ # end
81
+ # end
82
+ # end
83
+ # end
84
+ #
85
+ # module Bar
86
+ # extend Gorillib::Concern
87
+ # include Foo
88
+ #
89
+ # included do
90
+ # self.method_injected_by_foo
91
+ # end
92
+ # end
93
+ #
94
+ # class Host
95
+ # include Bar # works, Bar takes care now of its dependencies
96
+ # end
97
+ #
98
+ module Concern
99
+ def self.extended(base)
100
+ base.instance_variable_set("@_dependencies", [])
101
+ end
102
+
103
+ def append_features(base)
104
+ if base.instance_variable_defined?("@_dependencies")
105
+ base.instance_variable_get("@_dependencies") << self
106
+ return false
107
+ else
108
+ return false if base < self
109
+ @_dependencies.each { |dep| base.send(:include, dep) }
110
+ super
111
+ base.extend const_get("ClassMethods") if const_defined?("ClassMethods")
112
+ base.class_eval(&@_included_block) if instance_variable_defined?("@_included_block")
113
+ end
114
+ end
115
+
116
+ def included(base = nil, &block)
117
+ if base.nil?
118
+ @_included_block = block
119
+ else
120
+ super
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,68 @@
1
+ module Gorillib::Model
2
+ # == Active Model Conversions
3
+ #
4
+ # Handles default conversions: to_model, to_key, to_param, and to_partial_path.
5
+ #
6
+ # Let's take for example this non-persisted object.
7
+ #
8
+ # class ContactMessage
9
+ # include ActiveModel::Conversion
10
+ #
11
+ # # ContactMessage are never persisted in the DB
12
+ # def persisted?
13
+ # false
14
+ # end
15
+ # end
16
+ #
17
+ # cm = ContactMessage.new
18
+ # cm.to_model == self # => true
19
+ # cm.to_key # => nil
20
+ # cm.to_param # => nil
21
+ # cm.to_path # => "contact_messages/contact_message"
22
+ #
23
+ module Conversion
24
+ extend Gorillib::Concern
25
+
26
+ # If your object is already designed to implement all of the Active Model
27
+ # you can use the default <tt>:to_model</tt> implementation, which simply
28
+ # returns self.
29
+ #
30
+ # If your model does not act like an Active Model object, then you should
31
+ # define <tt>:to_model</tt> yourself returning a proxy object that wraps
32
+ # your object with Active Model compliant methods.
33
+ def to_model
34
+ self
35
+ end
36
+
37
+ # Returns an Enumerable of all key attributes if any is set, regardless
38
+ # if the object is persisted or not.
39
+ #
40
+ # Note the default implementation uses persisted? just because all objects
41
+ # in Ruby 1.8.x responds to <tt>:id</tt>.
42
+ def to_key
43
+ persisted? ? [id] : nil
44
+ end
45
+
46
+ # Returns a string representing the object's key suitable for use in URLs,
47
+ # or nil if <tt>persisted?</tt> is false.
48
+ def to_param
49
+ persisted? ? to_key.join('-') : nil
50
+ end
51
+
52
+ # Returns a string identifying the path associated with the object.
53
+ # ActionPack uses this to find a suitable partial to represent the object.
54
+ def to_partial_path
55
+ self.class._to_partial_path
56
+ end
57
+
58
+ module ClassMethods #:nodoc:
59
+ # Provide a class level cache for the to_path. This is an
60
+ # internal method and should not be accessed directly.
61
+ def _to_partial_path #:nodoc:
62
+ @_to_partial_path ||= begin
63
+ "#{model_name.collection}/#{model_name.element}".freeze
64
+ end
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,87 @@
1
+ module Gorillib::Model
2
+ class Name < String
3
+ attr_accessor :namespace
4
+ attr_reader :singular, :element, :collection, :partial_path, :param_key, :i18n_key
5
+
6
+ alias_method :cache_key, :collection
7
+
8
+ class_attribute :inflector
9
+ self.inflector ||= Gorillib::String::Inflector
10
+
11
+ def initialize(klass, namespace = nil, name = nil)
12
+ name ||= klass.name
13
+ raise ArgumentError, "Class name cannot be blank. You need to supply a name argument when anonymous class given" if name.blank?
14
+
15
+ super(name)
16
+
17
+ @klass = klass
18
+ @singular = _singularize(self).freeze
19
+ @element = inflector.underscore(inflector.demodulize(self)).freeze
20
+ @human = inflector.humanize(@element).freeze
21
+ @i18n_key = inflector.underscore(self).to_sym
22
+ #
23
+ self.namespace = namespace
24
+ self.plural = inflector.pluralize(@singular)
25
+ end
26
+
27
+ def plural=(str)
28
+ @plural = str.dup.freeze
29
+ @collection = inflector.underscore(@plural).freeze
30
+ @partial_path = "#{@collection}/#{@element}".freeze
31
+ str
32
+ end
33
+
34
+ def namespace=(ns)
35
+ if ns.present?
36
+ @unnamespaced = self.sub(/^#{ns.name}::/, '')
37
+ @param_key = _singularize(@unnamespaced).freeze
38
+ else
39
+ @unnamespaced = nil
40
+ @param_key = @singular.freeze
41
+ end
42
+ end
43
+
44
+ def singular_route_key
45
+ inflector.singularize(route_key).freeze
46
+ end
47
+
48
+ def route_key
49
+ rk = (namespace ? inflector.pluralize(param_key) : plural.dup)
50
+ rk << "_index" if plural == singular
51
+ rk.freeze
52
+ end
53
+
54
+ private
55
+
56
+ def _singularize(string, replacement='_')
57
+ inflector.underscore(string).tr('/', replacement)
58
+ end
59
+ end
60
+
61
+ # == Active Model Naming
62
+ #
63
+ # Creates a +model_name+ method on your object.
64
+ #
65
+ # To implement, just extend ActiveModel::Naming in your object:
66
+ #
67
+ # class BookCover
68
+ # extend ActiveModel::Naming
69
+ # end
70
+ #
71
+ # BookCover.model_name # => "BookCover"
72
+ # BookCover.model_name.human # => "Book cover"
73
+ #
74
+ # BookCover.model_name.i18n_key # => :book_cover
75
+ # BookModule::BookCover.model_name.i18n_key # => :"book_module/book_cover"
76
+ #
77
+ # Providing the functionality that ActiveModel::Naming provides in your object
78
+ # is required to pass the Active Model Lint test. So either extending the provided
79
+ # method below, or rolling your own is required.
80
+ module Naming
81
+ # Returns a Name object for module, which can be used to retrieve all kinds
82
+ # of naming-related information.
83
+ def model_name
84
+ @_model_name ||= Gorillib::Model::Name.new(self, namespace)
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,33 @@
1
+ require "active_model"
2
+
3
+ module Gorillib
4
+ module Model
5
+
6
+ # Provides the minimum functionality to pass the ActiveModel lint tests
7
+ #
8
+ # @example Usage
9
+ # class Person
10
+ # include Gorillib::Model::ActiveModelShim
11
+ # end
12
+ #
13
+ module ActiveModelShim
14
+ extend Gorillib::Concern
15
+ extend ActiveModel::Naming
16
+ include Gorillib::Model::Conversion
17
+ include ActiveModel::Validations
18
+
19
+ # @return [false]
20
+ def persisted?
21
+ false
22
+ end
23
+
24
+ def attribute_method?(attr_name)
25
+ self.class.has_field?(attr_name)
26
+ end
27
+
28
+ module ClassMethods
29
+ end # ActiveModelShim::ClassMethods
30
+ end # ActiveModelShim
31
+
32
+ end
33
+ end