davidlee-state-fu 0.0.2 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.textile +13 -1
- data/lib/state-fu.rb +4 -3
- data/lib/state_fu/active_support_lite/array/access.rb +53 -0
- data/lib/state_fu/active_support_lite/array/conversions.rb +196 -0
- data/lib/state_fu/active_support_lite/array/extract_options.rb +20 -0
- data/lib/state_fu/active_support_lite/array/grouping.rb +106 -0
- data/lib/state_fu/active_support_lite/array/random_access.rb +12 -0
- data/lib/state_fu/active_support_lite/array/wrapper.rb +24 -0
- data/lib/state_fu/active_support_lite/array.rb +7 -0
- data/lib/state_fu/active_support_lite/blank.rb +58 -0
- data/lib/state_fu/active_support_lite/cattr_reader.rb +54 -0
- data/lib/state_fu/active_support_lite/inheritable_attributes.rb +1 -0
- data/lib/state_fu/active_support_lite/keys.rb +52 -0
- data/lib/state_fu/active_support_lite/object.rb +6 -0
- data/lib/state_fu/active_support_lite/string.rb +33 -0
- data/lib/state_fu/active_support_lite/symbol.rb +15 -0
- data/lib/state_fu/binding.rb +113 -58
- data/lib/state_fu/core_ext.rb +12 -13
- data/lib/state_fu/event.rb +4 -4
- data/lib/state_fu/exceptions.rb +12 -0
- data/lib/state_fu/helper.rb +74 -12
- data/lib/state_fu/lathe.rb +15 -0
- data/lib/state_fu/machine.rb +17 -25
- data/lib/state_fu/mock_transition.rb +38 -0
- data/lib/state_fu/persistence/active_record.rb +2 -2
- data/lib/state_fu/persistence/attribute.rb +4 -4
- data/lib/state_fu/persistence/base.rb +1 -1
- data/lib/state_fu/sprocket.rb +4 -0
- data/lib/state_fu/state.rb +4 -4
- data/lib/state_fu/transition.rb +24 -22
- data/spec/helper.rb +5 -3
- data/spec/integration/binding_extension_spec.rb +41 -0
- data/spec/integration/dynamic_requirement_spec.rb +160 -0
- data/spec/integration/example_01_document_spec.rb +2 -1
- data/spec/integration/lathe_extension_spec.rb +67 -0
- data/spec/integration/requirement_reflection_spec.rb +71 -11
- data/spec/integration/temp_spec.rb +17 -0
- data/spec/integration/transition_spec.rb +31 -19
- data/spec/units/binding_spec.rb +6 -0
- data/spec/units/event_spec.rb +6 -5
- data/spec/units/lathe_spec.rb +4 -2
- metadata +40 -17
data/README.textile
CHANGED
|
@@ -170,7 +170,7 @@ To install the dependencies for running specs:
|
|
|
170
170
|
|
|
171
171
|
<pre>
|
|
172
172
|
<code>
|
|
173
|
-
sudo gem install rspec rr
|
|
173
|
+
sudo gem install rspec rr
|
|
174
174
|
rake # run the specs
|
|
175
175
|
rake spec:doc # generate specdocs
|
|
176
176
|
rake doc # generate rdocs
|
|
@@ -185,4 +185,16 @@ The spec folder is currently the best source of documentation.
|
|
|
185
185
|
|
|
186
186
|
If you have questions, feature request or ideas, please join the "google group":http://groups.google.com/group/state-fu
|
|
187
187
|
|
|
188
|
+
A note about ActiveSupport:
|
|
189
|
+
|
|
190
|
+
StateFu will use ActiveSupport if it is already loaded. If not, it
|
|
191
|
+
will load its own (very heavily cut down) 'lite' version. This means
|
|
192
|
+
that if you require StateFu *before* other libraries which require
|
|
193
|
+
ActiveSupport (e.g. ActiveRecord), you may have to explicitly
|
|
194
|
+
<code>require 'activesupport'</code> before loading the dependent
|
|
195
|
+
libraries.
|
|
196
|
+
|
|
197
|
+
|
|
188
198
|
Also see the "issue tracker":http://github.com/davidlee/state-fu/issues
|
|
199
|
+
|
|
200
|
+
And the "build monitor":http://runcoderun.com/davidlee/state-fu/
|
data/lib/state-fu.rb
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
|
|
2
1
|
#!/usr/bin/env ruby
|
|
3
2
|
#
|
|
4
3
|
# State-Fu
|
|
@@ -44,7 +43,7 @@
|
|
|
44
43
|
# my_doc = Document.new
|
|
45
44
|
#
|
|
46
45
|
# my_doc.status # returns a StateFu::Binding, which lets us access the 'Fu
|
|
47
|
-
# my_doc.
|
|
46
|
+
# my_doc.status.state => 'draft' # if this wasn't already a database column or attribute, an
|
|
48
47
|
# # attribute has been created to keep track of the state
|
|
49
48
|
# my_doc.status.name => :draft # the name of the current_state (defaults to the first defined)
|
|
50
49
|
# my_doc.status.publish! # raised => StateFu::RequirementError: [:author]
|
|
@@ -55,7 +54,8 @@
|
|
|
55
54
|
# "new feed!" # aha - our event hook fires!
|
|
56
55
|
# my_doc.status.name => :published # and the state has been updated.
|
|
57
56
|
|
|
58
|
-
require '
|
|
57
|
+
require 'rubygems'
|
|
58
|
+
# require 'activesupport'
|
|
59
59
|
|
|
60
60
|
require 'state_fu/core_ext'
|
|
61
61
|
require 'state_fu/logger'
|
|
@@ -76,6 +76,7 @@ require 'state_fu/event'
|
|
|
76
76
|
require 'state_fu/hooks'
|
|
77
77
|
require 'state_fu/interface'
|
|
78
78
|
require 'state_fu/transition'
|
|
79
|
+
require 'state_fu/mock_transition'
|
|
79
80
|
|
|
80
81
|
module StateFu
|
|
81
82
|
DEFAULT_MACHINE = :state_fu
|
|
@@ -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,196 @@
|
|
|
1
|
+
module ActiveSupport #:nodoc:
|
|
2
|
+
module CoreExtensions #:nodoc:
|
|
3
|
+
module Array #:nodoc:
|
|
4
|
+
module Conversions
|
|
5
|
+
# Converts the array to a comma-separated sentence where the last element is joined by the connector word. Options:
|
|
6
|
+
# * <tt>:words_connector</tt> - The sign or word used to join the elements in arrays with two or more elements (default: ", ")
|
|
7
|
+
# * <tt>:two_words_connector</tt> - The sign or word used to join the elements in arrays with two elements (default: " and ")
|
|
8
|
+
# * <tt>:last_word_connector</tt> - The sign or word used to join the last element in arrays with three or more elements (default: ", and ")
|
|
9
|
+
def to_sentence(options = {})
|
|
10
|
+
default_words_connector = I18n.translate(:'support.array.words_connector', :locale => options[:locale])
|
|
11
|
+
default_two_words_connector = I18n.translate(:'support.array.two_words_connector', :locale => options[:locale])
|
|
12
|
+
default_last_word_connector = I18n.translate(:'support.array.last_word_connector', :locale => options[:locale])
|
|
13
|
+
|
|
14
|
+
# Try to emulate to_senteces previous to 2.3
|
|
15
|
+
if options.has_key?(:connector) || options.has_key?(:skip_last_comma)
|
|
16
|
+
::ActiveSupport::Deprecation.warn(":connector has been deprecated. Use :words_connector instead", caller) if options.has_key? :connector
|
|
17
|
+
::ActiveSupport::Deprecation.warn(":skip_last_comma has been deprecated. Use :last_word_connector instead", caller) if options.has_key? :skip_last_comma
|
|
18
|
+
|
|
19
|
+
skip_last_comma = options.delete :skip_last_comma
|
|
20
|
+
if connector = options.delete(:connector)
|
|
21
|
+
options[:last_word_connector] ||= skip_last_comma ? connector : ", #{connector}"
|
|
22
|
+
else
|
|
23
|
+
options[:last_word_connector] ||= skip_last_comma ? default_two_words_connector : default_last_word_connector
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
options.assert_valid_keys(:words_connector, :two_words_connector, :last_word_connector, :locale)
|
|
28
|
+
options.reverse_merge! :words_connector => default_words_connector, :two_words_connector => default_two_words_connector, :last_word_connector => default_last_word_connector
|
|
29
|
+
|
|
30
|
+
case length
|
|
31
|
+
when 0
|
|
32
|
+
""
|
|
33
|
+
when 1
|
|
34
|
+
self[0].to_s
|
|
35
|
+
when 2
|
|
36
|
+
"#{self[0]}#{options[:two_words_connector]}#{self[1]}"
|
|
37
|
+
else
|
|
38
|
+
"#{self[0...-1].join(options[:words_connector])}#{options[:last_word_connector]}#{self[-1]}"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# Calls <tt>to_param</tt> on all its elements and joins the result with
|
|
44
|
+
# slashes. This is used by <tt>url_for</tt> in Action Pack.
|
|
45
|
+
def to_param
|
|
46
|
+
collect { |e| e.to_param }.join '/'
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Converts an array into a string suitable for use as a URL query string,
|
|
50
|
+
# using the given +key+ as the param name.
|
|
51
|
+
#
|
|
52
|
+
# ['Rails', 'coding'].to_query('hobbies') # => "hobbies%5B%5D=Rails&hobbies%5B%5D=coding"
|
|
53
|
+
def to_query(key)
|
|
54
|
+
prefix = "#{key}[]"
|
|
55
|
+
collect { |value| value.to_query(prefix) }.join '&'
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def self.included(base) #:nodoc:
|
|
59
|
+
base.class_eval do
|
|
60
|
+
alias_method :to_default_s, :to_s
|
|
61
|
+
alias_method :to_s, :to_formatted_s
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Converts a collection of elements into a formatted string by calling
|
|
66
|
+
# <tt>to_s</tt> on all elements and joining them:
|
|
67
|
+
#
|
|
68
|
+
# Blog.find(:all).to_formatted_s # => "First PostSecond PostThird Post"
|
|
69
|
+
#
|
|
70
|
+
# Adding in the <tt>:db</tt> argument as the format yields a prettier
|
|
71
|
+
# output:
|
|
72
|
+
#
|
|
73
|
+
# Blog.find(:all).to_formatted_s(:db) # => "First Post,Second Post,Third Post"
|
|
74
|
+
def to_formatted_s(format = :default)
|
|
75
|
+
case format
|
|
76
|
+
when :db
|
|
77
|
+
if respond_to?(:empty?) && self.empty?
|
|
78
|
+
"null"
|
|
79
|
+
else
|
|
80
|
+
collect { |element| element.id }.join(",")
|
|
81
|
+
end
|
|
82
|
+
else
|
|
83
|
+
to_default_s
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
# Returns a string that represents this array in XML by sending +to_xml+
|
|
88
|
+
# to each element. Active Record collections delegate their representation
|
|
89
|
+
# in XML to this method.
|
|
90
|
+
#
|
|
91
|
+
# All elements are expected to respond to +to_xml+, if any of them does
|
|
92
|
+
# not an exception is raised.
|
|
93
|
+
#
|
|
94
|
+
# The root node reflects the class name of the first element in plural
|
|
95
|
+
# if all elements belong to the same type and that's not Hash:
|
|
96
|
+
#
|
|
97
|
+
# customer.projects.to_xml
|
|
98
|
+
#
|
|
99
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
|
100
|
+
# <projects type="array">
|
|
101
|
+
# <project>
|
|
102
|
+
# <amount type="decimal">20000.0</amount>
|
|
103
|
+
# <customer-id type="integer">1567</customer-id>
|
|
104
|
+
# <deal-date type="date">2008-04-09</deal-date>
|
|
105
|
+
# ...
|
|
106
|
+
# </project>
|
|
107
|
+
# <project>
|
|
108
|
+
# <amount type="decimal">57230.0</amount>
|
|
109
|
+
# <customer-id type="integer">1567</customer-id>
|
|
110
|
+
# <deal-date type="date">2008-04-15</deal-date>
|
|
111
|
+
# ...
|
|
112
|
+
# </project>
|
|
113
|
+
# </projects>
|
|
114
|
+
#
|
|
115
|
+
# Otherwise the root element is "records":
|
|
116
|
+
#
|
|
117
|
+
# [{:foo => 1, :bar => 2}, {:baz => 3}].to_xml
|
|
118
|
+
#
|
|
119
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
|
120
|
+
# <records type="array">
|
|
121
|
+
# <record>
|
|
122
|
+
# <bar type="integer">2</bar>
|
|
123
|
+
# <foo type="integer">1</foo>
|
|
124
|
+
# </record>
|
|
125
|
+
# <record>
|
|
126
|
+
# <baz type="integer">3</baz>
|
|
127
|
+
# </record>
|
|
128
|
+
# </records>
|
|
129
|
+
#
|
|
130
|
+
# If the collection is empty the root element is "nil-classes" by default:
|
|
131
|
+
#
|
|
132
|
+
# [].to_xml
|
|
133
|
+
#
|
|
134
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
|
135
|
+
# <nil-classes type="array"/>
|
|
136
|
+
#
|
|
137
|
+
# To ensure a meaningful root element use the <tt>:root</tt> option:
|
|
138
|
+
#
|
|
139
|
+
# customer_with_no_projects.projects.to_xml(:root => "projects")
|
|
140
|
+
#
|
|
141
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
|
142
|
+
# <projects type="array"/>
|
|
143
|
+
#
|
|
144
|
+
# By default root children have as node name the one of the root
|
|
145
|
+
# singularized. You can change it with the <tt>:children</tt> option.
|
|
146
|
+
#
|
|
147
|
+
# The +options+ hash is passed downwards:
|
|
148
|
+
#
|
|
149
|
+
# Message.all.to_xml(:skip_types => true)
|
|
150
|
+
#
|
|
151
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
|
152
|
+
# <messages>
|
|
153
|
+
# <message>
|
|
154
|
+
# <created-at>2008-03-07T09:58:18+01:00</created-at>
|
|
155
|
+
# <id>1</id>
|
|
156
|
+
# <name>1</name>
|
|
157
|
+
# <updated-at>2008-03-07T09:58:18+01:00</updated-at>
|
|
158
|
+
# <user-id>1</user-id>
|
|
159
|
+
# </message>
|
|
160
|
+
# </messages>
|
|
161
|
+
#
|
|
162
|
+
def to_xml(options = {})
|
|
163
|
+
raise "Not all elements respond to to_xml" unless all? { |e| e.respond_to? :to_xml }
|
|
164
|
+
require 'builder' unless defined?(Builder)
|
|
165
|
+
|
|
166
|
+
options[:root] ||= all? { |e| e.is_a?(first.class) && first.class.to_s != "Hash" } ? first.class.to_s.underscore.pluralize : "records"
|
|
167
|
+
options[:children] ||= options[:root].singularize
|
|
168
|
+
options[:indent] ||= 2
|
|
169
|
+
options[:builder] ||= Builder::XmlMarkup.new(:indent => options[:indent])
|
|
170
|
+
|
|
171
|
+
root = options.delete(:root).to_s
|
|
172
|
+
children = options.delete(:children)
|
|
173
|
+
|
|
174
|
+
if !options.has_key?(:dasherize) || options[:dasherize]
|
|
175
|
+
root = root.dasherize
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
options[:builder].instruct! unless options.delete(:skip_instruct)
|
|
179
|
+
|
|
180
|
+
opts = options.merge({ :root => children })
|
|
181
|
+
|
|
182
|
+
xml = options[:builder]
|
|
183
|
+
if empty?
|
|
184
|
+
xml.tag!(root, options[:skip_types] ? {} : {:type => "array"})
|
|
185
|
+
else
|
|
186
|
+
xml.tag!(root, options[:skip_types] ? {} : {:type => "array"}) {
|
|
187
|
+
yield xml if block_given?
|
|
188
|
+
each { |e| e.to_xml(opts.merge({ :skip_instruct => true })) }
|
|
189
|
+
}
|
|
190
|
+
end
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
end
|
|
194
|
+
end
|
|
195
|
+
end
|
|
196
|
+
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, ' ') {|group| p group}
|
|
16
|
+
# ["1", "2"]
|
|
17
|
+
# ["3", " "]
|
|
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, ' ') {|group| p group}
|
|
51
|
+
# ["1", "2", "3"]
|
|
52
|
+
# ["4", "5", " "]
|
|
53
|
+
# ["6", "7", " "]
|
|
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,24 @@
|
|
|
1
|
+
module ActiveSupport #:nodoc:
|
|
2
|
+
module CoreExtensions #:nodoc:
|
|
3
|
+
module Array #:nodoc:
|
|
4
|
+
module Wrapper
|
|
5
|
+
# Wraps the object in an Array unless it's an Array. Converts the
|
|
6
|
+
# object to an Array using #to_ary if it implements that.
|
|
7
|
+
def wrap(object)
|
|
8
|
+
case object
|
|
9
|
+
when nil
|
|
10
|
+
[]
|
|
11
|
+
when self
|
|
12
|
+
object
|
|
13
|
+
else
|
|
14
|
+
if object.respond_to?(:to_ary)
|
|
15
|
+
object.to_ary
|
|
16
|
+
else
|
|
17
|
+
[object]
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
require File.join( File.dirname( __FILE__),'array/extract_options')
|
|
2
|
+
require File.join( File.dirname( __FILE__),'array/grouping')
|
|
3
|
+
|
|
4
|
+
class Array #:nodoc:
|
|
5
|
+
include ActiveSupport::CoreExtensions::Array::ExtractOptions
|
|
6
|
+
include ActiveSupport::CoreExtensions::Array::Grouping
|
|
7
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
class Object
|
|
2
|
+
# An object is blank if it's false, empty, or a whitespace string.
|
|
3
|
+
# For example, "", " ", +nil+, [], and {} are blank.
|
|
4
|
+
#
|
|
5
|
+
# This simplifies
|
|
6
|
+
#
|
|
7
|
+
# if !address.nil? && !address.empty?
|
|
8
|
+
#
|
|
9
|
+
# to
|
|
10
|
+
#
|
|
11
|
+
# if !address.blank?
|
|
12
|
+
def blank?
|
|
13
|
+
respond_to?(:empty?) ? empty? : !self
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# An object is present if it's not blank.
|
|
17
|
+
def present?
|
|
18
|
+
!blank?
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
class NilClass #:nodoc:
|
|
23
|
+
def blank?
|
|
24
|
+
true
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
class FalseClass #:nodoc:
|
|
29
|
+
def blank?
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
class TrueClass #:nodoc:
|
|
35
|
+
def blank?
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
class Array #:nodoc:
|
|
41
|
+
alias_method :blank?, :empty?
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
class Hash #:nodoc:
|
|
45
|
+
alias_method :blank?, :empty?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
class String #:nodoc:
|
|
49
|
+
def blank?
|
|
50
|
+
self !~ /\S/
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
class Numeric #:nodoc:
|
|
55
|
+
def blank?
|
|
56
|
+
false
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# Extends the class object with class and instance accessors for class attributes,
|
|
2
|
+
# just like the native attr* accessors for instance attributes.
|
|
3
|
+
#
|
|
4
|
+
# class Person
|
|
5
|
+
# cattr_accessor :hair_colors
|
|
6
|
+
# end
|
|
7
|
+
#
|
|
8
|
+
# Person.hair_colors = [:brown, :black, :blonde, :red]
|
|
9
|
+
class Class
|
|
10
|
+
def cattr_reader(*syms)
|
|
11
|
+
syms.flatten.each do |sym|
|
|
12
|
+
next if sym.is_a?(Hash)
|
|
13
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
|
14
|
+
unless defined? @@#{sym} # unless defined? @@hair_colors
|
|
15
|
+
@@#{sym} = nil # @@hair_colors = nil
|
|
16
|
+
end # end
|
|
17
|
+
#
|
|
18
|
+
def self.#{sym} # def self.hair_colors
|
|
19
|
+
@@#{sym} # @@hair_colors
|
|
20
|
+
end # end
|
|
21
|
+
#
|
|
22
|
+
def #{sym} # def hair_colors
|
|
23
|
+
@@#{sym} # @@hair_colors
|
|
24
|
+
end # end
|
|
25
|
+
EOS
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def cattr_writer(*syms)
|
|
30
|
+
options = syms.extract_options!
|
|
31
|
+
syms.flatten.each do |sym|
|
|
32
|
+
class_eval(<<-EOS, __FILE__, __LINE__)
|
|
33
|
+
unless defined? @@#{sym} # unless defined? @@hair_colors
|
|
34
|
+
@@#{sym} = nil # @@hair_colors = nil
|
|
35
|
+
end # end
|
|
36
|
+
#
|
|
37
|
+
def self.#{sym}=(obj) # def self.hair_colors=(obj)
|
|
38
|
+
@@#{sym} = obj # @@hair_colors = obj
|
|
39
|
+
end # end
|
|
40
|
+
#
|
|
41
|
+
#{" #
|
|
42
|
+
def #{sym}=(obj) # def hair_colors=(obj)
|
|
43
|
+
@@#{sym} = obj # @@hair_colors = obj
|
|
44
|
+
end # end
|
|
45
|
+
" unless options[:instance_writer] == false } # # instance writer above is generated unless options[:instance_writer] == false
|
|
46
|
+
EOS
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def cattr_accessor(*syms)
|
|
51
|
+
cattr_reader(*syms)
|
|
52
|
+
cattr_writer(*syms)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
module ActiveSupport #:nodoc:
|
|
2
|
+
module CoreExtensions #:nodoc:
|
|
3
|
+
module Hash #:nodoc:
|
|
4
|
+
module Keys
|
|
5
|
+
# Return a new hash with all keys converted to strings.
|
|
6
|
+
def stringify_keys
|
|
7
|
+
inject({}) do |options, (key, value)|
|
|
8
|
+
options[key.to_s] = value
|
|
9
|
+
options
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
# Destructively convert all keys to strings.
|
|
14
|
+
def stringify_keys!
|
|
15
|
+
keys.each do |key|
|
|
16
|
+
self[key.to_s] = delete(key)
|
|
17
|
+
end
|
|
18
|
+
self
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Return a new hash with all keys converted to symbols.
|
|
22
|
+
def symbolize_keys
|
|
23
|
+
inject({}) do |options, (key, value)|
|
|
24
|
+
options[(key.to_sym rescue key) || key] = value
|
|
25
|
+
options
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Destructively convert all keys to symbols.
|
|
30
|
+
def symbolize_keys!
|
|
31
|
+
self.replace(self.symbolize_keys)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
alias_method :to_options, :symbolize_keys
|
|
35
|
+
alias_method :to_options!, :symbolize_keys!
|
|
36
|
+
|
|
37
|
+
# Validate all keys in a hash match *valid keys, raising ArgumentError on a mismatch.
|
|
38
|
+
# Note that keys are NOT treated indifferently, meaning if you use strings for keys but assert symbols
|
|
39
|
+
# as keys, this will fail.
|
|
40
|
+
#
|
|
41
|
+
# ==== Examples
|
|
42
|
+
# { :name => "Rob", :years => "28" }.assert_valid_keys(:name, :age) # => raises "ArgumentError: Unknown key(s): years"
|
|
43
|
+
# { :name => "Rob", :age => "28" }.assert_valid_keys("name", "age") # => raises "ArgumentError: Unknown key(s): name, age"
|
|
44
|
+
# { :name => "Rob", :age => "28" }.assert_valid_keys(:name, :age) # => passes, raises nothing
|
|
45
|
+
def assert_valid_keys(*valid_keys)
|
|
46
|
+
unknown_keys = keys - [valid_keys].flatten
|
|
47
|
+
raise(ArgumentError, "Unknown key(s): #{unknown_keys.join(", ")}") unless unknown_keys.empty?
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|