topmodel 0.2
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.
- checksums.yaml +7 -0
- data/.gitignore +0 -0
- data/MIT-LICENSE +20 -0
- data/README.md +64 -0
- data/Rakefile +14 -0
- data/VERSION +1 -0
- data/lib/top_model.rb +1 -0
- data/lib/topmodel.rb +37 -0
- data/lib/topmodel/association.rb +54 -0
- data/lib/topmodel/base.rb +336 -0
- data/lib/topmodel/callbacks.rb +48 -0
- data/lib/topmodel/dirty.rb +24 -0
- data/lib/topmodel/ext/array.rb +7 -0
- data/lib/topmodel/marshal.rb +91 -0
- data/lib/topmodel/observing.rb +21 -0
- data/lib/topmodel/random_id.rb +9 -0
- data/lib/topmodel/redis.rb +169 -0
- data/lib/topmodel/timestamp.rb +53 -0
- data/lib/topmodel/validations.rb +32 -0
- data/lib/topmodel/validations/uniqueness.rb +40 -0
- data/topmodel.gemspec +59 -0
- metadata +85 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 6b8b321b0771de0cc8f8d29793630c8197e1170b
|
4
|
+
data.tar.gz: 0a5f7fcbea472f2063cc57d8c1977c573a355d5c
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5cfeea8c618607147fbfa39a659e24dd4f2afa33d0d27918cf408806510bcc8f9a335ad6f2083f54b4eee18bbca357ab8800bb137e4da036b226912f0c1e31ed
|
7
|
+
data.tar.gz: d3454ce5cfb68900d8299fca12ee0fe78ef20003326198151bfadcb034c56e59b6e14f9ff0095e51420d348fc009e847793af5471a443ba65f72a9987100b05c
|
data/.gitignore
ADDED
File without changes
|
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.md
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
#TopModel a Rails 4 compatible in-memory database using ActiveModel
|
2
|
+
**This gem is based on topmodel by Alex Maccaw (maccman). It includes some fixes and is updated to be compatible with Rails 4.**
|
3
|
+
|
4
|
+
|
5
|
+
**Primarily developed for Bowline applications.
|
6
|
+
http://github.com/maccman/bowline**
|
7
|
+
|
8
|
+
##Supports:
|
9
|
+
* Serialisation
|
10
|
+
* Validations
|
11
|
+
* Callbacks
|
12
|
+
* Dirty (Changes)
|
13
|
+
* Ruby Marshalling to disk
|
14
|
+
* Redis
|
15
|
+
|
16
|
+
##Examples:
|
17
|
+
|
18
|
+
require "topmodel"
|
19
|
+
|
20
|
+
class Test < TopModel::Base
|
21
|
+
end
|
22
|
+
|
23
|
+
t = Test.new
|
24
|
+
t.name = "foo"
|
25
|
+
t.save #=> true
|
26
|
+
|
27
|
+
Test.all
|
28
|
+
Test.first
|
29
|
+
Test.last
|
30
|
+
Test.find_by_name('foo)
|
31
|
+
|
32
|
+
You can use a random ID rather than the object ID:
|
33
|
+
|
34
|
+
class Test < TopModel::Base
|
35
|
+
include TopModel::RandomID
|
36
|
+
end
|
37
|
+
|
38
|
+
t = Test.create(:name => "test")
|
39
|
+
t.id #=> "7ee935377bb4aecc54ad4f9126"
|
40
|
+
|
41
|
+
You can marshal objects to disk on startup/shutdown
|
42
|
+
|
43
|
+
class Test < TopModel::Base
|
44
|
+
include TopModel::Marshal::Model
|
45
|
+
end
|
46
|
+
|
47
|
+
TopModel::Marshal.path = "dump.db"
|
48
|
+
TopModel::Marshal.load
|
49
|
+
|
50
|
+
at_exit {
|
51
|
+
TopModel::Marshal.dump
|
52
|
+
}
|
53
|
+
|
54
|
+
You can use Redis, you need the Redis gem installed:
|
55
|
+
|
56
|
+
require "redis"
|
57
|
+
class Test < TopModel::Base
|
58
|
+
include TopModel::Redis::Model
|
59
|
+
|
60
|
+
attributes :name
|
61
|
+
indexes :name
|
62
|
+
end
|
63
|
+
|
64
|
+
Test.find_or_create_by_name("foo")
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |gemspec|
|
4
|
+
gemspec.name = "topmodel"
|
5
|
+
gemspec.summary = "In memory DB using ActiveModel"
|
6
|
+
gemspec.email = "info@eribium.org"
|
7
|
+
gemspec.homepage = "http://github.com/maccman/topmodel"
|
8
|
+
gemspec.description = "In memory DB using ActiveModel"
|
9
|
+
gemspec.authors = ["Alex MacCaw", "Alex EH"]
|
10
|
+
gemspec.add_dependency("activemodel", "~> 3.0.0")
|
11
|
+
end
|
12
|
+
rescue LoadError
|
13
|
+
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
14
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.2
|
data/lib/top_model.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), "topmodel")
|
data/lib/topmodel.rb
ADDED
@@ -0,0 +1,37 @@
|
|
1
|
+
gem "activesupport"
|
2
|
+
gem "activemodel"
|
3
|
+
|
4
|
+
require "active_support/core_ext/class/attribute_accessors"
|
5
|
+
require "active_support/core_ext/hash/indifferent_access"
|
6
|
+
require "active_support/core_ext/kernel/reporting"
|
7
|
+
require "active_support/core_ext/module/delegation"
|
8
|
+
require "active_support/core_ext/module/aliasing"
|
9
|
+
require "active_support/core_ext/object/blank"
|
10
|
+
require "active_support/core_ext/object/try"
|
11
|
+
require "active_support/core_ext/object/to_query"
|
12
|
+
require "active_support/core_ext/class/attribute"
|
13
|
+
require "active_support/json"
|
14
|
+
|
15
|
+
require "active_model"
|
16
|
+
|
17
|
+
module TopModel
|
18
|
+
class TopModelError < StandardError; end
|
19
|
+
class UnknownRecord < TopModelError; end
|
20
|
+
class InvalidRecord < TopModelError; end
|
21
|
+
end
|
22
|
+
|
23
|
+
$:.unshift(File.dirname(__FILE__))
|
24
|
+
require "topmodel/ext/array"
|
25
|
+
|
26
|
+
module TopModel
|
27
|
+
autoload :Association, "topmodel/association"
|
28
|
+
autoload :Callbacks, "topmodel/callbacks"
|
29
|
+
#autoload :Observing, "topmodel/observing"
|
30
|
+
autoload :Marshal, "topmodel/marshal"
|
31
|
+
autoload :RandomID, "topmodel/random_id"
|
32
|
+
autoload :Timestamp, "topmodel/timestamp"
|
33
|
+
autoload :Validations, "topmodel/validations"
|
34
|
+
autoload :Dirty, "topmodel/dirty"
|
35
|
+
autoload :Redis, "topmodel/redis"
|
36
|
+
autoload :Base, "topmodel/base"
|
37
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require "active_support/core_ext/string/inflections.rb"
|
2
|
+
|
3
|
+
module TopModel
|
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
|
+
self.belongs_assoc[to_model] = {:class_name => class_name, :foreign_key => foreign_key, :primary_key => primary_key, :name => to_model}
|
14
|
+
|
15
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
16
|
+
def #{to_model} # def user
|
17
|
+
#{foreign_key} && #{class_name}.find(#{foreign_key}) # user_id && User.find(user_id)
|
18
|
+
end # end
|
19
|
+
#
|
20
|
+
def #{to_model}? # def user?
|
21
|
+
#{foreign_key} && #{class_name}.exists?(#{foreign_key}) # user_id && User.exists?(user_id)
|
22
|
+
end # end
|
23
|
+
#
|
24
|
+
def #{to_model}=(object) # def user=(model)
|
25
|
+
self.#{foreign_key} = (object && object.#{primary_key}) # self.user_id = (model && model.id)
|
26
|
+
end # end
|
27
|
+
EOS
|
28
|
+
end
|
29
|
+
|
30
|
+
def has_many(to_model, options = {})
|
31
|
+
to_model = to_model.to_s
|
32
|
+
class_name = options[:class_name] || to_model.classify
|
33
|
+
foreign_key = options[:foreign_key] || "#{model_name.singular}_id"
|
34
|
+
primary_key = options[:primary_key] || "id"
|
35
|
+
self.has_many_assoc[to_model] = {:class_name => class_name, :foreign_key => foreign_key, :primary_key => primary_key, :name => to_model}
|
36
|
+
|
37
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
38
|
+
def #{to_model} # def user
|
39
|
+
#{class_name}.find_all_by_attribute( # User.find_all_by_attribute(
|
40
|
+
:#{foreign_key}, # :task_id,
|
41
|
+
#{primary_key} # id
|
42
|
+
) # )
|
43
|
+
end # end
|
44
|
+
EOS
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module Model
|
49
|
+
def self.included(base)
|
50
|
+
base.extend(ClassMethods)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,336 @@
|
|
1
|
+
module TopModel
|
2
|
+
class Base
|
3
|
+
class_attribute :known_attributes
|
4
|
+
self.known_attributes = []
|
5
|
+
|
6
|
+
class << self
|
7
|
+
attr_accessor(:primary_key) #:nodoc:
|
8
|
+
|
9
|
+
def belongs_assoc
|
10
|
+
@belongs_assoc ||= {}
|
11
|
+
end
|
12
|
+
|
13
|
+
def has_many_assoc
|
14
|
+
@has_many_assoc ||= {}
|
15
|
+
end
|
16
|
+
|
17
|
+
def primary_key
|
18
|
+
@primary_key ||= 'id'
|
19
|
+
end
|
20
|
+
|
21
|
+
def collection(&block)
|
22
|
+
@collection ||= Class.new(Array)
|
23
|
+
@collection.class_eval(&block) if block_given?
|
24
|
+
@collection
|
25
|
+
end
|
26
|
+
|
27
|
+
def attributes(*attributes)
|
28
|
+
self.known_attributes |= attributes.map(&:to_s)
|
29
|
+
end
|
30
|
+
|
31
|
+
def records
|
32
|
+
@records ||= {}
|
33
|
+
end
|
34
|
+
|
35
|
+
def find_by_attribute(name, value) #:nodoc:
|
36
|
+
item = records.values.find {|r| r.send(name) == value }
|
37
|
+
item && item.dup
|
38
|
+
end
|
39
|
+
|
40
|
+
def find_all_by_attribute(name, value) #:nodoc:
|
41
|
+
items = records.values.select {|r| r.send(name) == value }
|
42
|
+
collection.new(items.deep_dup)
|
43
|
+
end
|
44
|
+
|
45
|
+
def raw_find(id) #:nodoc:
|
46
|
+
records[id] || raise(UnknownRecord, "Couldn't find #{self.name} with ID=#{id}")
|
47
|
+
end
|
48
|
+
|
49
|
+
# Find record by ID, or raise.
|
50
|
+
def find(id)
|
51
|
+
item = raw_find(id)
|
52
|
+
item && item.dup
|
53
|
+
end
|
54
|
+
alias :[] :find
|
55
|
+
|
56
|
+
def first
|
57
|
+
item = records.values[0]
|
58
|
+
item && item.dup
|
59
|
+
end
|
60
|
+
|
61
|
+
def last
|
62
|
+
item = records.values[-1]
|
63
|
+
item && item.dup
|
64
|
+
end
|
65
|
+
|
66
|
+
def where(options)
|
67
|
+
items = records.values.select do |r|
|
68
|
+
options.all? do |k, v|
|
69
|
+
if v.is_a?(Enumerable)
|
70
|
+
v.include?(r.send(k))
|
71
|
+
else
|
72
|
+
r.send(k) == v
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
collection.new(items.deep_dup)
|
77
|
+
end
|
78
|
+
|
79
|
+
def exists?(id)
|
80
|
+
records.has_key?(id)
|
81
|
+
end
|
82
|
+
|
83
|
+
def count
|
84
|
+
records.length
|
85
|
+
end
|
86
|
+
|
87
|
+
def all
|
88
|
+
collection.new(records.values.deep_dup)
|
89
|
+
end
|
90
|
+
|
91
|
+
def select(&block)
|
92
|
+
collection.new(records.values.select(&block).deep_dup)
|
93
|
+
end
|
94
|
+
|
95
|
+
def update(id, atts)
|
96
|
+
find(id).update_attributes(atts)
|
97
|
+
end
|
98
|
+
|
99
|
+
def destroy(id)
|
100
|
+
find(id).destroy
|
101
|
+
end
|
102
|
+
|
103
|
+
# Removes all records and executes
|
104
|
+
# destroy callbacks.
|
105
|
+
def destroy_all
|
106
|
+
all.each {|r| r.destroy }
|
107
|
+
end
|
108
|
+
|
109
|
+
# Removes all records without executing
|
110
|
+
# destroy callbacks.
|
111
|
+
def delete_all
|
112
|
+
records.clear
|
113
|
+
end
|
114
|
+
|
115
|
+
# Create a new record.
|
116
|
+
# Example:
|
117
|
+
# create(:name => "foo", :id => 1)
|
118
|
+
def create(atts = {})
|
119
|
+
rec = self.new(atts)
|
120
|
+
rec.save && rec
|
121
|
+
end
|
122
|
+
|
123
|
+
def create!(*args)
|
124
|
+
create(*args) || raise(InvalidRecord)
|
125
|
+
end
|
126
|
+
|
127
|
+
def method_missing(method_symbol, *args) #:nodoc:
|
128
|
+
method_name = method_symbol.to_s
|
129
|
+
|
130
|
+
if method_name =~ /^find_by_(\w+)!/
|
131
|
+
send("find_by_#{$1}", *args) || raise(UnknownRecord)
|
132
|
+
elsif method_name =~ /^find_by_(\w+)/
|
133
|
+
find_by_attribute($1, args.first)
|
134
|
+
elsif method_name =~ /^find_or_create_by_(\w+)/
|
135
|
+
send("find_by_#{$1}", *args) || create($1 => args.first)
|
136
|
+
elsif method_name =~ /^find_all_by_(\w+)/
|
137
|
+
find_all_by_attribute($1, args.first)
|
138
|
+
else
|
139
|
+
super
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
attr_accessor :attributes
|
145
|
+
attr_writer :new_record
|
146
|
+
|
147
|
+
def known_attributes
|
148
|
+
self.class.known_attributes | self.attributes.keys.map(&:to_s)
|
149
|
+
end
|
150
|
+
|
151
|
+
def initialize(attributes = {})
|
152
|
+
@new_record = true
|
153
|
+
@attributes = {}.with_indifferent_access
|
154
|
+
@attributes.merge!(known_attributes.inject({}) {|h, n| h[n] = nil; h })
|
155
|
+
@changed_attributes = {}
|
156
|
+
load(attributes)
|
157
|
+
end
|
158
|
+
|
159
|
+
def clone
|
160
|
+
cloned = attributes.reject {|k,v| k == self.class.primary_key }
|
161
|
+
cloned = cloned.inject({}) do |attrs, (k, v)|
|
162
|
+
attrs[k] = v.clone
|
163
|
+
attrs
|
164
|
+
end
|
165
|
+
self.class.new(cloned)
|
166
|
+
end
|
167
|
+
|
168
|
+
def new?
|
169
|
+
@new_record || false
|
170
|
+
end
|
171
|
+
alias :new_record? :new?
|
172
|
+
|
173
|
+
# Gets the <tt>\id</tt> attribute of the item.
|
174
|
+
def id
|
175
|
+
attributes[self.class.primary_key]
|
176
|
+
end
|
177
|
+
|
178
|
+
# Sets the <tt>\id</tt> attribute of the item.
|
179
|
+
def id=(id)
|
180
|
+
attributes[self.class.primary_key] = id
|
181
|
+
end
|
182
|
+
|
183
|
+
def ==(other)
|
184
|
+
other.equal?(self) || (other.instance_of?(self.class) && other.id == id)
|
185
|
+
end
|
186
|
+
|
187
|
+
# Tests for equality (delegates to ==).
|
188
|
+
def eql?(other)
|
189
|
+
self == other
|
190
|
+
end
|
191
|
+
|
192
|
+
def hash
|
193
|
+
id.hash
|
194
|
+
end
|
195
|
+
|
196
|
+
def dup
|
197
|
+
self.class.new.tap do |base|
|
198
|
+
base.attributes = attributes
|
199
|
+
base.new_record = new_record?
|
200
|
+
end
|
201
|
+
end
|
202
|
+
|
203
|
+
def save
|
204
|
+
new? ? create : update
|
205
|
+
end
|
206
|
+
|
207
|
+
def save!
|
208
|
+
save || raise(InvalidRecord)
|
209
|
+
end
|
210
|
+
|
211
|
+
def exists?
|
212
|
+
!new?
|
213
|
+
end
|
214
|
+
alias_method :persisted?, :exists?
|
215
|
+
|
216
|
+
def load(attributes) #:nodoc:
|
217
|
+
return unless attributes
|
218
|
+
attributes.each do |(name, value)|
|
219
|
+
self.send("#{name}=".to_sym, value)
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def reload
|
224
|
+
return self if new?
|
225
|
+
item = self.class.find(id)
|
226
|
+
load(item.attributes)
|
227
|
+
return self
|
228
|
+
end
|
229
|
+
|
230
|
+
def update_attribute(name, value)
|
231
|
+
self.send("#{name}=".to_sym, value)
|
232
|
+
self.save
|
233
|
+
end
|
234
|
+
|
235
|
+
def update_attributes(attributes)
|
236
|
+
load(attributes) && save
|
237
|
+
end
|
238
|
+
|
239
|
+
def update_attributes!(attributes)
|
240
|
+
update_attributes(attributes) || raise(InvalidRecord)
|
241
|
+
end
|
242
|
+
|
243
|
+
def has_attribute?(name)
|
244
|
+
@attributes.has_key?(name)
|
245
|
+
end
|
246
|
+
|
247
|
+
alias_method :respond_to_without_attributes?, :respond_to?
|
248
|
+
|
249
|
+
def respond_to?(method, include_priv = false)
|
250
|
+
method_name = method.to_s
|
251
|
+
if attributes.nil?
|
252
|
+
super
|
253
|
+
elsif known_attributes.include?(method_name)
|
254
|
+
true
|
255
|
+
elsif method_name =~ /(?:=|\?)$/ && attributes.include?($`)
|
256
|
+
true
|
257
|
+
else
|
258
|
+
super
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def destroy
|
263
|
+
raw_destroy
|
264
|
+
self
|
265
|
+
end
|
266
|
+
|
267
|
+
protected
|
268
|
+
def read_attribute(name)
|
269
|
+
@attributes[name]
|
270
|
+
end
|
271
|
+
|
272
|
+
def write_attribute(name, value)
|
273
|
+
@attributes[name] = value
|
274
|
+
end
|
275
|
+
|
276
|
+
def generate_id
|
277
|
+
object_id
|
278
|
+
end
|
279
|
+
|
280
|
+
def raw_destroy
|
281
|
+
self.class.records.delete(self.id)
|
282
|
+
end
|
283
|
+
|
284
|
+
def raw_create
|
285
|
+
self.class.records[self.id] = self.dup
|
286
|
+
end
|
287
|
+
|
288
|
+
def create
|
289
|
+
self.id ||= generate_id
|
290
|
+
self.new_record = false
|
291
|
+
raw_create
|
292
|
+
self.id
|
293
|
+
end
|
294
|
+
|
295
|
+
def raw_update
|
296
|
+
item = self.class.raw_find(id)
|
297
|
+
item.load(attributes)
|
298
|
+
end
|
299
|
+
|
300
|
+
def update
|
301
|
+
raw_update
|
302
|
+
true
|
303
|
+
end
|
304
|
+
|
305
|
+
private
|
306
|
+
|
307
|
+
def method_missing(method_symbol, *arguments) #:nodoc:
|
308
|
+
method_name = method_symbol.to_s
|
309
|
+
|
310
|
+
if method_name =~ /(=|\?)$/
|
311
|
+
case $1
|
312
|
+
when "="
|
313
|
+
attribute_will_change!($`)
|
314
|
+
attributes[$`] = arguments.first
|
315
|
+
when "?"
|
316
|
+
attributes[$`]
|
317
|
+
end
|
318
|
+
else
|
319
|
+
return attributes[method_name] if attributes.include?(method_name)
|
320
|
+
return nil if known_attributes.include?(method_name)
|
321
|
+
super
|
322
|
+
end
|
323
|
+
end
|
324
|
+
end
|
325
|
+
|
326
|
+
class Base
|
327
|
+
extend ActiveModel::Naming
|
328
|
+
include ActiveModel::Conversion
|
329
|
+
include ActiveModel::Serializers::JSON
|
330
|
+
include ActiveModel::Serializers::Xml
|
331
|
+
#include Dirty, Observing, Callbacks, Validations
|
332
|
+
include Dirty, Callbacks, Validations
|
333
|
+
|
334
|
+
include Association::Model
|
335
|
+
end
|
336
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
module TopModel
|
2
|
+
module Callbacks
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
instance_eval do
|
7
|
+
extend ActiveModel::Callbacks
|
8
|
+
define_model_callbacks :create, :save, :update, :destroy
|
9
|
+
end
|
10
|
+
|
11
|
+
# Callback Fixes https://raw.githubusercontent.com/locomotivapro/topmodel/33d952c5082245465f9f063a55a743b88df7558d/lib/topmodel/callbacks.rb
|
12
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
13
|
+
def create_with_callbacks(*args, &block)
|
14
|
+
run_callbacks :create do
|
15
|
+
# Your create action methods here
|
16
|
+
create_without_callbacks(*args, &block)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
alias_method_chain(:create, :callbacks)
|
20
|
+
|
21
|
+
def save_with_callbacks(*args, &block)
|
22
|
+
run_callbacks :save do
|
23
|
+
# Your save action methods here
|
24
|
+
save_without_callbacks(*args, &block)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
alias_method_chain(:save, :callbacks)
|
28
|
+
|
29
|
+
def update_with_callbacks(*args, &block)
|
30
|
+
run_callbacks :update do
|
31
|
+
# Your update action methods here
|
32
|
+
update_without_callbacks(*args, &block)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
alias_method_chain(:update, :callbacks)
|
36
|
+
|
37
|
+
def destroy_with_callbacks(*args, &block)
|
38
|
+
run_callbacks :destroy do
|
39
|
+
# Your destroy action methods here
|
40
|
+
destroy_without_callbacks(*args, &block)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
alias_method_chain(:destroy, :callbacks)
|
44
|
+
EOS
|
45
|
+
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module TopModel
|
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 TopModel
|
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,21 @@
|
|
1
|
+
module TopModel
|
2
|
+
module Observing
|
3
|
+
# extend ActiveSupport::Concern
|
4
|
+
# include ActiveModel::Observing
|
5
|
+
|
6
|
+
# included do
|
7
|
+
# %w( create save update destroy ).each do |method|
|
8
|
+
# class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
9
|
+
# def #{method}_with_notifications(*args, &block)
|
10
|
+
# notify_observers(:before_#{method})
|
11
|
+
# if result = #{method}_without_notifications(*args, &block)
|
12
|
+
# notify_observers(:after_#{method})
|
13
|
+
# end
|
14
|
+
# result
|
15
|
+
# end
|
16
|
+
# EOS
|
17
|
+
# alias_method_chain(method, :notifications)
|
18
|
+
# end
|
19
|
+
# end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,169 @@
|
|
1
|
+
module TopModel
|
2
|
+
module Redis
|
3
|
+
module ClassMethods
|
4
|
+
def self.extended(base)
|
5
|
+
base.class_eval do
|
6
|
+
#class_inheritable_array :indexed_attributes
|
7
|
+
class_attribute :indexed_attributes
|
8
|
+
self.indexed_attributes = []
|
9
|
+
|
10
|
+
#class_inheritable_hash :redis_options
|
11
|
+
class_attribute :redis_options
|
12
|
+
self.redis_options = {}
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def namespace
|
17
|
+
@namespace ||= self.name.downcase
|
18
|
+
end
|
19
|
+
|
20
|
+
def namespace=(namespace)
|
21
|
+
@namespace = namespace
|
22
|
+
end
|
23
|
+
|
24
|
+
def redis
|
25
|
+
@redis ||= ::Redis.connect(redis_options)
|
26
|
+
end
|
27
|
+
|
28
|
+
def indexes(*indexes)
|
29
|
+
self.indexed_attributes += indexes.map(&:to_s)
|
30
|
+
end
|
31
|
+
|
32
|
+
def redis_key(*args)
|
33
|
+
args.unshift(self.namespace)
|
34
|
+
args.join(":")
|
35
|
+
end
|
36
|
+
|
37
|
+
def find(id)
|
38
|
+
if redis.sismember(redis_key, id.to_s)
|
39
|
+
existing(:id => id)
|
40
|
+
else
|
41
|
+
raise UnknownRecord, "Couldn't find #{self.name} with ID=#{id}"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def first
|
46
|
+
item_ids = redis.sort(redis_key, :order => "ASC", :limit => [0, 1])
|
47
|
+
item_id = item_ids.first
|
48
|
+
item_id && existing(:id => item_id)
|
49
|
+
end
|
50
|
+
|
51
|
+
def last
|
52
|
+
item_ids = redis.sort(redis_key, :order => "DESC", :limit => [0, 1])
|
53
|
+
item_id = item_ids.first
|
54
|
+
item_id && existing(:id => item_id)
|
55
|
+
end
|
56
|
+
|
57
|
+
def exists?(id)
|
58
|
+
redis.sismember(redis_key, id.to_s)
|
59
|
+
end
|
60
|
+
|
61
|
+
def count
|
62
|
+
redis.scard(redis_key)
|
63
|
+
end
|
64
|
+
|
65
|
+
def all
|
66
|
+
from_ids(redis.sort(redis_key))
|
67
|
+
end
|
68
|
+
|
69
|
+
def select
|
70
|
+
raise "Not implemented"
|
71
|
+
end
|
72
|
+
|
73
|
+
def delete_all
|
74
|
+
raise "Not implemented"
|
75
|
+
end
|
76
|
+
|
77
|
+
def find_by_attribute(key, value)
|
78
|
+
item_ids = redis.sort(redis_key(key, value.to_s))
|
79
|
+
item_id = item_ids.first
|
80
|
+
item_id && existing(:id => item_id)
|
81
|
+
end
|
82
|
+
|
83
|
+
def find_all_by_attribute(key, value)
|
84
|
+
from_ids(redis.sort(redis_key(key, value.to_s)))
|
85
|
+
end
|
86
|
+
|
87
|
+
protected
|
88
|
+
def from_ids(ids)
|
89
|
+
ids.map {|id| existing(:id => id) }
|
90
|
+
end
|
91
|
+
|
92
|
+
def existing(atts = {})
|
93
|
+
item = self.new(atts)
|
94
|
+
item.new_record = false
|
95
|
+
item.redis_get
|
96
|
+
item
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
module InstanceMethods
|
101
|
+
protected
|
102
|
+
def raw_destroy
|
103
|
+
return if new?
|
104
|
+
|
105
|
+
destroy_indexes
|
106
|
+
redis.srem(self.class.redis_key, self.id)
|
107
|
+
redis.del(redis_key)
|
108
|
+
end
|
109
|
+
|
110
|
+
def destroy_indexes
|
111
|
+
indexed_attributes.each do |index|
|
112
|
+
old_attribute = changes[index].try(:first) || send(index)
|
113
|
+
redis.srem(self.class.redis_key(index, old_attribute), id)
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def create_indexes
|
118
|
+
indexed_attributes.each do |index|
|
119
|
+
new_attribute = send(index)
|
120
|
+
redis.sadd(self.class.redis_key(index, new_attribute), id)
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def generate_id
|
125
|
+
redis.incr(self.class.redis_key(:uid))
|
126
|
+
end
|
127
|
+
|
128
|
+
def indexed_attributes
|
129
|
+
attributes.keys & self.class.indexed_attributes
|
130
|
+
end
|
131
|
+
|
132
|
+
def redis
|
133
|
+
self.class.redis
|
134
|
+
end
|
135
|
+
|
136
|
+
def redis_key(*args)
|
137
|
+
self.class.redis_key(id, *args)
|
138
|
+
end
|
139
|
+
|
140
|
+
def redis_set
|
141
|
+
redis.set(redis_key, serializable_hash.to_json)
|
142
|
+
end
|
143
|
+
|
144
|
+
def redis_get
|
145
|
+
load(ActiveSupport::JSON.decode(redis.get(redis_key)))
|
146
|
+
end
|
147
|
+
public :redis_get
|
148
|
+
|
149
|
+
def raw_create
|
150
|
+
redis_set
|
151
|
+
create_indexes
|
152
|
+
redis.sadd(self.class.redis_key, self.id)
|
153
|
+
end
|
154
|
+
|
155
|
+
def raw_update
|
156
|
+
destroy_indexes
|
157
|
+
redis_set
|
158
|
+
create_indexes
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
module Model
|
163
|
+
def self.included(base)
|
164
|
+
base.send :include, InstanceMethods
|
165
|
+
base.send :extend, ClassMethods
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
module TopModel
|
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,32 @@
|
|
1
|
+
module TopModel
|
2
|
+
module Validations
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include ActiveModel::Validations
|
5
|
+
|
6
|
+
included do
|
7
|
+
alias_method_chain :save, :validation
|
8
|
+
end
|
9
|
+
|
10
|
+
def save_with_validation(options = nil)
|
11
|
+
perform_validation = case options
|
12
|
+
when Hash
|
13
|
+
options[:validate] != false
|
14
|
+
when NilClass
|
15
|
+
true
|
16
|
+
else
|
17
|
+
options
|
18
|
+
end
|
19
|
+
|
20
|
+
if perform_validation && valid? || !perform_validation
|
21
|
+
save_without_validation
|
22
|
+
true
|
23
|
+
else
|
24
|
+
false
|
25
|
+
end
|
26
|
+
rescue InvalidRecord => error
|
27
|
+
false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
require "topmodel/validations/uniqueness"
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module TopModel
|
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/topmodel.gemspec
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{topmodel}
|
8
|
+
s.version = "0.2"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Alex MacCaw", "Alex Ebeling-Hoppe"]
|
12
|
+
s.date = %q{2014-04-03}
|
13
|
+
s.description = %q{In memory DB using ActiveModel}
|
14
|
+
s.email = %q{info@eribium.org}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"README.md"
|
17
|
+
]
|
18
|
+
s.files = [
|
19
|
+
".gitignore",
|
20
|
+
"MIT-LICENSE",
|
21
|
+
"README.md",
|
22
|
+
"Rakefile",
|
23
|
+
"VERSION",
|
24
|
+
"lib/top_model.rb",
|
25
|
+
"lib/topmodel.rb",
|
26
|
+
"lib/topmodel/association.rb",
|
27
|
+
"lib/topmodel/base.rb",
|
28
|
+
"lib/topmodel/callbacks.rb",
|
29
|
+
"lib/topmodel/dirty.rb",
|
30
|
+
"lib/topmodel/ext/array.rb",
|
31
|
+
"lib/topmodel/marshal.rb",
|
32
|
+
"lib/topmodel/observing.rb",
|
33
|
+
"lib/topmodel/random_id.rb",
|
34
|
+
"lib/topmodel/redis.rb",
|
35
|
+
"lib/topmodel/timestamp.rb",
|
36
|
+
"lib/topmodel/validations.rb",
|
37
|
+
"lib/topmodel/validations/uniqueness.rb",
|
38
|
+
"topmodel.gemspec"
|
39
|
+
]
|
40
|
+
#s.homepage = %q{http://github.com/maccman/topmodel}
|
41
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
42
|
+
s.require_paths = ["lib"]
|
43
|
+
s.rubygems_version = %q{1.3.7}
|
44
|
+
s.summary = %q{In memory DB using ActiveModel}
|
45
|
+
|
46
|
+
if s.respond_to? :specification_version then
|
47
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
48
|
+
s.specification_version = 3
|
49
|
+
|
50
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
51
|
+
s.add_runtime_dependency(%q<activemodel>, [">= 3.0.0", "<= 4.0.3"])
|
52
|
+
else
|
53
|
+
s.add_dependency(%q<activemodel>, [">= 3.0.0", "<= 4.0.3"])
|
54
|
+
end
|
55
|
+
else
|
56
|
+
s.add_dependency(%q<activemodel>, [">= 3.0.0", "<= 4.0.3"])
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
metadata
ADDED
@@ -0,0 +1,85 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: topmodel
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: '0.2'
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Alex MacCaw
|
8
|
+
- Alex Ebeling-Hoppe
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2014-04-03 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: activemodel
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
requirements:
|
18
|
+
- - ">="
|
19
|
+
- !ruby/object:Gem::Version
|
20
|
+
version: 3.0.0
|
21
|
+
- - "<="
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 4.0.3
|
24
|
+
type: :runtime
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: !ruby/object:Gem::Requirement
|
27
|
+
requirements:
|
28
|
+
- - ">="
|
29
|
+
- !ruby/object:Gem::Version
|
30
|
+
version: 3.0.0
|
31
|
+
- - "<="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: 4.0.3
|
34
|
+
description: In memory DB using ActiveModel
|
35
|
+
email: info@eribium.org
|
36
|
+
executables: []
|
37
|
+
extensions: []
|
38
|
+
extra_rdoc_files:
|
39
|
+
- README.md
|
40
|
+
files:
|
41
|
+
- ".gitignore"
|
42
|
+
- MIT-LICENSE
|
43
|
+
- README.md
|
44
|
+
- Rakefile
|
45
|
+
- VERSION
|
46
|
+
- lib/top_model.rb
|
47
|
+
- lib/topmodel.rb
|
48
|
+
- lib/topmodel/association.rb
|
49
|
+
- lib/topmodel/base.rb
|
50
|
+
- lib/topmodel/callbacks.rb
|
51
|
+
- lib/topmodel/dirty.rb
|
52
|
+
- lib/topmodel/ext/array.rb
|
53
|
+
- lib/topmodel/marshal.rb
|
54
|
+
- lib/topmodel/observing.rb
|
55
|
+
- lib/topmodel/random_id.rb
|
56
|
+
- lib/topmodel/redis.rb
|
57
|
+
- lib/topmodel/timestamp.rb
|
58
|
+
- lib/topmodel/validations.rb
|
59
|
+
- lib/topmodel/validations/uniqueness.rb
|
60
|
+
- topmodel.gemspec
|
61
|
+
homepage:
|
62
|
+
licenses: []
|
63
|
+
metadata: {}
|
64
|
+
post_install_message:
|
65
|
+
rdoc_options:
|
66
|
+
- "--charset=UTF-8"
|
67
|
+
require_paths:
|
68
|
+
- lib
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
75
|
+
requirements:
|
76
|
+
- - ">="
|
77
|
+
- !ruby/object:Gem::Version
|
78
|
+
version: '0'
|
79
|
+
requirements: []
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 2.2.2
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: In memory DB using ActiveModel
|
85
|
+
test_files: []
|