supermodel 0.1.2 → 0.1.3

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/.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