supermodel 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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
+