activeobject 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (80) hide show
  1. data/CHANGE +10 -0
  2. data/Interface_desc +21 -0
  3. data/MIT-LICENSE +20 -0
  4. data/README +72 -0
  5. data/Rakefile.rb +9 -0
  6. data/active-object.gemspec +50 -0
  7. data/examples/account.rb +69 -0
  8. data/examples/data.tch +0 -0
  9. data/examples/light_cloud.yml +18 -0
  10. data/examples/test.rb +3 -0
  11. data/examples/user.rb +112 -0
  12. data/init.rb +4 -0
  13. data/lib/active-object.rb +23 -0
  14. data/lib/active_object/adapters/light_cloud.rb +40 -0
  15. data/lib/active_object/adapters/tokyo_cabinet.rb +48 -0
  16. data/lib/active_object/adapters/tokyo_tyrant.rb +14 -0
  17. data/lib/active_object/associations.rb +200 -0
  18. data/lib/active_object/base.rb +415 -0
  19. data/lib/active_object/callbacks.rb +180 -0
  20. data/lib/active_object/observer.rb +180 -0
  21. data/lib/active_object/serialization.rb +99 -0
  22. data/lib/active_object/serializers/json_serializer.rb +75 -0
  23. data/lib/active_object/serializers/xml_serializer.rb +325 -0
  24. data/lib/active_object/validations.rb +687 -0
  25. data/lib/active_support/callbacks.rb +303 -0
  26. data/lib/active_support/core_ext/array/access.rb +53 -0
  27. data/lib/active_support/core_ext/array/conversions.rb +183 -0
  28. data/lib/active_support/core_ext/array/extract_options.rb +20 -0
  29. data/lib/active_support/core_ext/array/grouping.rb +106 -0
  30. data/lib/active_support/core_ext/array/random_access.rb +12 -0
  31. data/lib/active_support/core_ext/array.rb +13 -0
  32. data/lib/active_support/core_ext/blank.rb +58 -0
  33. data/lib/active_support/core_ext/class/attribute_accessors.rb +54 -0
  34. data/lib/active_support/core_ext/class/inheritable_attributes.rb +140 -0
  35. data/lib/active_support/core_ext/class/removal.rb +50 -0
  36. data/lib/active_support/core_ext/class.rb +3 -0
  37. data/lib/active_support/core_ext/duplicable.rb +43 -0
  38. data/lib/active_support/core_ext/enumerable.rb +72 -0
  39. data/lib/active_support/core_ext/hash/conversions.rb +259 -0
  40. data/lib/active_support/core_ext/hash/keys.rb +52 -0
  41. data/lib/active_support/core_ext/hash.rb +8 -0
  42. data/lib/active_support/core_ext/module/aliasing.rb +74 -0
  43. data/lib/active_support/core_ext/module/attr_accessor_with_default.rb +31 -0
  44. data/lib/active_support/core_ext/module/attribute_accessors.rb +58 -0
  45. data/lib/active_support/core_ext/module.rb +16 -0
  46. data/lib/active_support/core_ext/object/conversions.rb +14 -0
  47. data/lib/active_support/core_ext/object/extending.rb +80 -0
  48. data/lib/active_support/core_ext/object/instance_variables.rb +74 -0
  49. data/lib/active_support/core_ext/object/metaclass.rb +13 -0
  50. data/lib/active_support/core_ext/object/misc.rb +43 -0
  51. data/lib/active_support/core_ext/object.rb +5 -0
  52. data/lib/active_support/core_ext/string/inflections.rb +167 -0
  53. data/lib/active_support/core_ext/string.rb +7 -0
  54. data/lib/active_support/core_ext.rb +4 -0
  55. data/lib/active_support/inflections.rb +55 -0
  56. data/lib/active_support/inflector.rb +348 -0
  57. data/lib/active_support/vendor/builder-2.1.2/blankslate.rb +113 -0
  58. data/lib/active_support/vendor/builder-2.1.2/builder/blankslate.rb +20 -0
  59. data/lib/active_support/vendor/builder-2.1.2/builder/css.rb +250 -0
  60. data/lib/active_support/vendor/builder-2.1.2/builder/xchar.rb +115 -0
  61. data/lib/active_support/vendor/builder-2.1.2/builder/xmlbase.rb +139 -0
  62. data/lib/active_support/vendor/builder-2.1.2/builder/xmlevents.rb +63 -0
  63. data/lib/active_support/vendor/builder-2.1.2/builder/xmlmarkup.rb +328 -0
  64. data/lib/active_support/vendor/builder-2.1.2/builder.rb +13 -0
  65. data/lib/active_support/vendor/xml-simple-1.0.11/xmlsimple.rb +1021 -0
  66. data/lib/active_support/vendor.rb +14 -0
  67. data/lib/active_support.rb +6 -0
  68. data/spec/case/association_test.rb +97 -0
  69. data/spec/case/base_test.rb +74 -0
  70. data/spec/case/callbacks_observers_test.rb +38 -0
  71. data/spec/case/callbacks_test.rb +424 -0
  72. data/spec/case/serialization_test.rb +87 -0
  73. data/spec/case/validations_test.rb +1482 -0
  74. data/spec/data.tch +0 -0
  75. data/spec/helper.rb +15 -0
  76. data/spec/light_cloud.yml +18 -0
  77. data/spec/model/account.rb +4 -0
  78. data/spec/model/topic.rb +26 -0
  79. data/spec/model/user.rb +8 -0
  80. metadata +173 -0
