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 CHANGED
@@ -3,3 +3,4 @@ test.rb
3
3
  redis_test.rb
4
4
  pkg
5
5
  lib/supermodel/cassandra.rb
6
+ *.gem
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.2
1
+ 0.1.3
data/lib/supermodel.rb CHANGED
@@ -24,6 +24,7 @@ end
24
24
 
25
25
  $:.unshift(File.dirname(__FILE__))
26
26
 
27
+ require "supermodel/association"
27
28
  require "supermodel/callbacks"
28
29
  require "supermodel/observing"
29
30
  require "supermodel/marshal"
@@ -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
@@ -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.dup
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! && rec
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 ActiveModel::Naming
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
@@ -15,10 +15,10 @@ module SuperModel
15
15
  alias_method_chain(method, :dirty)
16
16
  end
17
17
  end
18
-
18
+
19
19
  def save_previous_changes
20
20
  @previously_changed = changes
21
- changed_attributes.clear
21
+ @changed_attributes.clear
22
22
  end
23
23
  end
24
24
  end
@@ -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(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 = atts
70
+ @attributes = atts.with_indifferent_access
71
+ @changed_attributes = {}
67
72
  end
68
73
 
69
74
  module ClassMethods
70
- def marshal_records(records = nil)
71
- @records = records if records
72
- @records
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
@@ -36,19 +36,27 @@ module SuperModel
36
36
  end
37
37
 
38
38
  def find(id)
39
- if redis.set_member?(redis_key, id)
39
+ if redis.sismember(redis_key, id.to_s)
40
40
  existing(:id => id)
41
41
  else
42
- raise(UnknownRecord)
42
+ raise UnknownRecord, "Couldn't find #{self.name} with ID=#{id}"
43
43
  end
44
44
  end
45
45
 
46
46
  def first
47
- all.first
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
- all.last
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.set_members(redis_key))
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.set_members(redis_key(key, value))
68
- return if item_ids.empty?
69
- existing(:id => item_ids.first)
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(:to_i)
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.set_delete(self.class.redis_key, self.id)
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.set_delete(self.class.redis_key(index, old_attribute), id)
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.set_add(self.class.redis_key(index, new_attribute), id)
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.set_add(self.class.redis_key, self.id)
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
@@ -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
- private
13
-
14
- def set_created_at
15
- self.created_at = Time.now
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
- def set_updated_at
19
- self.updated_at = Time.now
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
- 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
22
51
  end
23
52
  end
24
53
  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"
@@ -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.2"
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-03-11}
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
- - 2
9
- version: 0.1.2
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-03-11 00:00:00 +00:00
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