couch_tomato 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE.txt +19 -0
- data/README.md +96 -0
- data/init.rb +3 -0
- data/lib/core_ext/date.rb +10 -0
- data/lib/core_ext/duplicable.rb +43 -0
- data/lib/core_ext/extract_options.rb +14 -0
- data/lib/core_ext/inheritable_attributes.rb +222 -0
- data/lib/core_ext/object.rb +5 -0
- data/lib/core_ext/string.rb +19 -0
- data/lib/core_ext/symbol.rb +15 -0
- data/lib/core_ext/time.rb +12 -0
- data/lib/couch_tomato/database.rb +279 -0
- data/lib/couch_tomato/js_view_source.rb +182 -0
- data/lib/couch_tomato/migration.rb +52 -0
- data/lib/couch_tomato/migrator.rb +235 -0
- data/lib/couch_tomato/persistence/base.rb +62 -0
- data/lib/couch_tomato/persistence/belongs_to_property.rb +58 -0
- data/lib/couch_tomato/persistence/callbacks.rb +60 -0
- data/lib/couch_tomato/persistence/dirty_attributes.rb +27 -0
- data/lib/couch_tomato/persistence/json.rb +48 -0
- data/lib/couch_tomato/persistence/magic_timestamps.rb +15 -0
- data/lib/couch_tomato/persistence/properties.rb +58 -0
- data/lib/couch_tomato/persistence/simple_property.rb +97 -0
- data/lib/couch_tomato/persistence/validation.rb +18 -0
- data/lib/couch_tomato/persistence.rb +85 -0
- data/lib/couch_tomato/replicator.rb +50 -0
- data/lib/couch_tomato.rb +46 -0
- data/lib/tasks/couch_tomato.rake +128 -0
- data/rails/init.rb +7 -0
- data/spec/callbacks_spec.rb +271 -0
- data/spec/comment.rb +8 -0
- data/spec/create_spec.rb +22 -0
- data/spec/custom_view_spec.rb +134 -0
- data/spec/destroy_spec.rb +29 -0
- data/spec/fixtures/address.rb +9 -0
- data/spec/fixtures/person.rb +6 -0
- data/spec/property_spec.rb +103 -0
- data/spec/spec_helper.rb +40 -0
- data/spec/unit/attributes_spec.rb +26 -0
- data/spec/unit/callbacks_spec.rb +33 -0
- data/spec/unit/create_spec.rb +58 -0
- data/spec/unit/customs_views_spec.rb +15 -0
- data/spec/unit/database_spec.rb +38 -0
- data/spec/unit/dirty_attributes_spec.rb +113 -0
- data/spec/unit/string_spec.rb +13 -0
- data/spec/unit/view_query_spec.rb +9 -0
- data/spec/update_spec.rb +40 -0
- data/test/test_helper.rb +63 -0
- data/test/unit/database_test.rb +285 -0
- data/test/unit/js_view_test.rb +362 -0
- data/test/unit/property_test.rb +193 -0
- metadata +133 -0
data/MIT-LICENSE.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2007 Bryan Helmkamp, Seth Fitzsimmons
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
Couch Tomato
|
2
|
+
============
|
3
|
+
|
4
|
+
## TODO
|
5
|
+
|
6
|
+
### Documentation
|
7
|
+
|
8
|
+
- Quick start
|
9
|
+
- Data migration mechanism for managing (implicit) schema and data changes between team members and deployed systems, complete with generator and Rake tasks
|
10
|
+
- Rake tasks that facilitate replication of local and remote CouchDB databases
|
11
|
+
- Removed model property dirty tracking (added significant complexity and we haven't needed it...yet?)
|
12
|
+
- Shoulda instead of RSpec
|
13
|
+
- RR for mocks/stubs
|
14
|
+
|
15
|
+
### Code
|
16
|
+
|
17
|
+
- default "server" to `http://localhost:5984`
|
18
|
+
|
19
|
+
|
20
|
+
## Potato/Tomato
|
21
|
+
|
22
|
+
We're huge fans of Couch Potato. We love it's advocacy for using Couch naturally (not trying to make it look like a SQL database). Originally a Couch Potato fork, Couch Tomato supports our own production needs.
|
23
|
+
|
24
|
+
### Multi-Database Support
|
25
|
+
|
26
|
+
CouchDB makes it dead-simple to manage multiple databases. For large data-sets, it's very important to separate unrelated documents into separate databases. Couch Tomato assumes (but doesn't force) the use of multiple databases.
|
27
|
+
|
28
|
+
class UserDb < CouchTomato::Database
|
29
|
+
name "users"
|
30
|
+
server "http://#{APP_CONFIG["couchdb_address"]}:#{APP_CONFIG["couchdb_port"]}"
|
31
|
+
end
|
32
|
+
|
33
|
+
class StatDb < CouchTomato::Database
|
34
|
+
name "stats"
|
35
|
+
server "http://#{APP_CONFIG["couchdb_address"]}:#{APP_CONFIG["couchdb_port"]}"
|
36
|
+
end
|
37
|
+
|
38
|
+
UserDb.save_doc(User.new({:name => 'Joe'}))
|
39
|
+
5_000.times { StatDb.save_doc(Stat.new({:metric => 10_000 * rand})) }
|
40
|
+
|
41
|
+
### Each view determines the model for its values
|
42
|
+
|
43
|
+
Views return arbitrary hashes. Often a views' value is an entire document (or more correctly, utilize `emit(key, null)` combined with `:include_docs => true`). But, a views' value is also often completely independent of the structure of the underlying documents.
|
44
|
+
|
45
|
+
Define views on the database rather than inside a model (this is arguably more Couch-like). Each views declaration stipulates whether their results should be 'raw' hashes or a particular model type.
|
46
|
+
|
47
|
+
class UserDb < CouchTomato::Database
|
48
|
+
name "users"
|
49
|
+
server "http://#{APP_CONFIG["couchdb_address"]}:#{APP_CONFIG["couchdb_port"]}"
|
50
|
+
|
51
|
+
view :by_created_at, User
|
52
|
+
view :count # raw
|
53
|
+
end
|
54
|
+
|
55
|
+
### Store view definitions on the file system
|
56
|
+
|
57
|
+
Rather than having Ruby generate JavaScript or writing JavaScript in our Ruby code as a string, define views in files on the file system:
|
58
|
+
|
59
|
+
RAILS_ROOT/couchdb/views/users/*-map.js
|
60
|
+
RAILS_ROOT/couchdb/views/users/*-reduce.js
|
61
|
+
|
62
|
+
The reduce is optional. If you want to define views in a specific design document (called 'lazy'), you can do so:
|
63
|
+
|
64
|
+
RAILS_ROOT/couchdb/views/users/lazy/*.js
|
65
|
+
|
66
|
+
There's a handy generator:
|
67
|
+
|
68
|
+
script/generate view users by_created_at
|
69
|
+
script/generate view users/lazy by_birthday
|
70
|
+
script/generate view users by_created_on map reduce
|
71
|
+
|
72
|
+
Rake tasks apply the views on the file system to Couch, skipping views that aren't dirty:
|
73
|
+
|
74
|
+
rake couch_tomato:push
|
75
|
+
|
76
|
+
You can also view the differences between the views in Couch and those on the file system:
|
77
|
+
|
78
|
+
rake couch_tomato:diff
|
79
|
+
|
80
|
+
### Remove dynamically generated views
|
81
|
+
|
82
|
+
We almost always need to write JavaScript to get the view behavior we need, and, for both conceptual and implementation complexity reasons, we value having all the views contained in one place--the file system. This also simplifies deployment and collaboration workflows.
|
83
|
+
|
84
|
+
### Multiple design documents per database
|
85
|
+
|
86
|
+
CouchDB supports multiple design documents per database. There's an important semantic consideration: all views in a design document are updated if any one view needs to be updated. To improve the read performance of couch views under high-volume reads and writes, you could organize views that don't need to be as timely into a separate design document named 'lazy', and always include the `stale=true` couch option in queries to views defined in the 'lazy' design document. You could then have a script that ran periodically to trigger the 'lazy' views to update.
|
87
|
+
|
88
|
+
class UserDb > CouchTomato::Database
|
89
|
+
name :users
|
90
|
+
|
91
|
+
view :by_created_at, User
|
92
|
+
view :count # raw
|
93
|
+
view 'lazy/count_created_by_date'
|
94
|
+
end
|
95
|
+
|
96
|
+
We have not had a use for this, nor have we demonstrated that the claimed performance benefit actually exists (it originated from the CouchDB docs, wiki or list or some-such). But, it is instance where this approach to representing views maps fairly directly to CouchDB functionality.
|
data/init.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
class Object
|
2
|
+
# Can you safely .dup this object?
|
3
|
+
# False for nil, false, true, symbols, and numbers; true otherwise.
|
4
|
+
def duplicable?
|
5
|
+
true
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
class NilClass #:nodoc:
|
10
|
+
def duplicable?
|
11
|
+
false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
class FalseClass #:nodoc:
|
16
|
+
def duplicable?
|
17
|
+
false
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
class TrueClass #:nodoc:
|
22
|
+
def duplicable?
|
23
|
+
false
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
class Symbol #:nodoc:
|
28
|
+
def duplicable?
|
29
|
+
false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
class Numeric #:nodoc:
|
34
|
+
def duplicable?
|
35
|
+
false
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
class Class #:nodoc:
|
40
|
+
def duplicable?
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
class Array
|
2
|
+
# Extracts options from a set of arguments. Removes and returns the last
|
3
|
+
# element in the array if it's a hash, otherwise returns a blank hash.
|
4
|
+
#
|
5
|
+
# def options(*args)
|
6
|
+
# args.extract_options!
|
7
|
+
# end
|
8
|
+
#
|
9
|
+
# options(1, 2) # => {}
|
10
|
+
# options(1, 2, :a => :b) # => {:a=>:b}
|
11
|
+
def extract_options!
|
12
|
+
last.is_a?(::Hash) ? pop : {}
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,222 @@
|
|
1
|
+
|
2
|
+
# Retain for backward compatibility. Methods are now included in Class.
|
3
|
+
module ClassInheritableAttributes # :nodoc:
|
4
|
+
end
|
5
|
+
|
6
|
+
# Allows attributes to be shared within an inheritance hierarchy, but where each descendant gets a copy of
|
7
|
+
# their parents' attributes, instead of just a pointer to the same. This means that the child can add elements
|
8
|
+
# to, for example, an array without those additions being shared with either their parent, siblings, or
|
9
|
+
# children, which is unlike the regular class-level attributes that are shared across the entire hierarchy.
|
10
|
+
class Class # :nodoc:
|
11
|
+
def class_inheritable_reader(*syms)
|
12
|
+
syms.each do |sym|
|
13
|
+
next if sym.is_a?(Hash)
|
14
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
15
|
+
def self.#{sym} # def self.after_add
|
16
|
+
read_inheritable_attribute(:#{sym}) # read_inheritable_attribute(:after_add)
|
17
|
+
end # end
|
18
|
+
|
19
|
+
def #{sym} # def after_add
|
20
|
+
self.class.#{sym} # self.class.after_add
|
21
|
+
end # end
|
22
|
+
EOS
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def class_inheritable_writer(*syms)
|
27
|
+
options = syms.extract_options!
|
28
|
+
syms.each do |sym|
|
29
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
30
|
+
def self.#{sym}=(obj) # def self.color=(obj)
|
31
|
+
write_inheritable_attribute(:#{sym}, obj) # write_inheritable_attribute(:color, obj)
|
32
|
+
end # end
|
33
|
+
#
|
34
|
+
#{" #
|
35
|
+
def #{sym}=(obj) # def color=(obj)
|
36
|
+
self.class.#{sym} = obj # self.class.color = obj
|
37
|
+
end # end
|
38
|
+
" unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false
|
39
|
+
EOS
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def class_inheritable_array_writer(*syms)
|
44
|
+
options = syms.extract_options!
|
45
|
+
syms.each do |sym|
|
46
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
47
|
+
def self.#{sym}=(obj) # def self.levels=(obj)
|
48
|
+
write_inheritable_array(:#{sym}, obj) # write_inheritable_array(:levels, obj)
|
49
|
+
end # end
|
50
|
+
#
|
51
|
+
#{" #
|
52
|
+
def #{sym}=(obj) # def levels=(obj)
|
53
|
+
self.class.#{sym} = obj # self.class.levels = obj
|
54
|
+
end # end
|
55
|
+
" unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false
|
56
|
+
EOS
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def class_inheritable_hash_writer(*syms)
|
61
|
+
options = syms.extract_options!
|
62
|
+
syms.each do |sym|
|
63
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
64
|
+
def self.#{sym}=(obj) # def self.nicknames=(obj)
|
65
|
+
write_inheritable_hash(:#{sym}, obj) # write_inheritable_hash(:nicknames, obj)
|
66
|
+
end # end
|
67
|
+
#
|
68
|
+
#{" #
|
69
|
+
def #{sym}=(obj) # def nicknames=(obj)
|
70
|
+
self.class.#{sym} = obj # self.class.nicknames = obj
|
71
|
+
end # end
|
72
|
+
" unless options[:instance_writer] == false } # # the writer above is generated unless options[:instance_writer] == false
|
73
|
+
EOS
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def class_inheritable_accessor(*syms)
|
78
|
+
class_inheritable_reader(*syms)
|
79
|
+
class_inheritable_writer(*syms)
|
80
|
+
end
|
81
|
+
|
82
|
+
def class_inheritable_array(*syms)
|
83
|
+
class_inheritable_reader(*syms)
|
84
|
+
class_inheritable_array_writer(*syms)
|
85
|
+
end
|
86
|
+
|
87
|
+
def class_inheritable_hash(*syms)
|
88
|
+
class_inheritable_reader(*syms)
|
89
|
+
class_inheritable_hash_writer(*syms)
|
90
|
+
end
|
91
|
+
|
92
|
+
def inheritable_attributes
|
93
|
+
@inheritable_attributes ||= EMPTY_INHERITABLE_ATTRIBUTES
|
94
|
+
end
|
95
|
+
|
96
|
+
def write_inheritable_attribute(key, value)
|
97
|
+
if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
|
98
|
+
@inheritable_attributes = {}
|
99
|
+
end
|
100
|
+
inheritable_attributes[key] = value
|
101
|
+
end
|
102
|
+
|
103
|
+
def write_inheritable_array(key, elements)
|
104
|
+
write_inheritable_attribute(key, []) if read_inheritable_attribute(key).nil?
|
105
|
+
write_inheritable_attribute(key, read_inheritable_attribute(key) + elements)
|
106
|
+
end
|
107
|
+
|
108
|
+
def write_inheritable_hash(key, hash)
|
109
|
+
write_inheritable_attribute(key, {}) if read_inheritable_attribute(key).nil?
|
110
|
+
write_inheritable_attribute(key, read_inheritable_attribute(key).merge(hash))
|
111
|
+
end
|
112
|
+
|
113
|
+
def read_inheritable_attribute(key)
|
114
|
+
inheritable_attributes[key]
|
115
|
+
end
|
116
|
+
|
117
|
+
def reset_inheritable_attributes
|
118
|
+
@inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
|
119
|
+
end
|
120
|
+
|
121
|
+
private
|
122
|
+
# Prevent this constant from being created multiple times
|
123
|
+
EMPTY_INHERITABLE_ATTRIBUTES = {}.freeze unless const_defined?(:EMPTY_INHERITABLE_ATTRIBUTES)
|
124
|
+
|
125
|
+
def inherited_with_inheritable_attributes(child)
|
126
|
+
inherited_without_inheritable_attributes(child) if respond_to?(:inherited_without_inheritable_attributes)
|
127
|
+
|
128
|
+
if inheritable_attributes.equal?(EMPTY_INHERITABLE_ATTRIBUTES)
|
129
|
+
new_inheritable_attributes = EMPTY_INHERITABLE_ATTRIBUTES
|
130
|
+
else
|
131
|
+
new_inheritable_attributes = inheritable_attributes.inject({}) do |memo, (key, value)|
|
132
|
+
memo.update(key => value.duplicable? ? value.dup : value)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
child.instance_variable_set('@inheritable_attributes', new_inheritable_attributes)
|
137
|
+
end
|
138
|
+
|
139
|
+
alias inherited_without_inheritable_attributes inherited
|
140
|
+
alias inherited inherited_with_inheritable_attributes
|
141
|
+
end
|
142
|
+
|
143
|
+
class Class
|
144
|
+
# Defines class-level inheritable attribute reader. Attributes are available to subclasses,
|
145
|
+
# each subclass has a copy of parent's attribute.
|
146
|
+
#
|
147
|
+
# @param *syms<Array[#to_s]> Array of attributes to define inheritable reader for.
|
148
|
+
# @return <Array[#to_s]> Array of attributes converted into inheritable_readers.
|
149
|
+
#
|
150
|
+
# @api public
|
151
|
+
#
|
152
|
+
# @todo Do we want to block instance_reader via :instance_reader => false
|
153
|
+
# @todo It would be preferable that we do something with a Hash passed in
|
154
|
+
# (error out or do the same as other methods above) instead of silently
|
155
|
+
# moving on). In particular, this makes the return value of this function
|
156
|
+
# less useful.
|
157
|
+
def extlib_inheritable_reader(*ivars)
|
158
|
+
instance_reader = ivars.pop[:reader] if ivars.last.is_a?(Hash)
|
159
|
+
|
160
|
+
ivars.each do |ivar|
|
161
|
+
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
162
|
+
def self.#{ivar}
|
163
|
+
return @#{ivar} if self.object_id == #{self.object_id} || defined?(@#{ivar})
|
164
|
+
ivar = superclass.#{ivar}
|
165
|
+
return nil if ivar.nil? && !#{self}.instance_variable_defined?("@#{ivar}")
|
166
|
+
@#{ivar} = ivar && !ivar.is_a?(Module) && !ivar.is_a?(Numeric) && !ivar.is_a?(TrueClass) && !ivar.is_a?(FalseClass) ? ivar.dup : ivar
|
167
|
+
end
|
168
|
+
RUBY
|
169
|
+
unless instance_reader == false
|
170
|
+
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
171
|
+
def #{ivar}
|
172
|
+
self.class.#{ivar}
|
173
|
+
end
|
174
|
+
RUBY
|
175
|
+
end
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
179
|
+
# Defines class-level inheritable attribute writer. Attributes are available to subclasses,
|
180
|
+
# each subclass has a copy of parent's attribute.
|
181
|
+
#
|
182
|
+
# @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
|
183
|
+
# define inheritable writer for.
|
184
|
+
# @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
|
185
|
+
# @return <Array[#to_s]> An Array of the attributes that were made into inheritable writers.
|
186
|
+
#
|
187
|
+
# @api public
|
188
|
+
#
|
189
|
+
# @todo We need a style for class_eval <<-HEREDOC. I'd like to make it
|
190
|
+
# class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere.
|
191
|
+
def extlib_inheritable_writer(*ivars)
|
192
|
+
instance_writer = ivars.pop[:writer] if ivars.last.is_a?(Hash)
|
193
|
+
ivars.each do |ivar|
|
194
|
+
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
195
|
+
def self.#{ivar}=(obj)
|
196
|
+
@#{ivar} = obj
|
197
|
+
end
|
198
|
+
RUBY
|
199
|
+
unless instance_writer == false
|
200
|
+
self.class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
201
|
+
def #{ivar}=(obj) self.class.#{ivar} = obj end
|
202
|
+
RUBY
|
203
|
+
end
|
204
|
+
|
205
|
+
self.send("#{ivar}=", yield) if block_given?
|
206
|
+
end
|
207
|
+
end
|
208
|
+
|
209
|
+
# Defines class-level inheritable attribute accessor. Attributes are available to subclasses,
|
210
|
+
# each subclass has a copy of parent's attribute.
|
211
|
+
#
|
212
|
+
# @param *syms<Array[*#to_s, Hash{:instance_writer => Boolean}]> Array of attributes to
|
213
|
+
# define inheritable accessor for.
|
214
|
+
# @option syms :instance_writer<Boolean> if true, instance-level inheritable attribute writer is defined.
|
215
|
+
# @return <Array[#to_s]> An Array of attributes turned into inheritable accessors.
|
216
|
+
#
|
217
|
+
# @api public
|
218
|
+
def extlib_inheritable_accessor(*syms, &block)
|
219
|
+
extlib_inheritable_reader(*syms)
|
220
|
+
extlib_inheritable_writer(*syms, &block)
|
221
|
+
end
|
222
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module ActiveSupportMethods
|
2
|
+
def camelize
|
3
|
+
sub(/^([a-z])/) {$1.upcase}.gsub(/_([a-z])/) do
|
4
|
+
$1.upcase
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
# Source
|
9
|
+
# http://github.com/rails/rails/blob/b600bf2cd728c90d50cc34456c944b2dfefe8c8d/activesupport/lib/active_support/inflector.rb
|
10
|
+
def underscore
|
11
|
+
gsub(/::/, '/').
|
12
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
13
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
14
|
+
tr("-", "_").
|
15
|
+
downcase
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
String.send :include, ActiveSupportMethods unless String.new.respond_to?(:underscore)
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# taken from ActiveSupport 2.3.2
|
2
|
+
unless :to_proc.respond_to?(:to_proc)
|
3
|
+
class Symbol
|
4
|
+
# Turns the symbol into a simple proc, which is especially useful for enumerations. Examples:
|
5
|
+
#
|
6
|
+
# # The same as people.collect { |p| p.name }
|
7
|
+
# people.collect(&:name)
|
8
|
+
#
|
9
|
+
# # The same as people.select { |p| p.manager? }.collect { |p| p.salary }
|
10
|
+
# people.select(&:manager?).collect(&:salary)
|
11
|
+
def to_proc
|
12
|
+
Proc.new { |*args| args.shift.__send__(self, *args) }
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
class Time
|
2
|
+
def to_json(*a)
|
3
|
+
self.utc
|
4
|
+
%("#{strftime("%Y/%m/%d %H:%M:%S +0000")}")
|
5
|
+
end
|
6
|
+
|
7
|
+
def self.json_create string
|
8
|
+
return nil if string.nil?
|
9
|
+
d = DateTime.parse(string).new_offset
|
10
|
+
self.utc(d.year, d.month, d.day, d.hour, d.min, d.sec)
|
11
|
+
end
|
12
|
+
end
|