@@ -0,0 +1,303 @@
1
+ module ActiveSupport
2
+ # Callbacks are hooks into the lifecycle of an object that allow you to trigger logic
3
+ # before or after an alteration of the object state.
4
+ #
5
+ # Mixing in this module allows you to define callbacks in your class.
6
+ #
7
+ # Example:
8
+ # class Storage
9
+ # include ActiveSupport::Callbacks
10
+ #
11
+ # define_callbacks :before_save, :after_save
12
+ # end
13
+ #
14
+ # class ConfigStorage < Storage
15
+ # before_save :saving_message
16
+ # def saving_message
17
+ # puts "saving..."
18
+ # end
19
+ #
20
+ # after_save do |object|
21
+ # puts "saved"
22
+ # end
23
+ #
24
+ # def save
25
+ # run_callbacks(:before_save)
26
+ # puts "- save"
27
+ # run_callbacks(:after_save)
28
+ # end
29
+ # end
30
+ #
31
+ # config = ConfigStorage.new
32
+ # config.save
33
+ #
34
+ # Output:
35
+ # saving...
36
+ # - save
37
+ # saved
38
+ #
39
+ # Callbacks from parent classes are inherited.
40
+ #
41
+ # Example:
42
+ # class Storage
43
+ # include ActiveSupport::Callbacks
44
+ #
45
+ # define_callbacks :before_save, :after_save
46
+ #
47
+ # before_save :prepare
48
+ # def prepare
49
+ # puts "preparing save"
50
+ # end
51
+ # end
52
+ #
53
+ # class ConfigStorage < Storage
54
+ # before_save :saving_message
55
+ # def saving_message
56
+ # puts "saving..."
57
+ # end
58
+ #
59
+ # after_save do |object|
60
+ # puts "saved"
61
+ # end
62
+ #
63
+ # def save
64
+ # run_callbacks(:before_save)
65
+ # puts "- save"
66
+ # run_callbacks(:after_save)
67
+ # end
68
+ # end
69
+ #
70
+ # config = ConfigStorage.new
71
+ # config.save
72
+ #
73
+ # Output:
74
+ # preparing save
75
+ # saving...
76
+ # - save
77
+ # saved
78
+ module Callbacks
79
+ class CallbackChain < Array
80
+ def self.build(kind, *methods, &block)
81
+ methods, options = extract_options(*methods, &block)
82
+ methods.map! { |method| Callback.new(kind, method, options) }
83
+ new(methods)
84
+ end
85
+
86
+ def run(object, options = {}, &terminator)
87
+ enumerator = options[:enumerator] || :each
88
+
89
+ unless block_given?
90
+ send(enumerator) { |callback| callback.call(object) }
91
+ else
92
+ send(enumerator) do |callback|
93
+ result = callback.call(object)
94
+ break result if terminator.call(result, object)
95
+ end
96
+ end
97
+ end
98
+
99
+ # TODO: Decompose into more Array like behavior
100
+ def replace_or_append!(chain)
101
+ if index = index(chain)
102
+ self[index] = chain
103
+ else
104
+ self << chain
105
+ end
106
+ self
107
+ end
108
+
109
+ def find(callback, &block)
110
+ select { |c| c == callback && (!block_given? || yield(c)) }.first
111
+ end
112
+
113
+ def delete(callback)
114
+ super(callback.is_a?(Callback) ? callback : find(callback))
115
+ end
116
+
117
+ private
118
+ def self.extract_options(*methods, &block)
119
+ methods.flatten!
120
+ options = methods.extract_options!
121
+ methods << block if block_given?
122
+ return methods, options
123
+ end
124
+
125
+ def extract_options(*methods, &block)
126
+ self.class.extract_options(*methods, &block)
127
+ end
128
+ end
129
+
130
+ class Callback
131
+ attr_reader :kind, :method, :identifier, :options
132
+
133
+ def initialize(kind, method, options = {})
134
+ @kind = kind
135
+ @method = method
136
+ @identifier = options[:identifier]
137
+ @options = options
138
+ end
139
+
140
+ def ==(other)
141
+ case other
142
+ when Callback
143
+ (self.identifier && self.identifier == other.identifier) || self.method == other.method
144
+ else
145
+ (self.identifier && self.identifier == other) || self.method == other
146
+ end
147
+ end
148
+
149
+ def eql?(other)
150
+ self == other
151
+ end
152
+
153
+ def dup
154
+ self.class.new(@kind, @method, @options.dup)
155
+ end
156
+
157
+ def hash
158
+ if @identifier
159
+ @identifier.hash
160
+ else
161
+ @method.hash
162
+ end
163
+ end
164
+
165
+ def call(*args, &block)
166
+ evaluate_method(method, *args, &block) if should_run_callback?(*args)
167
+ rescue LocalJumpError
168
+ raise ArgumentError,
169
+ "Cannot yield from a Proc type filter. The Proc must take two " +
170
+ "arguments and execute #call on the second argument."
171
+ end
172
+
173
+ private
174
+ def evaluate_method(method, *args, &block)
175
+ case method
176
+ when Symbol
177
+ object = args.shift
178
+ object.send(method, *args, &block)
179
+ when String
180
+ eval(method, args.first.instance_eval { binding })
181
+ when Proc, Method
182
+ method.call(*args, &block)
183
+ else
184
+ if method.respond_to?(kind)
185
+ method.send(kind, *args, &block)
186
+ else
187
+ raise ArgumentError,
188
+ "Callbacks must be a symbol denoting the method to call, a string to be evaluated, " +
189
+ "a block to be invoked, or an object responding to the callback method."
190
+ end
191
+ end
192
+ end
193
+
194
+ def should_run_callback?(*args)
195
+ if options[:if]
196
+ evaluate_method(options[:if], *args)
197
+ elsif options[:unless]
198
+ !evaluate_method(options[:unless], *args)
199
+ else
200
+ true
201
+ end
202
+ end
203
+ end
204
+
205
+ def self.included(base)
206
+ base.extend ClassMethods
207
+ end
208
+
209
+ module ClassMethods
210
+ # 定义回调
211
+ #
212
+ # 例如:
213
+ # callbacks = %w(before_save)
214
+ # base.define_callbacks *callbacks
215
+ # 等价于
216
+ # class << base
217
+ # def before_save(*methods,&block)
218
+ # callbacks= CallbackChain.build(:before_save,*methods,&block)
219
+ # (@before_save_callbacks ||= CallbackChain.new).concat callbacks
220
+ # end
221
+ #
222
+ # def before_save_callback_chain
223
+ # @before_save_callbacks ||= CallbackChain.new
224
+ # if superclass.respond_to?(:before_save_callback_chain)
225
+ # CallbackChain.new(superclass.before_save_callback_chain+@before_save_callbacks)
226
+ # else
227
+ # @before_save_callbacks
228
+ # end
229
+ # end
230
+ # end
231
+ def define_callbacks(*callbacks)
232
+ callbacks.each do |callback|
233
+ class_eval <<-"end_eval"
234
+ def self.#{callback}(*methods, &block)
235
+ callbacks = CallbackChain.build(:#{callback}, *methods, &block)
236
+ (@#{callback}_callbacks ||= CallbackChain.new).concat callbacks
237
+ end
238
+
239
+ def self.#{callback}_callback_chain
240
+ @#{callback}_callbacks ||= CallbackChain.new
241
+
242
+ if superclass.respond_to?(:#{callback}_callback_chain)
243
+ CallbackChain.new(superclass.#{callback}_callback_chain + @#{callback}_callbacks)
244
+ else
245
+ @#{callback}_callbacks
246
+ end
247
+ end
248
+ end_eval
249
+ end
250
+ end
251
+ end
252
+
253
+ # Runs all the callbacks defined for the given options.
254
+ #
255
+ # If a block is given it will be called after each callback receiving as arguments:
256
+ #
257
+ # * the result from the callback
258
+ # * the object which has the callback
259
+ #
260
+ # If the result from the block evaluates to false, the callback chain is stopped.
261
+ #
262
+ # Example:
263
+ # class Storage
264
+ # include ActiveSupport::Callbacks
265
+ #
266
+ # define_callbacks :before_save, :after_save
267
+ # end
268
+ #
269
+ # class ConfigStorage < Storage
270
+ # before_save :pass
271
+ # before_save :pass
272
+ # before_save :stop
273
+ # before_save :pass
274
+ #
275
+ # def pass
276
+ # puts "pass"
277
+ # end
278
+ #
279
+ # def stop
280
+ # puts "stop"
281
+ # return false
282
+ # end
283
+ #
284
+ # def save
285
+ # result = run_callbacks(:before_save) { |result, object| result == false }
286
+ # puts "- save" if result
287
+ # end
288
+ # end
289
+ #
290
+ # config = ConfigStorage.new
291
+ # config.save
292
+ #
293
+ # Output:
294
+ # pass
295
+ # pass
296
+ # stop
297
+ def run_callbacks(kind, options = {}, &block)
298
+ # self.class.send("#{kind}_callback_chain")
299
+ #
300
+ self.class.send("#{kind}_callback_chain").run(self, options, &block)
301
+ end
302
+ end
303
+ end
@@ -0,0 +1,53 @@
1
+ module ActiveSupport #:nodoc:
2
+ module CoreExtensions #:nodoc:
3
+ module Array #:nodoc:
4
+ # Makes it easier to access parts of an array.
5
+ module Access
6
+ # Returns the tail of the array from +position+.
7
+ #
8
+ # %w( a b c d ).from(0) # => %w( a b c d )
9
+ # %w( a b c d ).from(2) # => %w( c d )
10
+ # %w( a b c d ).from(10) # => nil
11
+ # %w().from(0) # => nil
12
+ def from(position)
13
+ self[position..-1]
14
+ end
15
+
16
+ # Returns the beginning of the array up to +position+.
17
+ #
18
+ # %w( a b c d ).to(0) # => %w( a )
19
+ # %w( a b c d ).to(2) # => %w( a b c )
20
+ # %w( a b c d ).to(10) # => %w( a b c d )
21
+ # %w().to(0) # => %w()
22
+ def to(position)
23
+ self[0..position]
24
+ end
25
+
26
+ # Equal to <tt>self[1]</tt>.
27
+ def second
28
+ self[1]
29
+ end
30
+
31
+ # Equal to <tt>self[2]</tt>.
32
+ def third
33
+ self[2]
34
+ end
35
+
36
+ # Equal to <tt>self[3]</tt>.
37
+ def fourth
38
+ self[3]
39
+ end
40
+
41
+ # Equal to <tt>self[4]</tt>.
42
+ def fifth
43
+ self[4]
44
+ end
45
+
46
+ # Equal to <tt>self[41]</tt>. Also known as accessing "the reddit".
47
+ def forty_two
48
+ self[41]
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,183 @@
1
+ require 'builder'
2
+
3
+ module ActiveSupport #:nodoc:
4
+ module CoreExtensions #:nodoc:
5
+ module Array #:nodoc:
6
+ module Conversions
7
+ # Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options:
8
+ # * <tt>:connector</tt> - The word used to join the last element in arrays with two or more elements (default: "and")
9
+ # * <tt>:skip_last_comma</tt> - Set to true to return "a, b and c" instead of "a, b, and c".
10
+ def to_sentence(options = {})
11
+ options.assert_valid_keys(:connector, :skip_last_comma, :locale)
12
+
13
+ default = I18n.translate(:'support.array.sentence_connector', :locale => options[:locale])
14
+ default_skip_last_comma = I18n.translate(:'support.array.skip_last_comma', :locale => options[:locale])
15
+ options.reverse_merge! :connector => default, :skip_last_comma => default_skip_last_comma
16
+ options[:connector] = "#{options[:connector]} " unless options[:connector].nil? || options[:connector].strip == ''
17
+
18
+ case length
19
+ when 0
20
+ ""
21
+ when 1
22
+ self[0].to_s
23
+ when 2
24
+ "#{self[0]} #{options[:connector]}#{self[1]}"
25
+ else
26
+ "#{self[0...-1].join(', ')}#{options[:skip_last_comma] ? '' : ','} #{options[:connector]}#{self[-1]}"
27
+ end
28
+ end
29
+
30
+
31
+ # Calls <tt>to_param</tt> on all its elements and joins the result with
32
+ # slashes. This is used by <tt>url_for</tt> in Action Pack.
33
+ def to_param
34
+ collect { |e| e.to_param }.join '/'
35
+ end
36
+
37
+ # Converts an array into a string suitable for use as a URL query string,
38
+ # using the given +key+ as the param name.
39
+ #
40
+ # ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
41
+ def to_query(key)
42
+ prefix = "#{key}[]"
43
+ collect { |value| value.to_query(prefix) }.join '&'
44
+ end
45
+
46
+ def self.included(base) #:nodoc:
47
+ base.class_eval do
48
+ alias_method :to_default_s, :to_s
49
+ alias_method :to_s, :to_formatted_s
50
+ end
51
+ end
52
+
53
+ # Converts a collection of elements into a formatted string by calling
54
+ # <tt>to_s</tt> on all elements and joining them:
55
+ #
56
+ # Blog.find(:all).to_formatted_s # => "First PostSecond PostThird Post"
57
+ #
58
+ # Adding in the <tt>:db</tt> argument as the format yields a prettier
59
+ # output:
60
+ #
61
+ # Blog.find(:all).to_formatted_s(:db) # => "First Post,Second Post,Third Post"
62
+ def to_formatted_s(format = :default)
63
+ case format
64
+ when :db
65
+ if respond_to?(:empty?) && self.empty?
66
+ "null"
67
+ else
68
+ collect { |element| element.id }.join(",")
69
+ end
70
+ else
71
+ to_default_s
72
+ end
73
+ end
74
+
75
+ # Returns a string that represents this array in XML by sending +to_xml+
76
+ # to each element. Active Record collections delegate their representation
77
+ # in XML to this method.
78
+ #
79
+ # All elements are expected to respond to +to_xml+, if any of them does
80
+ # not an exception is raised.
81
+ #
82
+ # The root node reflects the class name of the first element in plural
83
+ # if all elements belong to the same type and that's not Hash:
84
+ #
85
+ # customer.projects.to_xml
86
+ #
87
+ # <?xml version="1.0" encoding="UTF-8"?>
88
+ # <projects type="array">
89
+ # <project>
90
+ # <amount type="decimal">20000.0</amount>
91
+ # <customer-id type="integer">1567</customer-id>
92
+ # <deal-date type="date">2008-04-09</deal-date>
93
+ # ...
94
+ # </project>
95
+ # <project>
96
+ # <amount type="decimal">57230.0</amount>
97
+ # <customer-id type="integer">1567</customer-id>
98
+ # <deal-date type="date">2008-04-15</deal-date>
99
+ # ...
100
+ # </project>
101
+ # </projects>
102
+ #
103
+ # Otherwise the root element is "records":
104
+ #
105
+ # [{:foo => 1, :bar => 2}, {:baz => 3}].to_xml
106
+ #
107
+ # <?xml version="1.0" encoding="UTF-8"?>
108
+ # <records type="array">
109
+ # <record>
110
+ # <bar type="integer">2</bar>
111
+ # <foo type="integer">1</foo>
112
+ # </record>
113
+ # <record>
114
+ # <baz type="integer">3</baz>
115
+ # </record>
116
+ # </records>
117
+ #
118
+ # If the collection is empty the root element is "nil-classes" by default:
119
+ #
120
+ # [].to_xml
121
+ #
122
+ # <?xml version="1.0" encoding="UTF-8"?>
123
+ # <nil-classes type="array"/>
124
+ #
125
+ # To ensure a meaningful root element use the <tt>:root</tt> option:
126
+ #
127
+ # customer_with_no_projects.projects.to_xml(:root => "projects")
128
+ #
129
+ # <?xml version="1.0" encoding="UTF-8"?>
130
+ # <projects type="array"/>
131
+ #
132
+ # By default root children have as node name the one of the root
133
+ # singularized. You can change it with the <tt>:children</tt> option.
134
+ #
135
+ # The +options+ hash is passed downwards:
136
+ #
137
+ # Message.all.to_xml(:skip_types => true)
138
+ #
139
+ # <?xml version="1.0" encoding="UTF-8"?>
140
+ # <messages>
141
+ # <message>
142
+ # <created-at>2008-03-07T09:58:18+01:00</created-at>
143
+ # <id>1</id>
144
+ # <name>1</name>
145
+ # <updated-at>2008-03-07T09:58:18+01:00</updated-at>
146
+ # <user-id>1</user-id>
147
+ # </message>
148
+ # </messages>
149
+ #
150
+ def to_xml(options = {})
151
+ raise "Not all elements respond to to_xml" unless all? { |e| e.respond_to? :to_xml }
152
+
153
+ options[:root] ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? first.class.to_s.underscore.pluralize : "records"
154
+ options[:children] ||= options[:root].singularize
155
+ options[:indent] ||= 2
156
+ options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
157
+
158
+ root = options.delete(:root).to_s
159
+ children = options.delete(:children)
160
+
161
+ if !options.has_key?(:dasherize) || options[:dasherize]
162
+ root = root.dasherize
163
+ end
164
+
165
+ options[:builder].instruct! unless options.delete(:skip_instruct)
166
+
167
+ opts = options.merge({ :root => children })
168
+
169
+ xml = options[:builder]
170
+ if empty?
171
+ xml.tag!(root, options[:skip_types] ? {} : {:type => "array"})
172
+ else
173
+ xml.tag!(root, options[:skip_types] ? {} : {:type => "array"}) {
174
+ yield xml if block_given?
175
+ each { |e| e.to_xml(opts.merge({ :skip_instruct => true })) }
176
+ }
177
+ end
178
+ end
179
+
180
+ end
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,20 @@
1
+ module ActiveSupport #:nodoc:
2
+ module CoreExtensions #:nodoc:
3
+ module Array #:nodoc:
4
+ module ExtractOptions
5
+ # Extracts options from a set of arguments. Removes and returns the last
6
+ # element in the array if it's a hash, otherwise returns a blank hash.
7
+ #
8
+ # def options(*args)
9
+ # args.extract_options!
10
+ # end
11
+ #
12
+ # options(1, 2) # => {}
13
+ # options(1, 2, :a => :b) # => {:a=>:b}
14
+ def extract_options!
15
+ last.is_a?(::Hash) ? pop : {}
16
+ end
17
+ end
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,106 @@
1
+ require 'enumerator'
2
+
3
+ module ActiveSupport #:nodoc:
4
+ module CoreExtensions #:nodoc:
5
+ module Array #:nodoc:
6
+ module Grouping
7
+ # Splits or iterates over the array in groups of size +number+,
8
+ # padding any remaining slots with +fill_with+ unless it is +false+.
9
+ #
10
+ # %w(1 2 3 4 5 6 7).in_groups_of(3) {|group| p group}
11
+ # ["1", "2", "3"]
12
+ # ["4", "5", "6"]
13
+ # ["7", nil, nil]
14
+ #
15
+ # %w(1 2 3).in_groups_of(2, '&nbsp;') {|group| p group}
16
+ # ["1", "2"]
17
+ # ["3", "&nbsp;"]
18
+ #
19
+ # %w(1 2 3).in_groups_of(2, false) {|group| p group}
20
+ # ["1", "2"]
21
+ # ["3"]
22
+ def in_groups_of(number, fill_with = nil)
23
+ if fill_with == false
24
+ collection = self
25
+ else
26
+ # size % number gives how many extra we have;
27
+ # subtracting from number gives how many to add;
28
+ # modulo number ensures we don't add group of just fill.
29
+ padding = (number - size % number) % number
30
+ collection = dup.concat([fill_with] * padding)
31
+ end
32
+
33
+ if block_given?
34
+ collection.each_slice(number) { |slice| yield(slice) }
35
+ else
36
+ returning [] do |groups|
37
+ collection.each_slice(number) { |group| groups << group }
38
+ end
39
+ end
40
+ end
41
+
42
+ # Splits or iterates over the array in +number+ of groups, padding any
43
+ # remaining slots with +fill_with+ unless it is +false+.
44
+ #
45
+ # %w(1 2 3 4 5 6 7 8 9 10).in_groups(3) {|group| p group}
46
+ # ["1", "2", "3", "4"]
47
+ # ["5", "6", "7", nil]
48
+ # ["8", "9", "10", nil]
49
+ #
50
+ # %w(1 2 3 4 5 6 7).in_groups(3, '&nbsp;') {|group| p group}
51
+ # ["1", "2", "3"]
52
+ # ["4", "5", "&nbsp;"]
53
+ # ["6", "7", "&nbsp;"]
54
+ #
55
+ # %w(1 2 3 4 5 6 7).in_groups(3, false) {|group| p group}
56
+ # ["1", "2", "3"]
57
+ # ["4", "5"]
58
+ # ["6", "7"]
59
+ def in_groups(number, fill_with = nil)
60
+ # size / number gives minor group size;
61
+ # size % number gives how many objects need extra accomodation;
62
+ # each group hold either division or division + 1 items.
63
+ division = size / number
64
+ modulo = size % number
65
+
66
+ # create a new array avoiding dup
67
+ groups = []
68
+ start = 0
69
+
70
+ number.times do |index|
71
+ length = division + (modulo > 0 && modulo > index ? 1 : 0)
72
+ padding = fill_with != false &&
73
+ modulo > 0 && length == division ? 1 : 0
74
+ groups << slice(start, length).concat([fill_with] * padding)
75
+ start += length
76
+ end
77
+
78
+ if block_given?
79
+ groups.each{|g| yield(g) }
80
+ else
81
+ groups
82
+ end
83
+ end
84
+
85
+ # Divides the array into one or more subarrays based on a delimiting +value+
86
+ # or the result of an optional block.
87
+ #
88
+ # [1, 2, 3, 4, 5].split(3) # => [[1, 2], [4, 5]]
89
+ # (1..10).to_a.split { |i| i % 3 == 0 } # => [[1, 2], [4, 5], [7, 8], [10]]
90
+ def split(value = nil)
91
+ using_block = block_given?
92
+
93
+ inject([[]]) do |results, element|
94
+ if (using_block && yield(element)) || (value == element)
95
+ results << []
96
+ else
97
+ results.last << element
98
+ end
99
+
100
+ results
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,12 @@
1
+ module ActiveSupport #:nodoc:
2
+ module CoreExtensions #:nodoc:
3
+ module Array #:nodoc:
4
+ module RandomAccess
5
+ # Returns a random element from the array.
6
+ def rand
7
+ self[Kernel.rand(length)]
8
+ end
9
+ end
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,13 @@
1
+ require 'active_support/core_ext/array/access'
2
+ require 'active_support/core_ext/array/conversions'
3
+ require 'active_support/core_ext/array/extract_options'
4
+ require 'active_support/core_ext/array/grouping'
5
+ require 'active_support/core_ext/array/random_access'
6
+
7
+ class Array #:nodoc:
8
+ include ActiveSupport::CoreExtensions::Array::Access
9
+ include ActiveSupport::CoreExtensions::Array::Conversions
10
+ include ActiveSupport::CoreExtensions::Array::ExtractOptions
11
+ include ActiveSupport::CoreExtensions::Array::Grouping
12
+ include ActiveSupport::CoreExtensions::Array::RandomAccess
13
+ end