topmodel 0.2

Sign up to get free protection for your applications and to get access to all the features.
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,7 @@
1
+ class Array
2
+ unless defined?(deep_dup)
3
+ def deep_dup
4
+ Marshal.load(Marshal.dump(self))
5
+ end
6
+ end
7
+ 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,9 @@
1
+ module TopModel
2
+ module RandomID
3
+ protected
4
+ def generate_id
5
+ #ActiveSupport::SecureRandom.hex(13)
6
+ SecureRandom.hex(13)
7
+ end
8
+ end
9
+ 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: []