supermodel 0.0.1 → 0.1.6

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