worsemodel 0.1.7

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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 73b0ddf3f4f4f933d500a077db9de43a79883e69
4
+ data.tar.gz: 868b7b750fa597aa5d37e3e48a0e22e3ff2bbd43
5
+ SHA512:
6
+ metadata.gz: 77a6f434dd511abe74ac3513b6f32a603958330a089b7b832c24975c662fa9b46d298e6ebe91f06e90daf9ab52c4f0a93baa9be6e9669a88cb2eeeda71e9f5b6
7
+ data.tar.gz: c359ae2464fb408ce3b1372d81502914d3a3cded7e803fd25935229522f95cd4dcca404cea9bb33b9f1a82adc21ac91b503d079337c101f4c0cde2d4561490dc
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ dump.db
2
+ test.rb
3
+ redis_test.rb
4
+ pkg
5
+ lib/supermodel/cassandra.rb
6
+ *.gem
7
+ tags
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 ADDED
@@ -0,0 +1,64 @@
1
+
2
+ Simple in-memory database using ActiveModel.
3
+
4
+ Primarily developed for Bowline applications.
5
+ http://github.com/maccman/bowline
6
+
7
+ Supports:
8
+ * Serialisation
9
+ * Validations
10
+ * Callbacks
11
+ * Observers
12
+ * Dirty (Changes)
13
+ * Ruby Marshalling to disk
14
+ * Redis
15
+
16
+ Examples:
17
+
18
+ require "supermodel"
19
+
20
+ class Test < SuperModel::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 < SuperModel::Base
35
+ include SuperModel::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 < SuperModel::Base
44
+ include SuperModel::Marshal::Model
45
+ end
46
+
47
+ SuperModel::Marshal.path = "dump.db"
48
+ SuperModel::Marshal.load
49
+
50
+ at_exit {
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 ADDED
@@ -0,0 +1,14 @@
1
+ begin
2
+ require 'jeweler'
3
+ Jeweler::Tasks.new do |gemspec|
4
+ gemspec.name = "supermodel"
5
+ gemspec.summary = "In memory DB using ActiveModel"
6
+ gemspec.email = "info@eribium.org"
7
+ gemspec.homepage = "http://github.com/maccman/supermodel"
8
+ gemspec.description = "In memory DB using ActiveModel"
9
+ gemspec.authors = ["Alex MacCaw"]
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.1.7
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), "supermodel")
@@ -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
@@ -0,0 +1,326 @@
1
+ module SuperModel
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 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
22
+
23
+ def 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)
35
+ end
36
+
37
+ def raw_find(id) #:nodoc:
38
+ records[id] || raise(UnknownRecord, "Couldn't find #{self.name} with ID=#{id}")
39
+ end
40
+
41
+ # Find record by ID, or raise.
42
+ def find(id)
43
+ item = raw_find(id)
44
+ item && item.dup
45
+ end
46
+ alias :[] :find
47
+
48
+ def first
49
+ item = records.values[0]
50
+ item && item.dup
51
+ end
52
+
53
+ def last
54
+ item = records.values[-1]
55
+ item && item.dup
56
+ end
57
+
58
+ def where(options)
59
+ items = records.values.select do |r|
60
+ options.all? do |k, v|
61
+ if v.is_a?(Enumerable)
62
+ v.include?(r.send(k))
63
+ else
64
+ r.send(k) == v
65
+ end
66
+ end
67
+ end
68
+ collection.new(items.deep_dup)
69
+ end
70
+
71
+ def exists?(id)
72
+ records.has_key?(id)
73
+ end
74
+
75
+ def count
76
+ records.length
77
+ end
78
+
79
+ def all
80
+ collection.new(records.values.deep_dup)
81
+ end
82
+
83
+ def select(&block)
84
+ collection.new(records.values.select(&block).deep_dup)
85
+ end
86
+
87
+ def update(id, atts)
88
+ find(id).update_attributes(atts)
89
+ end
90
+
91
+ def destroy(id)
92
+ find(id).destroy
93
+ end
94
+
95
+ # Removes all records and executes
96
+ # destroy callbacks.
97
+ def destroy_all
98
+ all.each {|r| r.destroy }
99
+ end
100
+
101
+ # Removes all records without executing
102
+ # destroy callbacks.
103
+ def delete_all
104
+ records.clear
105
+ end
106
+
107
+ # Create a new record.
108
+ # Example:
109
+ # create(:name => "foo", :id => 1)
110
+ def create(atts = {})
111
+ rec = self.new(atts)
112
+ rec.save && rec
113
+ end
114
+
115
+ def create!(*args)
116
+ create(*args) || raise(InvalidRecord)
117
+ end
118
+
119
+ def method_missing(method_symbol, *args) #:nodoc:
120
+ method_name = method_symbol.to_s
121
+
122
+ if method_name =~ /^find_by_(\w+)!/
123
+ send("find_by_#{$1}", *args) || raise(UnknownRecord)
124
+ elsif method_name =~ /^find_by_(\w+)/
125
+ find_by_attribute($1, args.first)
126
+ elsif method_name =~ /^find_or_create_by_(\w+)/
127
+ send("find_by_#{$1}", *args) || create($1 => args.first)
128
+ elsif method_name =~ /^find_all_by_(\w+)/
129
+ find_all_by_attribute($1, args.first)
130
+ else
131
+ super
132
+ end
133
+ end
134
+ end
135
+
136
+ attr_accessor :attributes
137
+ attr_writer :new_record
138
+
139
+ def known_attributes
140
+ self.class.known_attributes | self.attributes.keys.map(&:to_s)
141
+ end
142
+
143
+ def initialize(attributes = {})
144
+ @new_record = true
145
+ @attributes = {}.with_indifferent_access
146
+ @attributes.merge!(known_attributes.inject({}) {|h, n| h[n] = nil; h })
147
+ @changed_attributes = {}
148
+ load(attributes)
149
+ end
150
+
151
+ def clone
152
+ cloned = attributes.reject {|k,v| k == self.class.primary_key }
153
+ cloned = cloned.inject({}) do |attrs, (k, v)|
154
+ attrs[k] = v.clone
155
+ attrs
156
+ end
157
+ self.class.new(cloned)
158
+ end
159
+
160
+ def new?
161
+ @new_record || false
162
+ end
163
+ alias :new_record? :new?
164
+
165
+ # Gets the <tt>\id</tt> attribute of the item.
166
+ def id
167
+ attributes[self.class.primary_key]
168
+ end
169
+
170
+ # Sets the <tt>\id</tt> attribute of the item.
171
+ def id=(id)
172
+ attributes[self.class.primary_key] = id
173
+ end
174
+
175
+ def ==(other)
176
+ other.equal?(self) || (other.instance_of?(self.class) && other.id == id)
177
+ end
178
+
179
+ # Tests for equality (delegates to ==).
180
+ def eql?(other)
181
+ self == other
182
+ end
183
+
184
+ def hash
185
+ id.hash
186
+ end
187
+
188
+ def dup
189
+ self.class.new.tap do |base|
190
+ base.attributes = attributes
191
+ base.new_record = new_record?
192
+ end
193
+ end
194
+
195
+ def save
196
+ new? ? create : update
197
+ end
198
+
199
+ def save!
200
+ save || raise(InvalidRecord)
201
+ end
202
+
203
+ def exists?
204
+ !new?
205
+ end
206
+ alias_method :persisted?, :exists?
207
+
208
+ def load(attributes) #:nodoc:
209
+ return unless attributes
210
+ attributes.each do |(name, value)|
211
+ self.send("#{name}=".to_sym, value)
212
+ end
213
+ end
214
+
215
+ def reload
216
+ return self if new?
217
+ item = self.class.find(id)
218
+ load(item.attributes)
219
+ return self
220
+ end
221
+
222
+ def update_attribute(name, value)
223
+ self.send("#{name}=".to_sym, value)
224
+ self.save
225
+ end
226
+
227
+ def update_attributes(attributes)
228
+ load(attributes) && save
229
+ end
230
+
231
+ def update_attributes!(attributes)
232
+ update_attributes(attributes) || raise(InvalidRecord)
233
+ end
234
+
235
+ def has_attribute?(name)
236
+ @attributes.has_key?(name)
237
+ end
238
+
239
+ alias_method :respond_to_without_attributes?, :respond_to?
240
+
241
+ def respond_to?(method, include_priv = false)
242
+ method_name = method.to_s
243
+ if attributes.nil?
244
+ super
245
+ elsif known_attributes.include?(method_name)
246
+ true
247
+ elsif method_name =~ /(?:=|\?)$/ && attributes.include?($`)
248
+ true
249
+ else
250
+ super
251
+ end
252
+ end
253
+
254
+ def destroy
255
+ raw_destroy
256
+ self
257
+ end
258
+
259
+ protected
260
+ def read_attribute(name)
261
+ @attributes[name]
262
+ end
263
+
264
+ def write_attribute(name, value)
265
+ @attributes[name] = value
266
+ end
267
+
268
+ def generate_id
269
+ object_id
270
+ end
271
+
272
+ def raw_destroy
273
+ self.class.records.delete(self.id)
274
+ end
275
+
276
+ def raw_create
277
+ self.class.records[self.id] = self.dup
278
+ end
279
+
280
+ def create
281
+ self.id ||= generate_id
282
+ self.new_record = false
283
+ raw_create
284
+ self.id
285
+ end
286
+
287
+ def raw_update
288
+ item = self.class.raw_find(id)
289
+ item.load(attributes)
290
+ end
291
+
292
+ def update
293
+ raw_update
294
+ true
295
+ end
296
+
297
+ private
298
+
299
+ def method_missing(method_symbol, *arguments) #:nodoc:
300
+ method_name = method_symbol.to_s
301
+
302
+ if method_name =~ /(=|\?)$/
303
+ case $1
304
+ when "="
305
+ attribute_will_change!($`)
306
+ attributes[$`] = arguments.first
307
+ when "?"
308
+ attributes[$`]
309
+ end
310
+ else
311
+ return attributes[method_name] if attributes.include?(method_name)
312
+ return nil if known_attributes.include?(method_name)
313
+ super
314
+ end
315
+ end
316
+ end
317
+
318
+ class Base
319
+ extend ActiveModel::Naming
320
+ include ActiveModel::Conversion
321
+ include ActiveModel::Serializers::JSON
322
+ include ActiveModel::Serializers::Xml
323
+ include Dirty, Observing, Callbacks, Validations
324
+ include Association::Model
325
+ end
326
+ end
@@ -0,0 +1,23 @@
1
+ module SuperModel
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
+ %w( create save update destroy ).each do |method|
12
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
13
+ def #{method}_with_callbacks(*args, &block)
14
+ run_callbacks :#{method} do
15
+ #{method}_without_callbacks(*args, &block)
16
+ end
17
+ end
18
+ EOS
19
+ alias_method_chain(method, :callbacks)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -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,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 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,21 @@
1
+ module SuperModel
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,8 @@
1
+ module SuperModel
2
+ module RandomID
3
+ protected
4
+ def generate_id
5
+ ActiveSupport::SecureRandom.hex(13)
6
+ end
7
+ end
8
+ 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
@@ -0,0 +1,32 @@
1
+ module SuperModel
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 "supermodel/validations/uniqueness"
data/lib/supermodel.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 SuperModel
18
+ class SuperModelError < StandardError; end
19
+ class UnknownRecord < SuperModelError; end
20
+ class InvalidRecord < SuperModelError; end
21
+ end
22
+
23
+ $:.unshift(File.dirname(__FILE__))
24
+ require "supermodel/ext/array"
25
+
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
@@ -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 = 'worsemodel'
8
+ s.version = "0.1.7"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Alex MacCaw"]
12
+ s.date = %q{2010-08-30}
13
+ s.description = %q{In memory DB using ActiveModel}
14
+ s.email = %q{info@eribium.org}
15
+ s.extra_rdoc_files = [
16
+ "README"
17
+ ]
18
+ s.files = [
19
+ ".gitignore",
20
+ "MIT-LICENSE",
21
+ "README",
22
+ "Rakefile",
23
+ "VERSION",
24
+ "lib/super_model.rb",
25
+ "lib/supermodel.rb",
26
+ "lib/supermodel/association.rb",
27
+ "lib/supermodel/base.rb",
28
+ "lib/supermodel/callbacks.rb",
29
+ "lib/supermodel/dirty.rb",
30
+ "lib/supermodel/ext/array.rb",
31
+ "lib/supermodel/marshal.rb",
32
+ "lib/supermodel/observing.rb",
33
+ "lib/supermodel/random_id.rb",
34
+ "lib/supermodel/redis.rb",
35
+ "lib/supermodel/timestamp.rb",
36
+ "lib/supermodel/validations.rb",
37
+ "lib/supermodel/validations/uniqueness.rb",
38
+ "supermodel.gemspec"
39
+ ]
40
+ s.homepage = %q{http://github.com/maccman/supermodel}
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"])
52
+ else
53
+ s.add_dependency(%q<activemodel>, [">= 3.0.0"])
54
+ end
55
+ else
56
+ s.add_dependency(%q<activemodel>, [">= 3.0.0"])
57
+ end
58
+ end
59
+
metadata ADDED
@@ -0,0 +1,79 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: worsemodel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.7
5
+ platform: ruby
6
+ authors:
7
+ - Alex MacCaw
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2010-08-30 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activemodel
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: 3.0.0
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: 3.0.0
27
+ description: In memory DB using ActiveModel
28
+ email: info@eribium.org
29
+ executables: []
30
+ extensions: []
31
+ extra_rdoc_files:
32
+ - README
33
+ files:
34
+ - ".gitignore"
35
+ - MIT-LICENSE
36
+ - README
37
+ - Rakefile
38
+ - VERSION
39
+ - lib/super_model.rb
40
+ - lib/supermodel.rb
41
+ - lib/supermodel/association.rb
42
+ - lib/supermodel/base.rb
43
+ - lib/supermodel/callbacks.rb
44
+ - lib/supermodel/dirty.rb
45
+ - lib/supermodel/ext/array.rb
46
+ - lib/supermodel/marshal.rb
47
+ - lib/supermodel/observing.rb
48
+ - lib/supermodel/random_id.rb
49
+ - lib/supermodel/redis.rb
50
+ - lib/supermodel/timestamp.rb
51
+ - lib/supermodel/validations.rb
52
+ - lib/supermodel/validations/uniqueness.rb
53
+ - supermodel.gemspec
54
+ homepage: http://github.com/maccman/supermodel
55
+ licenses: []
56
+ metadata: {}
57
+ post_install_message:
58
+ rdoc_options:
59
+ - "--charset=UTF-8"
60
+ require_paths:
61
+ - lib
62
+ required_ruby_version: !ruby/object:Gem::Requirement
63
+ requirements:
64
+ - - ">="
65
+ - !ruby/object:Gem::Version
66
+ version: '0'
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ requirements:
69
+ - - ">="
70
+ - !ruby/object:Gem::Version
71
+ version: '0'
72
+ requirements: []
73
+ rubyforge_project:
74
+ rubygems_version: 2.4.5
75
+ signing_key:
76
+ specification_version: 3
77
+ summary: In memory DB using ActiveModel
78
+ test_files: []
79
+ has_rdoc: