supermodel 0.0.1

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 ADDED
@@ -0,0 +1,2 @@
1
+ dump.db
2
+ test.rb
data/README ADDED
@@ -0,0 +1,51 @@
1
+
2
+ Simple in-memory database using ActiveModel.
3
+
4
+ Primarily developed for Bowline application.
5
+ http://github.com/maccman/bowline
6
+
7
+ Supports:
8
+ * Serialisation
9
+ * Validations
10
+ * Callbacks
11
+ * Observers
12
+ * Dirty (Changes)
13
+ * Persistance
14
+
15
+ Examples:
16
+
17
+ require "supermodel"
18
+
19
+ class Test < SuperModel::Base
20
+ end
21
+
22
+ t = Test.new
23
+ t.name = "foo"
24
+ t.save #=> true
25
+
26
+ Test.all
27
+ Test.first
28
+ Test.last
29
+ Test.find_by_name('foo)
30
+
31
+ You can use a random ID rather than the object ID:
32
+
33
+ class Test < SuperModel::Base
34
+ include SuperModel::RandomID
35
+ end
36
+
37
+ t = Test.create(:name => "test")
38
+ t.id #=> "7ee935377bb4aecc54ad4f9126"
39
+
40
+ You can persist objects to disk on startup/shutdown
41
+
42
+ class Test < SuperModel::Base
43
+ include SuperModel::Persist::Model
44
+ end
45
+
46
+ SuperModel::Persist.path = "dump.db"
47
+ SuperModel::Persist.load
48
+
49
+ at_exit {
50
+ SuperModel::Persist.dump
51
+ }
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")
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.0.1
@@ -0,0 +1 @@
1
+ require File.join(File.dirname(__FILE__), "supermodel")
@@ -0,0 +1,230 @@
1
+ module SuperModel
2
+ class Base
3
+ include ActiveModel::Dirty
4
+
5
+ class << self
6
+ attr_accessor_with_default(:primary_key, 'id') #:nodoc:
7
+
8
+ def records
9
+ @records ||= []
10
+ end
11
+
12
+ def raw_find(id) #:nodoc:
13
+ records.find {|r| r.id == id } || raise(UnknownRecord)
14
+ end
15
+
16
+ # Find record by ID, or raise.
17
+ def find(id)
18
+ item = raw_find(id)
19
+ item && item.dup
20
+ end
21
+ alias :[] :find
22
+
23
+ def first
24
+ item = records[0]
25
+ item && item.dup
26
+ end
27
+
28
+ def last
29
+ item = records[1]
30
+ item && item.dup
31
+ end
32
+
33
+ def count
34
+ records.length
35
+ end
36
+
37
+ def all
38
+ records.dup
39
+ end
40
+
41
+ def update(id, data)
42
+ find(id).update(data)
43
+ end
44
+
45
+ def destroy(id)
46
+ find(id).destroy
47
+ end
48
+
49
+ # Removes all records and executes
50
+ # destory callbacks.
51
+ def destroy_all
52
+ records.dup.each {|r| r.destroy }
53
+ end
54
+
55
+ # Removes all records without executing
56
+ # destroy callbacks.
57
+ def delete_all
58
+ records.clear
59
+ end
60
+
61
+ # Create a new record.
62
+ # Example:
63
+ # create(:name => "foo", :id => 1)
64
+ def create(atts = {})
65
+ rec = self.new(atts)
66
+ rec.save && rec
67
+ end
68
+
69
+ def method_missing(method_symbol, *arguments) #:nodoc:
70
+ method_name = method_symbol.to_s
71
+
72
+ if method_name =~ /^find_by_(\w+)!/
73
+ send("find_by_#{$1}", *arguments) || raise(UnknownRecord)
74
+ elsif method_name =~ /^find_by_(\w+)/
75
+ records.find {|r| r.send($1) == arguments.first }
76
+ else
77
+ super
78
+ end
79
+ end
80
+ end
81
+
82
+ attr_accessor :attributes
83
+
84
+ def initialize(attributes = {})
85
+ @attributes = {}.with_indifferent_access
86
+ load(attributes)
87
+ end
88
+
89
+ def clone
90
+ cloned = attributes.reject {|k,v| k == self.class.primary_key }
91
+ cloned = cloned.inject({}) do |attrs, (k, v)|
92
+ attrs[k] = v.clone
93
+ attrs
94
+ end
95
+ self.class.new(cloned)
96
+ end
97
+
98
+ def new?
99
+ id.nil?
100
+ end
101
+ alias :new_record? :new?
102
+
103
+ # Gets the <tt>\id</tt> attribute of the item.
104
+ def id
105
+ attributes[self.class.primary_key]
106
+ end
107
+
108
+ # Sets the <tt>\id</tt> attribute of the item.
109
+ def id=(id)
110
+ attributes[self.class.primary_key] = id
111
+ end
112
+
113
+ def ==(other)
114
+ other.equal?(self) || (other.instance_of?(self.class) && other.id == id)
115
+ end
116
+
117
+ # Tests for equality (delegates to ==).
118
+ def eql?(other)
119
+ self == other
120
+ end
121
+
122
+ def hash
123
+ id.hash
124
+ end
125
+
126
+ def dup
127
+ self.class.new.tap do |base|
128
+ base.attributes = @attributes.dup
129
+ end
130
+ end
131
+
132
+ def save
133
+ new? ? create : update
134
+ end
135
+
136
+ def save!
137
+ save || raise(InvalidRecord)
138
+ end
139
+
140
+ def exists?
141
+ !new?
142
+ end
143
+
144
+ def load(attributes) #:nodoc:
145
+ @attributes.merge!(attributes)
146
+ end
147
+
148
+ def update_attribute(name, value)
149
+ self.send("#{name}=".to_sym, value)
150
+ self.save
151
+ end
152
+
153
+ def update_attributes(attributes)
154
+ load(attributes) && save
155
+ end
156
+
157
+ alias_method :respond_to_without_attributes?, :respond_to?
158
+
159
+ def respond_to?(method, include_priv = false)
160
+ method_name = method.to_s
161
+ if attributes.nil?
162
+ super
163
+ elsif method_name =~ /(?:=|\?)$/ && attributes.include?($`)
164
+ true
165
+ else
166
+ super
167
+ end
168
+ end
169
+
170
+ def destroy
171
+ self.class.records.delete(self)
172
+ self
173
+ end
174
+
175
+ protected
176
+ def read_attribute(name)
177
+ @attributes[name]
178
+ end
179
+
180
+ def write_attribute(name, value)
181
+ @attributes[name] = value
182
+ end
183
+
184
+ def generate_id
185
+ object_id
186
+ end
187
+
188
+ def create
189
+ self.id ||= generate_id
190
+ self.class.records << self.dup
191
+ save_previous_changes
192
+ end
193
+
194
+ def update
195
+ item = self.class.raw_find(id)
196
+ item.load(attributes)
197
+ save_previous_changes
198
+ end
199
+
200
+ def save_previous_changes
201
+ @previously_changed = changes
202
+ changed_attributes.clear
203
+ end
204
+
205
+ private
206
+
207
+ def method_missing(method_symbol, *arguments) #:nodoc:
208
+ method_name = method_symbol.to_s
209
+
210
+ if method_name =~ /(=|\?)$/
211
+ case $1
212
+ when "="
213
+ attribute_will_change!($`)
214
+ attributes[$`] = arguments.first
215
+ when "?"
216
+ attributes[$`]
217
+ end
218
+ else
219
+ return attributes[method_name] if attributes.include?(method_name)
220
+ super
221
+ end
222
+ end
223
+ end
224
+
225
+ class Base
226
+ extend ActiveModel::Naming
227
+ include ActiveModel::Conversion
228
+ include Observing, Validations, Scriber
229
+ end
230
+ end
@@ -0,0 +1,20 @@
1
+ module SuperModel
2
+ module Callbacks
3
+ extend ActiveSupport::Concern
4
+ extend ActiveModel::Callbacks
5
+
6
+ included do
7
+ define_model_callbacks :create, :save, :update, :destroy
8
+ %w( create save update destroy ).each do |method|
9
+ class_eval(<<-EOS, __FILE__, __LINE__ + 1)
10
+ def #{method}_with_callbacks(*args, &block)
11
+ _run_#{method}_callbacks do
12
+ #{method}_without_notifications(*args, &block)
13
+ end
14
+ end
15
+ EOS
16
+ alias_method_chain(method, :callbacks)
17
+ end
18
+ end
19
+ end
20
+ 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,56 @@
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
@@ -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,51 @@
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
@@ -0,0 +1,30 @@
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
data/lib/supermodel.rb ADDED
@@ -0,0 +1,33 @@
1
+ gem "activesupport"
2
+ gem "activemodel"
3
+
4
+ require "active_support/core_ext/class/attribute_accessors"
5
+ require "active_support/core_ext/class/inheritable_attributes"
6
+ require "active_support/core_ext/hash/indifferent_access"
7
+ require "active_support/core_ext/kernel/reporting"
8
+ require "active_support/core_ext/module/attr_accessor_with_default"
9
+ require "active_support/core_ext/module/delegation"
10
+ require "active_support/core_ext/module/aliasing"
11
+ require "active_support/core_ext/object/blank"
12
+ require "active_support/core_ext/object/misc"
13
+ require "active_support/core_ext/object/try"
14
+ require "active_support/core_ext/object/to_query"
15
+
16
+ require "active_model"
17
+
18
+
19
+ module SuperModel
20
+ class SuperModelError < StandardError; end
21
+ class UnknownRecord < SuperModelError; end
22
+ class InvalidRecord < SuperModelError; end
23
+ end
24
+
25
+ $: << File.dirname(__FILE__)
26
+
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"
@@ -0,0 +1,53 @@
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 = %q{supermodel}
8
+ s.version = "0.0.1"
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-02-04}
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
+ "README",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "lib/super_model.rb",
24
+ "lib/supermodel.rb",
25
+ "lib/supermodel/base.rb",
26
+ "lib/supermodel/callbacks.rb",
27
+ "lib/supermodel/observing.rb",
28
+ "lib/supermodel/persist.rb",
29
+ "lib/supermodel/random_id.rb",
30
+ "lib/supermodel/scriber.rb",
31
+ "lib/supermodel/validations.rb",
32
+ "supermodel.gemspec"
33
+ ]
34
+ s.homepage = %q{http://github.com/maccman/supermodel}
35
+ s.rdoc_options = ["--charset=UTF-8"]
36
+ s.require_paths = ["lib"]
37
+ s.rubygems_version = %q{1.3.5}
38
+ s.summary = %q{In memory DB using ActiveModel}
39
+
40
+ if s.respond_to? :specification_version then
41
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
42
+ s.specification_version = 3
43
+
44
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
45
+ s.add_runtime_dependency(%q<activemodel>, [">= 0"])
46
+ else
47
+ s.add_dependency(%q<activemodel>, [">= 0"])
48
+ end
49
+ else
50
+ s.add_dependency(%q<activemodel>, [">= 0"])
51
+ end
52
+ end
53
+
metadata ADDED
@@ -0,0 +1,77 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: supermodel
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Alex MacCaw
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2010-02-04 00:00:00 +00:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: activemodel
17
+ 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:
25
+ description: In memory DB using ActiveModel
26
+ email: info@eribium.org
27
+ executables: []
28
+
29
+ extensions: []
30
+
31
+ extra_rdoc_files:
32
+ - README
33
+ files:
34
+ - .gitignore
35
+ - README
36
+ - Rakefile
37
+ - VERSION
38
+ - lib/super_model.rb
39
+ - lib/supermodel.rb
40
+ - lib/supermodel/base.rb
41
+ - lib/supermodel/callbacks.rb
42
+ - lib/supermodel/observing.rb
43
+ - lib/supermodel/persist.rb
44
+ - lib/supermodel/random_id.rb
45
+ - lib/supermodel/scriber.rb
46
+ - lib/supermodel/validations.rb
47
+ - supermodel.gemspec
48
+ has_rdoc: true
49
+ homepage: http://github.com/maccman/supermodel
50
+ licenses: []
51
+
52
+ post_install_message:
53
+ rdoc_options:
54
+ - --charset=UTF-8
55
+ require_paths:
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:
69
+ requirements: []
70
+
71
+ rubyforge_project:
72
+ rubygems_version: 1.3.5
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: In memory DB using ActiveModel
76
+ test_files: []
77
+