supermodel 0.1.2 → 0.1.3
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +1 -0
- data/MIT-LICENSE +20 -0
- data/VERSION +1 -1
- data/lib/supermodel.rb +1 -0
- data/lib/supermodel/association.rb +51 -0
- data/lib/supermodel/base.rb +31 -4
- data/lib/supermodel/dirty.rb +2 -2
- data/lib/supermodel/marshal.rb +21 -7
- data/lib/supermodel/redis.rb +26 -14
- data/lib/supermodel/timestamp.rb +37 -8
- data/lib/supermodel/validations.rb +3 -1
- data/lib/supermodel/validations/uniqueness.rb +40 -0
- data/supermodel.gemspec +5 -2
- metadata +6 -3
data/.gitignore
CHANGED
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/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.1.
|
1
|
+
0.1.3
|
data/lib/supermodel.rb
CHANGED
@@ -0,0 +1,51 @@
|
|
1
|
+
require "active_support/core_ext/string/inflections.rb"
|
2
|
+
|
3
|
+
module SuperModel
|
4
|
+
module Association
|
5
|
+
module ClassMethods
|
6
|
+
def belongs_to(to_model, options = {})
|
7
|
+
to_model = to_model.to_s
|
8
|
+
class_name = options[:class_name] || to_model.classify
|
9
|
+
foreign_key = options[:foreign_key] || "#{to_model}_id"
|
10
|
+
primary_key = options[:primary_key] || "id"
|
11
|
+
|
12
|
+
attributes foreign_key
|
13
|
+
|
14
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
15
|
+
def #{to_model} # def user
|
16
|
+
#{foreign_key} && #{class_name}.find(#{foreign_key}) # user_id && User.find(user_id)
|
17
|
+
end # end
|
18
|
+
#
|
19
|
+
def #{to_model}? # def user?
|
20
|
+
#{foreign_key} && #{class_name}.exists?(#{foreign_key}) # user_id && User.exists?(user_id)
|
21
|
+
end # end
|
22
|
+
#
|
23
|
+
def #{to_model}=(object) # def user=(model)
|
24
|
+
self.#{foreign_key} = (object && object.#{primary_key}) # self.user_id = (model && model.id)
|
25
|
+
end # end
|
26
|
+
EOS
|
27
|
+
end
|
28
|
+
|
29
|
+
def has_many(to_model, options = {})
|
30
|
+
to_model = to_model.to_s
|
31
|
+
class_name = options[:class_name] || to_model.classify
|
32
|
+
foreign_key = options[:foreign_key] || "#{model_name.singular}_id"
|
33
|
+
primary_key = options[:primary_key] || "id"
|
34
|
+
class_eval(<<-EOS, __FILE__, __LINE__ + 1)
|
35
|
+
def #{to_model} # def user
|
36
|
+
#{class_name}.find_all_by_attribute( # User.find_all_by_attribute(
|
37
|
+
:#{foreign_key}, # :task_id,
|
38
|
+
#{primary_key} # id
|
39
|
+
) # )
|
40
|
+
end # end
|
41
|
+
EOS
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module Model
|
46
|
+
def self.included(base)
|
47
|
+
base.extend(ClassMethods)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
data/lib/supermodel/base.rb
CHANGED
@@ -5,6 +5,12 @@ module SuperModel
|
|
5
5
|
|
6
6
|
class << self
|
7
7
|
attr_accessor_with_default(:primary_key, 'id') #:nodoc:
|
8
|
+
|
9
|
+
def collection(&block)
|
10
|
+
@collection ||= Class.new(Array)
|
11
|
+
@collection.class_eval(&block) if block_given?
|
12
|
+
@collection
|
13
|
+
end
|
8
14
|
|
9
15
|
def attributes(*attributes)
|
10
16
|
self.known_attributes += attributes.map(&:to_s)
|
@@ -19,8 +25,13 @@ module SuperModel
|
|
19
25
|
item && item.dup
|
20
26
|
end
|
21
27
|
|
28
|
+
def find_all_by_attribute(name, value) #:nodoc:
|
29
|
+
items = records.values.select {|r| r.send(name) == value }
|
30
|
+
collection.new(items)
|
31
|
+
end
|
32
|
+
|
22
33
|
def raw_find(id) #:nodoc:
|
23
|
-
records[id] || raise(UnknownRecord)
|
34
|
+
records[id] || raise(UnknownRecord, "Couldn't find #{self.name} with ID=#{id}")
|
24
35
|
end
|
25
36
|
|
26
37
|
# Find record by ID, or raise.
|
@@ -40,12 +51,16 @@ module SuperModel
|
|
40
51
|
item && item.dup
|
41
52
|
end
|
42
53
|
|
54
|
+
def exists?(id)
|
55
|
+
records.has_key?(id)
|
56
|
+
end
|
57
|
+
|
43
58
|
def count
|
44
59
|
records.length
|
45
60
|
end
|
46
61
|
|
47
62
|
def all
|
48
|
-
records.values
|
63
|
+
collection.new(records.values)
|
49
64
|
end
|
50
65
|
|
51
66
|
def update(id, atts)
|
@@ -73,7 +88,11 @@ module SuperModel
|
|
73
88
|
# create(:name => "foo", :id => 1)
|
74
89
|
def create(atts = {})
|
75
90
|
rec = self.new(atts)
|
76
|
-
rec.save
|
91
|
+
rec.save && rec
|
92
|
+
end
|
93
|
+
|
94
|
+
def create!(*args)
|
95
|
+
create(*args) || raise(InvalidRecord)
|
77
96
|
end
|
78
97
|
|
79
98
|
def method_missing(method_symbol, *args) #:nodoc:
|
@@ -85,6 +104,8 @@ module SuperModel
|
|
85
104
|
find_by_attribute($1, args.first)
|
86
105
|
elsif method_name =~ /^find_or_create_by_(\w+)/
|
87
106
|
send("find_by_#{$1}", *args) || create($1 => args.first)
|
107
|
+
elsif method_name =~ /^find_all_by_(\w+)/
|
108
|
+
find_all_by_attribute($1, args.first)
|
88
109
|
else
|
89
110
|
super
|
90
111
|
end
|
@@ -101,6 +122,7 @@ module SuperModel
|
|
101
122
|
def initialize(attributes = {})
|
102
123
|
@new_record = true
|
103
124
|
@attributes = {}.with_indifferent_access
|
125
|
+
@changed_attributes = {}
|
104
126
|
load(attributes)
|
105
127
|
end
|
106
128
|
|
@@ -175,6 +197,10 @@ module SuperModel
|
|
175
197
|
load(attributes) && save
|
176
198
|
end
|
177
199
|
|
200
|
+
def update_attributes!(attributes)
|
201
|
+
update_attributes(attributes) || raise(InvalidRecord)
|
202
|
+
end
|
203
|
+
|
178
204
|
def has_attribute?(name)
|
179
205
|
@attributes.has_key?(name)
|
180
206
|
end
|
@@ -259,10 +285,11 @@ module SuperModel
|
|
259
285
|
end
|
260
286
|
|
261
287
|
class Base
|
262
|
-
extend
|
288
|
+
extend ActiveModel::Naming
|
263
289
|
include ActiveModel::Conversion
|
264
290
|
include ActiveModel::Serializers::JSON
|
265
291
|
include ActiveModel::Serializers::Xml
|
266
292
|
include Dirty, Observing, Callbacks, Validations
|
293
|
+
include Association::Model
|
267
294
|
end
|
268
295
|
end
|
data/lib/supermodel/dirty.rb
CHANGED
data/lib/supermodel/marshal.rb
CHANGED
@@ -22,14 +22,17 @@ module SuperModel
|
|
22
22
|
File.open(path, "rb") do |file|
|
23
23
|
begin
|
24
24
|
data = ::Marshal.load(file)
|
25
|
-
rescue
|
25
|
+
rescue => e
|
26
|
+
if defined?(Bowline)
|
27
|
+
Bowline::Logging.log_error(e)
|
28
|
+
end
|
26
29
|
# Lots of errors can occur during
|
27
30
|
# marshaling - such as EOF etc
|
28
31
|
return false
|
29
32
|
end
|
30
33
|
end
|
31
34
|
data.each do |klass, records|
|
32
|
-
klass.marshal_records
|
35
|
+
klass.marshal_records = records
|
33
36
|
end
|
34
37
|
true
|
35
38
|
end
|
@@ -43,6 +46,7 @@ module SuperModel
|
|
43
46
|
hash
|
44
47
|
}
|
45
48
|
::Marshal.dump(data, tmp_file)
|
49
|
+
tmp_file.close
|
46
50
|
# Atomic serialization - so we never corrupt the db
|
47
51
|
FileUtils.mv(tmp_file.path, path)
|
48
52
|
true
|
@@ -57,20 +61,30 @@ module SuperModel
|
|
57
61
|
end
|
58
62
|
|
59
63
|
def marshal_dump
|
60
|
-
serializable_hash
|
64
|
+
serializable_hash(self.class.marshal)
|
61
65
|
end
|
62
66
|
|
63
67
|
def marshal_load(atts)
|
64
68
|
# Can't call load, since class
|
65
69
|
# isn't setup properly
|
66
|
-
@attributes
|
70
|
+
@attributes = atts.with_indifferent_access
|
71
|
+
@changed_attributes = {}
|
67
72
|
end
|
68
73
|
|
69
74
|
module ClassMethods
|
70
|
-
def
|
71
|
-
@
|
72
|
-
@
|
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
|
73
83
|
end
|
84
|
+
|
85
|
+
def marshal_records
|
86
|
+
@records
|
87
|
+
end
|
74
88
|
end
|
75
89
|
end
|
76
90
|
end
|
data/lib/supermodel/redis.rb
CHANGED
@@ -36,19 +36,27 @@ module SuperModel
|
|
36
36
|
end
|
37
37
|
|
38
38
|
def find(id)
|
39
|
-
if redis.
|
39
|
+
if redis.sismember(redis_key, id.to_s)
|
40
40
|
existing(:id => id)
|
41
41
|
else
|
42
|
-
raise
|
42
|
+
raise UnknownRecord, "Couldn't find #{self.name} with ID=#{id}"
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
46
|
def first
|
47
|
-
|
47
|
+
item_ids = redis.sort(redis_key, :order => "ASC", :limit => [0, 1])
|
48
|
+
item_id = item_ids.first
|
49
|
+
item_id && existing(:id => item_id)
|
48
50
|
end
|
49
51
|
|
50
52
|
def last
|
51
|
-
|
53
|
+
item_ids = redis.sort(redis_key, :order => "DESC", :limit => [0, 1])
|
54
|
+
item_id = item_ids.first
|
55
|
+
item_id && existing(:id => item_id)
|
56
|
+
end
|
57
|
+
|
58
|
+
def exists?(id)
|
59
|
+
redis.set_member?(redis_key, id.to_s)
|
52
60
|
end
|
53
61
|
|
54
62
|
def count
|
@@ -56,7 +64,7 @@ module SuperModel
|
|
56
64
|
end
|
57
65
|
|
58
66
|
def all
|
59
|
-
from_ids(redis.
|
67
|
+
from_ids(redis.sort(redis_key))
|
60
68
|
end
|
61
69
|
|
62
70
|
def delete_all
|
@@ -64,9 +72,13 @@ module SuperModel
|
|
64
72
|
end
|
65
73
|
|
66
74
|
def find_by_attribute(key, value)
|
67
|
-
item_ids = redis.
|
68
|
-
|
69
|
-
existing(:id =>
|
75
|
+
item_ids = redis.sort(redis_key(key, value.to_s))
|
76
|
+
item_id = item_ids.first
|
77
|
+
item_id && existing(:id => item_id)
|
78
|
+
end
|
79
|
+
|
80
|
+
def find_all_by_attribute(key, value)
|
81
|
+
from_ids(redis.sort(redis_key(key, value.to_s)))
|
70
82
|
end
|
71
83
|
|
72
84
|
protected
|
@@ -85,7 +97,7 @@ module SuperModel
|
|
85
97
|
module InstanceMethods
|
86
98
|
# Redis integers are stored as strings
|
87
99
|
def id
|
88
|
-
super.try(:
|
100
|
+
super.try(:to_s)
|
89
101
|
end
|
90
102
|
|
91
103
|
protected
|
@@ -93,7 +105,7 @@ module SuperModel
|
|
93
105
|
return if new?
|
94
106
|
|
95
107
|
destroy_indexes
|
96
|
-
redis.
|
108
|
+
redis.srem(self.class.redis_key, self.id)
|
97
109
|
|
98
110
|
attributes.keys.each do |key|
|
99
111
|
redis.delete(redis_key(key))
|
@@ -103,14 +115,14 @@ module SuperModel
|
|
103
115
|
def destroy_indexes
|
104
116
|
indexed_attributes.each do |index|
|
105
117
|
old_attribute = changes[index].try(:first) || send(index)
|
106
|
-
redis.
|
118
|
+
redis.srem(self.class.redis_key(index, old_attribute), id)
|
107
119
|
end
|
108
120
|
end
|
109
121
|
|
110
122
|
def create_indexes
|
111
123
|
indexed_attributes.each do |index|
|
112
124
|
new_attribute = send(index)
|
113
|
-
redis.
|
125
|
+
redis.sadd(self.class.redis_key(index, new_attribute), id)
|
114
126
|
end
|
115
127
|
end
|
116
128
|
|
@@ -161,7 +173,7 @@ module SuperModel
|
|
161
173
|
def raw_create
|
162
174
|
redis_set
|
163
175
|
create_indexes
|
164
|
-
redis.
|
176
|
+
redis.sadd(self.class.redis_key, self.id)
|
165
177
|
end
|
166
178
|
|
167
179
|
def raw_update
|
@@ -178,4 +190,4 @@ module SuperModel
|
|
178
190
|
end
|
179
191
|
end
|
180
192
|
end
|
181
|
-
end
|
193
|
+
end
|
data/lib/supermodel/timestamp.rb
CHANGED
@@ -8,17 +8,46 @@ module SuperModel
|
|
8
8
|
before_create :set_created_at
|
9
9
|
before_save :set_updated_at
|
10
10
|
end
|
11
|
+
end
|
11
12
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
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)
|
16
33
|
end
|
17
|
-
|
18
|
-
|
19
|
-
|
34
|
+
end
|
35
|
+
|
36
|
+
def current_time
|
37
|
+
if Time.respond_to?(:current)
|
38
|
+
Time.current
|
39
|
+
else
|
40
|
+
Time.now
|
20
41
|
end
|
21
|
-
|
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
|
22
51
|
end
|
23
52
|
end
|
24
53
|
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module ActiveModel
|
2
|
+
module Validations
|
3
|
+
class UniquenessValidator < 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 == record
|
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/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.1.
|
8
|
+
s.version = "0.1.3"
|
9
9
|
|
10
10
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
11
|
s.authors = ["Alex MacCaw"]
|
12
|
-
s.date = %q{2010-
|
12
|
+
s.date = %q{2010-05-11}
|
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,11 +17,13 @@ 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",
|
27
29
|
"lib/supermodel/dirty.rb",
|
@@ -31,6 +33,7 @@ Gem::Specification.new do |s|
|
|
31
33
|
"lib/supermodel/redis.rb",
|
32
34
|
"lib/supermodel/timestamp.rb",
|
33
35
|
"lib/supermodel/validations.rb",
|
36
|
+
"lib/supermodel/validations/uniqueness.rb",
|
34
37
|
"supermodel.gemspec"
|
35
38
|
]
|
36
39
|
s.homepage = %q{http://github.com/maccman/supermodel}
|
metadata
CHANGED
@@ -5,8 +5,8 @@ version: !ruby/object:Gem::Version
|
|
5
5
|
segments:
|
6
6
|
- 0
|
7
7
|
- 1
|
8
|
-
-
|
9
|
-
version: 0.1.
|
8
|
+
- 3
|
9
|
+
version: 0.1.3
|
10
10
|
platform: ruby
|
11
11
|
authors:
|
12
12
|
- Alex MacCaw
|
@@ -14,7 +14,7 @@ autorequire:
|
|
14
14
|
bindir: bin
|
15
15
|
cert_chain: []
|
16
16
|
|
17
|
-
date: 2010-
|
17
|
+
date: 2010-05-11 00:00:00 +01:00
|
18
18
|
default_executable:
|
19
19
|
dependencies:
|
20
20
|
- !ruby/object:Gem::Dependency
|
@@ -42,11 +42,13 @@ extra_rdoc_files:
|
|
42
42
|
- README
|
43
43
|
files:
|
44
44
|
- .gitignore
|
45
|
+
- MIT-LICENSE
|
45
46
|
- README
|
46
47
|
- Rakefile
|
47
48
|
- VERSION
|
48
49
|
- lib/super_model.rb
|
49
50
|
- lib/supermodel.rb
|
51
|
+
- lib/supermodel/association.rb
|
50
52
|
- lib/supermodel/base.rb
|
51
53
|
- lib/supermodel/callbacks.rb
|
52
54
|
- lib/supermodel/dirty.rb
|
@@ -56,6 +58,7 @@ files:
|
|
56
58
|
- lib/supermodel/redis.rb
|
57
59
|
- lib/supermodel/timestamp.rb
|
58
60
|
- lib/supermodel/validations.rb
|
61
|
+
- lib/supermodel/validations/uniqueness.rb
|
59
62
|
- supermodel.gemspec
|
60
63
|
has_rdoc: true
|
61
64
|
homepage: http://github.com/maccman/supermodel
|