supermodel 0.0.1 → 0.1.6
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/MIT-LICENSE +20 -0
- data/README +21 -8
- data/Rakefile +2 -2
- data/VERSION +1 -1
- data/lib/supermodel/association.rb +51 -0
- data/lib/supermodel/base.rb +111 -28
- data/lib/supermodel/callbacks.rb +7 -4
- data/lib/supermodel/dirty.rb +24 -0
- data/lib/supermodel/ext/array.rb +7 -0
- data/lib/supermodel/marshal.rb +91 -0
- data/lib/supermodel/redis.rb +167 -0
- data/lib/supermodel/timestamp.rb +53 -0
- data/lib/supermodel/validations/uniqueness.rb +40 -0
- data/lib/supermodel/validations.rb +3 -1
- data/lib/supermodel.rb +16 -12
- data/supermodel.gemspec +15 -9
- metadata +41 -43
- data/.gitignore +0 -2
- data/lib/supermodel/persist.rb +0 -56
- data/lib/supermodel/scriber.rb +0 -51
data/MIT-LICENSE
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
Copyright (c) 2010 Alexander MacCaw (info@eribium.org)
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
|
4
|
+
a copy of this software and associated documentation files (the
|
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
|
9
|
+
the following conditions:
|
|
10
|
+
|
|
11
|
+
The above copyright notice and this permission notice shall be
|
|
12
|
+
included in all copies or substantial portions of the Software.
|
|
13
|
+
|
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
Simple in-memory database using ActiveModel.
|
|
3
3
|
|
|
4
|
-
Primarily developed for Bowline
|
|
4
|
+
Primarily developed for Bowline applications.
|
|
5
5
|
http://github.com/maccman/bowline
|
|
6
6
|
|
|
7
7
|
Supports:
|
|
@@ -10,7 +10,8 @@ Supports:
|
|
|
10
10
|
* Callbacks
|
|
11
11
|
* Observers
|
|
12
12
|
* Dirty (Changes)
|
|
13
|
-
*
|
|
13
|
+
* Ruby Marshalling to disk
|
|
14
|
+
* Redis
|
|
14
15
|
|
|
15
16
|
Examples:
|
|
16
17
|
|
|
@@ -37,15 +38,27 @@ You can use a random ID rather than the object ID:
|
|
|
37
38
|
t = Test.create(:name => "test")
|
|
38
39
|
t.id #=> "7ee935377bb4aecc54ad4f9126"
|
|
39
40
|
|
|
40
|
-
You can
|
|
41
|
+
You can marshal objects to disk on startup/shutdown
|
|
41
42
|
|
|
42
43
|
class Test < SuperModel::Base
|
|
43
|
-
include SuperModel::
|
|
44
|
+
include SuperModel::Marshal::Model
|
|
44
45
|
end
|
|
45
46
|
|
|
46
|
-
SuperModel::
|
|
47
|
-
SuperModel::
|
|
47
|
+
SuperModel::Marshal.path = "dump.db"
|
|
48
|
+
SuperModel::Marshal.load
|
|
48
49
|
|
|
49
50
|
at_exit {
|
|
50
|
-
SuperModel::
|
|
51
|
-
}
|
|
51
|
+
SuperModel::Marshal.dump
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
You can use Redis, you need the Redis gem installed:
|
|
55
|
+
|
|
56
|
+
require "redis"
|
|
57
|
+
class Test < SuperModel::Base
|
|
58
|
+
include SuperModel::Redis::Model
|
|
59
|
+
|
|
60
|
+
attributes :name
|
|
61
|
+
indexes :name
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
Test.find_or_create_by_name("foo")
|
data/Rakefile
CHANGED
|
@@ -7,8 +7,8 @@ begin
|
|
|
7
7
|
gemspec.homepage = "http://github.com/maccman/supermodel"
|
|
8
8
|
gemspec.description = "In memory DB using ActiveModel"
|
|
9
9
|
gemspec.authors = ["Alex MacCaw"]
|
|
10
|
-
gemspec.add_dependency("activemodel")
|
|
10
|
+
gemspec.add_dependency("activemodel", "~> 3.0.0")
|
|
11
11
|
end
|
|
12
12
|
rescue LoadError
|
|
13
13
|
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
|
14
|
-
end
|
|
14
|
+
end
|
data/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.1.6
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
require "active_support/core_ext/string/inflections.rb"
|
|
2
|
+
|
|
3
|
+
module SuperModel
|
|
4
|
+
module Association
|
|
5
|
+
module ClassMethods
|
|
6
|
+
def belongs_to(to_model, options = {})
|
|
7
|
+
to_model = to_model.to_s
|
|
8
|
+
class_name = options[:class_name] || to_model.classify
|
|
9
|
+
foreign_key = options[:foreign_key] || "#{to_model}_id"
|
|
10
|
+
primary_key = options[:primary_key] || "id"
|
|
11
|
+
|
|
12
|
+
attributes foreign_key
|
|
13
|
+
|
|
14
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
|
15
|
+
def #{to_model} # def user
|
|
16
|
+
#{foreign_key} && #{class_name}.find(#{foreign_key}) # user_id && User.find(user_id)
|
|
17
|
+
end # end
|
|
18
|
+
#
|
|
19
|
+
def #{to_model}? # def user?
|
|
20
|
+
#{foreign_key} && #{class_name}.exists?(#{foreign_key}) # user_id && User.exists?(user_id)
|
|
21
|
+
end # end
|
|
22
|
+
#
|
|
23
|
+
def #{to_model}=(object) # def user=(model)
|
|
24
|
+
self.#{foreign_key} = (object && object.#{primary_key}) # self.user_id = (model && model.id)
|
|
25
|
+
end # end
|
|
26
|
+
EOS
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def has_many(to_model, options = {})
|
|
30
|
+
to_model = to_model.to_s
|
|
31
|
+
class_name = options[:class_name] || to_model.classify
|
|
32
|
+
foreign_key = options[:foreign_key] || "#{model_name.singular}_id"
|
|
33
|
+
primary_key = options[:primary_key] || "id"
|
|
34
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
|
35
|
+
def #{to_model} # def user
|
|
36
|
+
#{class_name}.find_all_by_attribute( # User.find_all_by_attribute(
|
|
37
|
+
:#{foreign_key}, # :task_id,
|
|
38
|
+
#{primary_key} # id
|
|
39
|
+
) # )
|
|
40
|
+
end # end
|
|
41
|
+
EOS
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
module Model
|
|
46
|
+
def self.included(base)
|
|
47
|
+
base.extend(ClassMethods)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
end
|
data/lib/supermodel/base.rb
CHANGED
|
@@ -1,16 +1,41 @@
|
|
|
1
1
|
module SuperModel
|
|
2
2
|
class Base
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
class_attribute :known_attributes
|
|
4
|
+
self.known_attributes = []
|
|
5
|
+
|
|
5
6
|
class << self
|
|
6
|
-
|
|
7
|
+
attr_accessor(:primary_key) #:nodoc:
|
|
8
|
+
|
|
9
|
+
def primary_key
|
|
10
|
+
@primary_key ||= 'id'
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def collection(&block)
|
|
14
|
+
@collection ||= Class.new(Array)
|
|
15
|
+
@collection.class_eval(&block) if block_given?
|
|
16
|
+
@collection
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def attributes(*attributes)
|
|
20
|
+
self.known_attributes |= attributes.map(&:to_s)
|
|
21
|
+
end
|
|
7
22
|
|
|
8
23
|
def records
|
|
9
|
-
@records ||=
|
|
24
|
+
@records ||= {}
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def find_by_attribute(name, value) #:nodoc:
|
|
28
|
+
item = records.values.find {|r| r.send(name) == value }
|
|
29
|
+
item && item.dup
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def find_all_by_attribute(name, value) #:nodoc:
|
|
33
|
+
items = records.values.select {|r| r.send(name) == value }
|
|
34
|
+
collection.new(items.deep_dup)
|
|
10
35
|
end
|
|
11
36
|
|
|
12
37
|
def raw_find(id) #:nodoc:
|
|
13
|
-
records
|
|
38
|
+
records[id] || raise(UnknownRecord, "Couldn't find #{self.name} with ID=#{id}")
|
|
14
39
|
end
|
|
15
40
|
|
|
16
41
|
# Find record by ID, or raise.
|
|
@@ -21,25 +46,33 @@ module SuperModel
|
|
|
21
46
|
alias :[] :find
|
|
22
47
|
|
|
23
48
|
def first
|
|
24
|
-
item = records[0]
|
|
49
|
+
item = records.values[0]
|
|
25
50
|
item && item.dup
|
|
26
51
|
end
|
|
27
52
|
|
|
28
53
|
def last
|
|
29
|
-
item = records[1]
|
|
54
|
+
item = records.values[-1]
|
|
30
55
|
item && item.dup
|
|
31
56
|
end
|
|
32
57
|
|
|
58
|
+
def exists?(id)
|
|
59
|
+
records.has_key?(id)
|
|
60
|
+
end
|
|
61
|
+
|
|
33
62
|
def count
|
|
34
63
|
records.length
|
|
35
64
|
end
|
|
36
65
|
|
|
37
66
|
def all
|
|
38
|
-
records.
|
|
67
|
+
collection.new(records.values.deep_dup)
|
|
39
68
|
end
|
|
40
69
|
|
|
41
|
-
def
|
|
42
|
-
|
|
70
|
+
def select(&block)
|
|
71
|
+
collection.new(records.values.select(&block).deep_dup)
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def update(id, atts)
|
|
75
|
+
find(id).update_attributes(atts)
|
|
43
76
|
end
|
|
44
77
|
|
|
45
78
|
def destroy(id)
|
|
@@ -49,7 +82,7 @@ module SuperModel
|
|
|
49
82
|
# Removes all records and executes
|
|
50
83
|
# destory callbacks.
|
|
51
84
|
def destroy_all
|
|
52
|
-
|
|
85
|
+
all.each {|r| r.destroy }
|
|
53
86
|
end
|
|
54
87
|
|
|
55
88
|
# Removes all records without executing
|
|
@@ -66,13 +99,21 @@ module SuperModel
|
|
|
66
99
|
rec.save && rec
|
|
67
100
|
end
|
|
68
101
|
|
|
69
|
-
def
|
|
102
|
+
def create!(*args)
|
|
103
|
+
create(*args) || raise(InvalidRecord)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def method_missing(method_symbol, *args) #:nodoc:
|
|
70
107
|
method_name = method_symbol.to_s
|
|
71
108
|
|
|
72
109
|
if method_name =~ /^find_by_(\w+)!/
|
|
73
|
-
send("find_by_#{$1}", *
|
|
110
|
+
send("find_by_#{$1}", *args) || raise(UnknownRecord)
|
|
74
111
|
elsif method_name =~ /^find_by_(\w+)/
|
|
75
|
-
|
|
112
|
+
find_by_attribute($1, args.first)
|
|
113
|
+
elsif method_name =~ /^find_or_create_by_(\w+)/
|
|
114
|
+
send("find_by_#{$1}", *args) || create($1 => args.first)
|
|
115
|
+
elsif method_name =~ /^find_all_by_(\w+)/
|
|
116
|
+
find_all_by_attribute($1, args.first)
|
|
76
117
|
else
|
|
77
118
|
super
|
|
78
119
|
end
|
|
@@ -80,9 +121,17 @@ module SuperModel
|
|
|
80
121
|
end
|
|
81
122
|
|
|
82
123
|
attr_accessor :attributes
|
|
124
|
+
attr_writer :new_record
|
|
125
|
+
|
|
126
|
+
def known_attributes
|
|
127
|
+
self.class.known_attributes | self.attributes.keys.map(&:to_s)
|
|
128
|
+
end
|
|
83
129
|
|
|
84
130
|
def initialize(attributes = {})
|
|
131
|
+
@new_record = true
|
|
85
132
|
@attributes = {}.with_indifferent_access
|
|
133
|
+
@attributes.merge!(known_attributes.inject({}) {|h, n| h[n] = nil; h })
|
|
134
|
+
@changed_attributes = {}
|
|
86
135
|
load(attributes)
|
|
87
136
|
end
|
|
88
137
|
|
|
@@ -96,7 +145,7 @@ module SuperModel
|
|
|
96
145
|
end
|
|
97
146
|
|
|
98
147
|
def new?
|
|
99
|
-
|
|
148
|
+
@new_record || false
|
|
100
149
|
end
|
|
101
150
|
alias :new_record? :new?
|
|
102
151
|
|
|
@@ -125,7 +174,8 @@ module SuperModel
|
|
|
125
174
|
|
|
126
175
|
def dup
|
|
127
176
|
self.class.new.tap do |base|
|
|
128
|
-
base.attributes =
|
|
177
|
+
base.attributes = attributes
|
|
178
|
+
base.new_record = new_record?
|
|
129
179
|
end
|
|
130
180
|
end
|
|
131
181
|
|
|
@@ -140,9 +190,20 @@ module SuperModel
|
|
|
140
190
|
def exists?
|
|
141
191
|
!new?
|
|
142
192
|
end
|
|
193
|
+
alias_method :persisted?, :exists?
|
|
143
194
|
|
|
144
195
|
def load(attributes) #:nodoc:
|
|
145
|
-
|
|
196
|
+
return unless attributes
|
|
197
|
+
attributes.each do |(name, value)|
|
|
198
|
+
self.send("#{name}=".to_sym, value)
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def reload
|
|
203
|
+
return self if new?
|
|
204
|
+
item = self.class.find(id)
|
|
205
|
+
load(item.attributes)
|
|
206
|
+
return self
|
|
146
207
|
end
|
|
147
208
|
|
|
148
209
|
def update_attribute(name, value)
|
|
@@ -154,21 +215,31 @@ module SuperModel
|
|
|
154
215
|
load(attributes) && save
|
|
155
216
|
end
|
|
156
217
|
|
|
218
|
+
def update_attributes!(attributes)
|
|
219
|
+
update_attributes(attributes) || raise(InvalidRecord)
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def has_attribute?(name)
|
|
223
|
+
@attributes.has_key?(name)
|
|
224
|
+
end
|
|
225
|
+
|
|
157
226
|
alias_method :respond_to_without_attributes?, :respond_to?
|
|
158
227
|
|
|
159
228
|
def respond_to?(method, include_priv = false)
|
|
160
229
|
method_name = method.to_s
|
|
161
230
|
if attributes.nil?
|
|
162
231
|
super
|
|
232
|
+
elsif known_attributes.include?(method_name)
|
|
233
|
+
true
|
|
163
234
|
elsif method_name =~ /(?:=|\?)$/ && attributes.include?($`)
|
|
164
235
|
true
|
|
165
236
|
else
|
|
166
237
|
super
|
|
167
238
|
end
|
|
168
239
|
end
|
|
169
|
-
|
|
240
|
+
|
|
170
241
|
def destroy
|
|
171
|
-
|
|
242
|
+
raw_destroy
|
|
172
243
|
self
|
|
173
244
|
end
|
|
174
245
|
|
|
@@ -185,21 +256,29 @@ module SuperModel
|
|
|
185
256
|
object_id
|
|
186
257
|
end
|
|
187
258
|
|
|
259
|
+
def raw_destroy
|
|
260
|
+
self.class.records.delete(self.id)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def raw_create
|
|
264
|
+
self.class.records[self.id] = self.dup
|
|
265
|
+
end
|
|
266
|
+
|
|
188
267
|
def create
|
|
189
268
|
self.id ||= generate_id
|
|
190
|
-
self.
|
|
191
|
-
|
|
269
|
+
self.new_record = false
|
|
270
|
+
raw_create
|
|
271
|
+
self.id
|
|
192
272
|
end
|
|
193
273
|
|
|
194
|
-
def
|
|
274
|
+
def raw_update
|
|
195
275
|
item = self.class.raw_find(id)
|
|
196
276
|
item.load(attributes)
|
|
197
|
-
save_previous_changes
|
|
198
277
|
end
|
|
199
278
|
|
|
200
|
-
def
|
|
201
|
-
|
|
202
|
-
|
|
279
|
+
def update
|
|
280
|
+
raw_update
|
|
281
|
+
true
|
|
203
282
|
end
|
|
204
283
|
|
|
205
284
|
private
|
|
@@ -217,14 +296,18 @@ module SuperModel
|
|
|
217
296
|
end
|
|
218
297
|
else
|
|
219
298
|
return attributes[method_name] if attributes.include?(method_name)
|
|
299
|
+
return nil if known_attributes.include?(method_name)
|
|
220
300
|
super
|
|
221
301
|
end
|
|
222
302
|
end
|
|
223
303
|
end
|
|
224
304
|
|
|
225
305
|
class Base
|
|
226
|
-
extend
|
|
306
|
+
extend ActiveModel::Naming
|
|
227
307
|
include ActiveModel::Conversion
|
|
228
|
-
include
|
|
308
|
+
include ActiveModel::Serializers::JSON
|
|
309
|
+
include ActiveModel::Serializers::Xml
|
|
310
|
+
include Dirty, Observing, Callbacks, Validations
|
|
311
|
+
include Association::Model
|
|
229
312
|
end
|
|
230
313
|
end
|
data/lib/supermodel/callbacks.rb
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
module SuperModel
|
|
2
2
|
module Callbacks
|
|
3
3
|
extend ActiveSupport::Concern
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
|
|
6
5
|
included do
|
|
7
|
-
|
|
6
|
+
instance_eval do
|
|
7
|
+
extend ActiveModel::Callbacks
|
|
8
|
+
define_model_callbacks :create, :save, :update, :destroy
|
|
9
|
+
end
|
|
10
|
+
|
|
8
11
|
%w( create save update destroy ).each do |method|
|
|
9
12
|
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
|
10
13
|
def #{method}_with_callbacks(*args, &block)
|
|
11
14
|
_run_#{method}_callbacks do
|
|
12
|
-
#{method}
|
|
15
|
+
#{method}_without_callbacks(*args, &block)
|
|
13
16
|
end
|
|
14
17
|
end
|
|
15
18
|
EOS
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
module SuperModel
|
|
2
|
+
module Dirty
|
|
3
|
+
extend ActiveSupport::Concern
|
|
4
|
+
include ActiveModel::Dirty
|
|
5
|
+
|
|
6
|
+
included do
|
|
7
|
+
%w( create update ).each do |method|
|
|
8
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
|
9
|
+
def #{method}_with_dirty(*args, &block)
|
|
10
|
+
result = #{method}_without_dirty(*args, &block)
|
|
11
|
+
save_previous_changes
|
|
12
|
+
result
|
|
13
|
+
end
|
|
14
|
+
EOS
|
|
15
|
+
alias_method_chain(method, :dirty)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def save_previous_changes
|
|
20
|
+
@previously_changed = changes
|
|
21
|
+
@changed_attributes.clear
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
require "tempfile"
|
|
2
|
+
require "fileutils"
|
|
3
|
+
|
|
4
|
+
module SuperModel
|
|
5
|
+
module Marshal
|
|
6
|
+
def path
|
|
7
|
+
@path || raise("Provide a path")
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def path=(p)
|
|
11
|
+
@path = p
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def klasses
|
|
15
|
+
@klasses ||= []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def load
|
|
19
|
+
return unless path
|
|
20
|
+
return unless File.exist?(path)
|
|
21
|
+
data = []
|
|
22
|
+
File.open(path, "rb") do |file|
|
|
23
|
+
begin
|
|
24
|
+
data = ::Marshal.load(file)
|
|
25
|
+
rescue => e
|
|
26
|
+
if defined?(Bowline)
|
|
27
|
+
Bowline::Logging.log_error(e)
|
|
28
|
+
end
|
|
29
|
+
# Lots of errors can occur during
|
|
30
|
+
# marshaling - such as EOF etc
|
|
31
|
+
return false
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
data.each do |klass, records|
|
|
35
|
+
klass.marshal_records = records
|
|
36
|
+
end
|
|
37
|
+
true
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def dump
|
|
41
|
+
return unless path
|
|
42
|
+
tmp_file = Tempfile.new("rbdump")
|
|
43
|
+
tmp_file.binmode
|
|
44
|
+
data = klasses.inject({}) {|hash, klass|
|
|
45
|
+
hash[klass] = klass.marshal_records
|
|
46
|
+
hash
|
|
47
|
+
}
|
|
48
|
+
::Marshal.dump(data, tmp_file)
|
|
49
|
+
tmp_file.close
|
|
50
|
+
# Atomic serialization - so we never corrupt the db
|
|
51
|
+
FileUtils.mv(tmp_file.path, path)
|
|
52
|
+
true
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
extend self
|
|
56
|
+
|
|
57
|
+
module Model
|
|
58
|
+
def self.included(base)
|
|
59
|
+
base.extend ClassMethods
|
|
60
|
+
Marshal.klasses << base
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def marshal_dump
|
|
64
|
+
serializable_hash(self.class.marshal)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def marshal_load(atts)
|
|
68
|
+
# Can't call load, since class
|
|
69
|
+
# isn't setup properly
|
|
70
|
+
@attributes = atts.with_indifferent_access
|
|
71
|
+
@changed_attributes = {}
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
module ClassMethods
|
|
75
|
+
def marshal(options = nil)
|
|
76
|
+
@marshal = options if options
|
|
77
|
+
@marshal ||= {}
|
|
78
|
+
end
|
|
79
|
+
alias_method :marshal=, :marshal
|
|
80
|
+
|
|
81
|
+
def marshal_records=(records)
|
|
82
|
+
@records = records
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def marshal_records
|
|
86
|
+
@records
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
module SuperModel
|
|
2
|
+
module Redis
|
|
3
|
+
module ClassMethods
|
|
4
|
+
def self.extended(base)
|
|
5
|
+
base.class_eval do
|
|
6
|
+
class_inheritable_array :indexed_attributes
|
|
7
|
+
self.indexed_attributes = []
|
|
8
|
+
|
|
9
|
+
class_inheritable_hash :redis_options
|
|
10
|
+
self.redis_options = {}
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def namespace
|
|
15
|
+
@namespace ||= self.name.downcase
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def namespace=(namespace)
|
|
19
|
+
@namespace = namespace
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def redis
|
|
23
|
+
@redis ||= ::Redis.connect(redis_options)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def indexes(*indexes)
|
|
27
|
+
self.indexed_attributes += indexes.map(&:to_s)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def redis_key(*args)
|
|
31
|
+
args.unshift(self.namespace)
|
|
32
|
+
args.join(":")
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def find(id)
|
|
36
|
+
if redis.sismember(redis_key, id.to_s)
|
|
37
|
+
existing(:id => id)
|
|
38
|
+
else
|
|
39
|
+
raise UnknownRecord, "Couldn't find #{self.name} with ID=#{id}"
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def first
|
|
44
|
+
item_ids = redis.sort(redis_key, :order => "ASC", :limit => [0, 1])
|
|
45
|
+
item_id = item_ids.first
|
|
46
|
+
item_id && existing(:id => item_id)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def last
|
|
50
|
+
item_ids = redis.sort(redis_key, :order => "DESC", :limit => [0, 1])
|
|
51
|
+
item_id = item_ids.first
|
|
52
|
+
item_id && existing(:id => item_id)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def exists?(id)
|
|
56
|
+
redis.sismember(redis_key, id.to_s)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def count
|
|
60
|
+
redis.scard(redis_key)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def all
|
|
64
|
+
from_ids(redis.sort(redis_key))
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def select
|
|
68
|
+
raise "Not implemented"
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def delete_all
|
|
72
|
+
raise "Not implemented"
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def find_by_attribute(key, value)
|
|
76
|
+
item_ids = redis.sort(redis_key(key, value.to_s))
|
|
77
|
+
item_id = item_ids.first
|
|
78
|
+
item_id && existing(:id => item_id)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def find_all_by_attribute(key, value)
|
|
82
|
+
from_ids(redis.sort(redis_key(key, value.to_s)))
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
protected
|
|
86
|
+
def from_ids(ids)
|
|
87
|
+
ids.map {|id| existing(:id => id) }
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def existing(atts = {})
|
|
91
|
+
item = self.new(atts)
|
|
92
|
+
item.new_record = false
|
|
93
|
+
item.redis_get
|
|
94
|
+
item
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
module InstanceMethods
|
|
99
|
+
protected
|
|
100
|
+
def raw_destroy
|
|
101
|
+
return if new?
|
|
102
|
+
|
|
103
|
+
destroy_indexes
|
|
104
|
+
redis.srem(self.class.redis_key, self.id)
|
|
105
|
+
redis.del(redis_key)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def destroy_indexes
|
|
109
|
+
indexed_attributes.each do |index|
|
|
110
|
+
old_attribute = changes[index].try(:first) || send(index)
|
|
111
|
+
redis.srem(self.class.redis_key(index, old_attribute), id)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def create_indexes
|
|
116
|
+
indexed_attributes.each do |index|
|
|
117
|
+
new_attribute = send(index)
|
|
118
|
+
redis.sadd(self.class.redis_key(index, new_attribute), id)
|
|
119
|
+
end
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def generate_id
|
|
123
|
+
redis.incr(self.class.redis_key(:uid))
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def indexed_attributes
|
|
127
|
+
attributes.keys & self.class.indexed_attributes
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def redis
|
|
131
|
+
self.class.redis
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def redis_key(*args)
|
|
135
|
+
self.class.redis_key(id, *args)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def redis_set
|
|
139
|
+
redis.set(redis_key, serializable_hash.to_json)
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def redis_get
|
|
143
|
+
load(ActiveSupport::JSON.decode(redis.get(redis_key)))
|
|
144
|
+
end
|
|
145
|
+
public :redis_get
|
|
146
|
+
|
|
147
|
+
def raw_create
|
|
148
|
+
redis_set
|
|
149
|
+
create_indexes
|
|
150
|
+
redis.sadd(self.class.redis_key, self.id)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def raw_update
|
|
154
|
+
destroy_indexes
|
|
155
|
+
redis_set
|
|
156
|
+
create_indexes
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
module Model
|
|
161
|
+
def self.included(base)
|
|
162
|
+
base.send :include, InstanceMethods
|
|
163
|
+
base.send :extend, ClassMethods
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
module SuperModel
|
|
2
|
+
module Timestamp
|
|
3
|
+
module Model
|
|
4
|
+
def self.included(base)
|
|
5
|
+
base.class_eval do
|
|
6
|
+
attributes :created_at, :updated_at
|
|
7
|
+
|
|
8
|
+
before_create :set_created_at
|
|
9
|
+
before_save :set_updated_at
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def touch
|
|
14
|
+
set_updated_at
|
|
15
|
+
save!
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def created_at=(time)
|
|
19
|
+
write_attribute(:created_at, parse_time(time))
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def updated_at=(time)
|
|
23
|
+
write_attribute(:updated_at, parse_time(time))
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
def parse_time(time)
|
|
28
|
+
return time unless time.is_a?(String)
|
|
29
|
+
if Time.respond_to?(:zone) && Time.zone
|
|
30
|
+
Time.zone.parse(time)
|
|
31
|
+
else
|
|
32
|
+
Time.parse(time)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def current_time
|
|
37
|
+
if Time.respond_to?(:current)
|
|
38
|
+
Time.current
|
|
39
|
+
else
|
|
40
|
+
Time.now
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def set_created_at
|
|
45
|
+
self.created_at = current_time
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def set_updated_at
|
|
49
|
+
self.updated_at = current_time
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module SuperModel
|
|
2
|
+
module Validations
|
|
3
|
+
class UniquenessValidator < ActiveModel::EachValidator
|
|
4
|
+
attr_reader :klass
|
|
5
|
+
|
|
6
|
+
def validate_each(record, attribute, value)
|
|
7
|
+
alternate = klass.find_by_attribute(attribute, value)
|
|
8
|
+
return unless alternate
|
|
9
|
+
record.errors.add(attribute, "must be unique", :default => options[:message])
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def setup(klass)
|
|
13
|
+
@klass = klass
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module ClassMethods
|
|
18
|
+
|
|
19
|
+
# Validates that the specified attribute is unique.
|
|
20
|
+
# class Person < ActiveRecord::Base
|
|
21
|
+
# validates_uniquness_of :essay
|
|
22
|
+
# end
|
|
23
|
+
#
|
|
24
|
+
# Configuration options:
|
|
25
|
+
# * <tt>:allow_nil</tt> - Attribute may be +nil+; skip validation.
|
|
26
|
+
# * <tt>:allow_blank</tt> - Attribute may be blank; skip validation.
|
|
27
|
+
# * <tt>:message</tt> - The error message to use for a <tt>:minimum</tt>, <tt>:maximum</tt>, or <tt>:is</tt> violation. An alias of the appropriate <tt>too_long</tt>/<tt>too_short</tt>/<tt>wrong_length</tt> message.
|
|
28
|
+
# * <tt>:on</tt> - Specifies when this validation is active (default is <tt>:save</tt>, other options <tt>:create</tt>, <tt>:update</tt>).
|
|
29
|
+
# * <tt>:if</tt> - Specifies a method, proc or string to call to determine if the validation should
|
|
30
|
+
# occur (e.g. <tt>:if => :allow_validation</tt>, or <tt>:if => Proc.new { |user| user.signup_step > 2 }</tt>). The
|
|
31
|
+
# method, proc or string should return or evaluate to a true or false value.
|
|
32
|
+
# * <tt>:unless</tt> - Specifies a method, proc or string to call to determine if the validation should
|
|
33
|
+
# not occur (e.g. <tt>:unless => :skip_validation</tt>, or <tt>:unless => Proc.new { |user| user.signup_step <= 2 }</tt>). The
|
|
34
|
+
# method, proc or string should return or evaluate to a true or false value.
|
|
35
|
+
def validates_uniqueness_of(*attr_names)
|
|
36
|
+
validates_with UniquenessValidator, _merge_attributes(attr_names)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
data/lib/supermodel.rb
CHANGED
|
@@ -2,32 +2,36 @@ gem "activesupport"
|
|
|
2
2
|
gem "activemodel"
|
|
3
3
|
|
|
4
4
|
require "active_support/core_ext/class/attribute_accessors"
|
|
5
|
-
require "active_support/core_ext/class/inheritable_attributes"
|
|
6
5
|
require "active_support/core_ext/hash/indifferent_access"
|
|
7
6
|
require "active_support/core_ext/kernel/reporting"
|
|
8
|
-
require "active_support/core_ext/module/attr_accessor_with_default"
|
|
9
7
|
require "active_support/core_ext/module/delegation"
|
|
10
8
|
require "active_support/core_ext/module/aliasing"
|
|
11
9
|
require "active_support/core_ext/object/blank"
|
|
12
|
-
require "active_support/core_ext/object/misc"
|
|
13
10
|
require "active_support/core_ext/object/try"
|
|
14
11
|
require "active_support/core_ext/object/to_query"
|
|
12
|
+
require "active_support/core_ext/class/attribute"
|
|
13
|
+
require "active_support/json"
|
|
15
14
|
|
|
16
15
|
require "active_model"
|
|
17
16
|
|
|
18
|
-
|
|
19
17
|
module SuperModel
|
|
20
18
|
class SuperModelError < StandardError; end
|
|
21
19
|
class UnknownRecord < SuperModelError; end
|
|
22
20
|
class InvalidRecord < SuperModelError; end
|
|
23
21
|
end
|
|
24
22
|
|
|
25
|
-
|
|
23
|
+
$:.unshift(File.dirname(__FILE__))
|
|
24
|
+
require "supermodel/ext/array"
|
|
26
25
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
26
|
+
module SuperModel
|
|
27
|
+
autoload :Association, "supermodel/association"
|
|
28
|
+
autoload :Callbacks, "supermodel/callbacks"
|
|
29
|
+
autoload :Observing, "supermodel/observing"
|
|
30
|
+
autoload :Marshal, "supermodel/marshal"
|
|
31
|
+
autoload :RandomID, "supermodel/random_id"
|
|
32
|
+
autoload :Timestamp, "supermodel/timestamp"
|
|
33
|
+
autoload :Validations, "supermodel/validations"
|
|
34
|
+
autoload :Dirty, "supermodel/dirty"
|
|
35
|
+
autoload :Redis, "supermodel/redis"
|
|
36
|
+
autoload :Base, "supermodel/base"
|
|
37
|
+
end
|
data/supermodel.gemspec
CHANGED
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
|
|
6
6
|
Gem::Specification.new do |s|
|
|
7
7
|
s.name = %q{supermodel}
|
|
8
|
-
s.version = "0.
|
|
8
|
+
s.version = "0.1.4"
|
|
9
9
|
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
11
|
s.authors = ["Alex MacCaw"]
|
|
12
|
-
s.date = %q{2010-
|
|
12
|
+
s.date = %q{2010-08-30}
|
|
13
13
|
s.description = %q{In memory DB using ActiveModel}
|
|
14
14
|
s.email = %q{info@eribium.org}
|
|
15
15
|
s.extra_rdoc_files = [
|
|
@@ -17,37 +17,43 @@ Gem::Specification.new do |s|
|
|
|
17
17
|
]
|
|
18
18
|
s.files = [
|
|
19
19
|
".gitignore",
|
|
20
|
+
"MIT-LICENSE",
|
|
20
21
|
"README",
|
|
21
22
|
"Rakefile",
|
|
22
23
|
"VERSION",
|
|
23
24
|
"lib/super_model.rb",
|
|
24
25
|
"lib/supermodel.rb",
|
|
26
|
+
"lib/supermodel/association.rb",
|
|
25
27
|
"lib/supermodel/base.rb",
|
|
26
28
|
"lib/supermodel/callbacks.rb",
|
|
29
|
+
"lib/supermodel/dirty.rb",
|
|
30
|
+
"lib/supermodel/ext/array.rb",
|
|
31
|
+
"lib/supermodel/marshal.rb",
|
|
27
32
|
"lib/supermodel/observing.rb",
|
|
28
|
-
"lib/supermodel/persist.rb",
|
|
29
33
|
"lib/supermodel/random_id.rb",
|
|
30
|
-
"lib/supermodel/
|
|
34
|
+
"lib/supermodel/redis.rb",
|
|
35
|
+
"lib/supermodel/timestamp.rb",
|
|
31
36
|
"lib/supermodel/validations.rb",
|
|
37
|
+
"lib/supermodel/validations/uniqueness.rb",
|
|
32
38
|
"supermodel.gemspec"
|
|
33
39
|
]
|
|
34
40
|
s.homepage = %q{http://github.com/maccman/supermodel}
|
|
35
41
|
s.rdoc_options = ["--charset=UTF-8"]
|
|
36
42
|
s.require_paths = ["lib"]
|
|
37
|
-
s.rubygems_version = %q{1.3.
|
|
43
|
+
s.rubygems_version = %q{1.3.7}
|
|
38
44
|
s.summary = %q{In memory DB using ActiveModel}
|
|
39
45
|
|
|
40
46
|
if s.respond_to? :specification_version then
|
|
41
47
|
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
|
42
48
|
s.specification_version = 3
|
|
43
49
|
|
|
44
|
-
if Gem::Version.new(Gem::
|
|
45
|
-
s.add_runtime_dependency(%q<activemodel>, [">= 0"])
|
|
50
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
|
51
|
+
s.add_runtime_dependency(%q<activemodel>, [">= 3.0.0"])
|
|
46
52
|
else
|
|
47
|
-
s.add_dependency(%q<activemodel>, ["
|
|
53
|
+
s.add_dependency(%q<activemodel>, ["~> 3.0.0"])
|
|
48
54
|
end
|
|
49
55
|
else
|
|
50
|
-
s.add_dependency(%q<activemodel>, ["
|
|
56
|
+
s.add_dependency(%q<activemodel>, ["~> 3.0.0"])
|
|
51
57
|
end
|
|
52
58
|
end
|
|
53
59
|
|
metadata
CHANGED
|
@@ -1,77 +1,75 @@
|
|
|
1
|
-
--- !ruby/object:Gem::Specification
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: supermodel
|
|
3
|
-
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.6
|
|
5
|
+
prerelease:
|
|
5
6
|
platform: ruby
|
|
6
|
-
authors:
|
|
7
|
+
authors:
|
|
7
8
|
- Alex MacCaw
|
|
8
9
|
autorequire:
|
|
9
10
|
bindir: bin
|
|
10
11
|
cert_chain: []
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
dependencies:
|
|
15
|
-
- !ruby/object:Gem::Dependency
|
|
12
|
+
date: 2011-08-26 00:00:00.000000000Z
|
|
13
|
+
dependencies:
|
|
14
|
+
- !ruby/object:Gem::Dependency
|
|
16
15
|
name: activemodel
|
|
16
|
+
requirement: &70347445690680 !ruby/object:Gem::Requirement
|
|
17
|
+
none: false
|
|
18
|
+
requirements:
|
|
19
|
+
- - ~>
|
|
20
|
+
- !ruby/object:Gem::Version
|
|
21
|
+
version: 3.0.0
|
|
17
22
|
type: :runtime
|
|
18
|
-
|
|
19
|
-
version_requirements:
|
|
20
|
-
requirements:
|
|
21
|
-
- - ">="
|
|
22
|
-
- !ruby/object:Gem::Version
|
|
23
|
-
version: "0"
|
|
24
|
-
version:
|
|
23
|
+
prerelease: false
|
|
24
|
+
version_requirements: *70347445690680
|
|
25
25
|
description: In memory DB using ActiveModel
|
|
26
26
|
email: info@eribium.org
|
|
27
27
|
executables: []
|
|
28
|
-
|
|
29
28
|
extensions: []
|
|
30
|
-
|
|
31
|
-
extra_rdoc_files:
|
|
29
|
+
extra_rdoc_files:
|
|
32
30
|
- README
|
|
33
|
-
files:
|
|
34
|
-
-
|
|
31
|
+
files:
|
|
32
|
+
- MIT-LICENSE
|
|
35
33
|
- README
|
|
36
34
|
- Rakefile
|
|
37
35
|
- VERSION
|
|
38
36
|
- lib/super_model.rb
|
|
39
37
|
- lib/supermodel.rb
|
|
38
|
+
- lib/supermodel/association.rb
|
|
40
39
|
- lib/supermodel/base.rb
|
|
41
40
|
- lib/supermodel/callbacks.rb
|
|
41
|
+
- lib/supermodel/dirty.rb
|
|
42
|
+
- lib/supermodel/ext/array.rb
|
|
43
|
+
- lib/supermodel/marshal.rb
|
|
42
44
|
- lib/supermodel/observing.rb
|
|
43
|
-
- lib/supermodel/persist.rb
|
|
44
45
|
- lib/supermodel/random_id.rb
|
|
45
|
-
- lib/supermodel/
|
|
46
|
+
- lib/supermodel/redis.rb
|
|
47
|
+
- lib/supermodel/timestamp.rb
|
|
46
48
|
- lib/supermodel/validations.rb
|
|
49
|
+
- lib/supermodel/validations/uniqueness.rb
|
|
47
50
|
- supermodel.gemspec
|
|
48
|
-
has_rdoc: true
|
|
49
51
|
homepage: http://github.com/maccman/supermodel
|
|
50
52
|
licenses: []
|
|
51
|
-
|
|
52
53
|
post_install_message:
|
|
53
|
-
rdoc_options:
|
|
54
|
-
|
|
55
|
-
require_paths:
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
require_paths:
|
|
56
56
|
- lib
|
|
57
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
+
none: false
|
|
59
|
+
requirements:
|
|
60
|
+
- - ! '>='
|
|
61
|
+
- !ruby/object:Gem::Version
|
|
62
|
+
version: '0'
|
|
63
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
64
|
+
none: false
|
|
65
|
+
requirements:
|
|
66
|
+
- - ! '>='
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
69
|
requirements: []
|
|
70
|
-
|
|
71
70
|
rubyforge_project:
|
|
72
|
-
rubygems_version: 1.
|
|
71
|
+
rubygems_version: 1.8.6
|
|
73
72
|
signing_key:
|
|
74
73
|
specification_version: 3
|
|
75
74
|
summary: In memory DB using ActiveModel
|
|
76
75
|
test_files: []
|
|
77
|
-
|
data/.gitignore
DELETED
data/lib/supermodel/persist.rb
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
require "tempfile"
|
|
2
|
-
require "fileutils"
|
|
3
|
-
|
|
4
|
-
module SuperModel
|
|
5
|
-
module Persist
|
|
6
|
-
def path
|
|
7
|
-
@path || raise("Provide a path")
|
|
8
|
-
end
|
|
9
|
-
|
|
10
|
-
def path=(p)
|
|
11
|
-
@path = p
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def klasses
|
|
15
|
-
@klasses ||= []
|
|
16
|
-
end
|
|
17
|
-
|
|
18
|
-
def load
|
|
19
|
-
return unless path
|
|
20
|
-
return unless File.exist?(path)
|
|
21
|
-
records = []
|
|
22
|
-
File.open(path, "rb") {|file|
|
|
23
|
-
records = Marshal.load(file)
|
|
24
|
-
}
|
|
25
|
-
records.each {|r| r.class.records << r }
|
|
26
|
-
true
|
|
27
|
-
end
|
|
28
|
-
|
|
29
|
-
def dump
|
|
30
|
-
return unless path
|
|
31
|
-
tmp_file = Tempfile.new("rbdump")
|
|
32
|
-
tmp_file.binmode
|
|
33
|
-
records = klasses.map {|k| k.records }.flatten
|
|
34
|
-
Marshal.dump(records, tmp_file)
|
|
35
|
-
# Atomic serialization - so we never corrupt the db
|
|
36
|
-
FileUtils.mv(tmp_file.path, path)
|
|
37
|
-
true
|
|
38
|
-
end
|
|
39
|
-
|
|
40
|
-
extend self
|
|
41
|
-
|
|
42
|
-
module Model
|
|
43
|
-
def self.included(base)
|
|
44
|
-
Persist.klasses << base
|
|
45
|
-
end
|
|
46
|
-
|
|
47
|
-
def marshal_dump
|
|
48
|
-
@attributes
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
def marshal_load(attributes)
|
|
52
|
-
@attributes = attributes
|
|
53
|
-
end
|
|
54
|
-
end
|
|
55
|
-
end
|
|
56
|
-
end
|
data/lib/supermodel/scriber.rb
DELETED
|
@@ -1,51 +0,0 @@
|
|
|
1
|
-
module SuperModel
|
|
2
|
-
module Scriber
|
|
3
|
-
def klasses
|
|
4
|
-
@klasses ||= []
|
|
5
|
-
end
|
|
6
|
-
module_function :klasses
|
|
7
|
-
|
|
8
|
-
class Observer < ActiveModel::Observer
|
|
9
|
-
def self.observed_classes
|
|
10
|
-
Scriber.klasses
|
|
11
|
-
end
|
|
12
|
-
|
|
13
|
-
def after_create(rec)
|
|
14
|
-
rec.class.record(:create, rec.attributes)
|
|
15
|
-
end
|
|
16
|
-
|
|
17
|
-
def after_update(rec)
|
|
18
|
-
changed_to = rec.previous_changes.inject({}) {|hash, (key, (from, to))|
|
|
19
|
-
hash[key] = to
|
|
20
|
-
hash
|
|
21
|
-
}
|
|
22
|
-
rec.class.record(:update, changed_to)
|
|
23
|
-
end
|
|
24
|
-
|
|
25
|
-
def after_destroy
|
|
26
|
-
rec.class.record(:destroy, rec.id)
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
module Model
|
|
31
|
-
def self.extended(base)
|
|
32
|
-
Scriber.klasses << base
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def scribe_load(type, data) #:nodoc:
|
|
36
|
-
case type
|
|
37
|
-
when :create then create(data)
|
|
38
|
-
when :destroy then destroy(data)
|
|
39
|
-
when :update then update(data)
|
|
40
|
-
else
|
|
41
|
-
method = "scribe_load_#{type}"
|
|
42
|
-
send(method) if respond_to?(method)
|
|
43
|
-
end
|
|
44
|
-
end
|
|
45
|
-
|
|
46
|
-
def record(type, data)
|
|
47
|
-
::Scriber.record(self, type, data)
|
|
48
|
-
end
|
|
49
|
-
end
|
|
50
|
-
end
|
|
51
|
-
end
|