memory_model 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (46) hide show
  1. data/.gitignore +17 -0
  2. data/.rspec +2 -0
  3. data/.travis.yml +6 -0
  4. data/Gemfile +4 -0
  5. data/Guardfile +5 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +31 -0
  8. data/Rakefile +1 -0
  9. data/lib/concerned_inheritance/class_methods.rb +28 -0
  10. data/lib/concerned_inheritance/delegator.rb +21 -0
  11. data/lib/concerned_inheritance/module_methods.rb +11 -0
  12. data/lib/concerned_inheritance.rb +27 -0
  13. data/lib/memory_model/base/actionable.rb +95 -0
  14. data/lib/memory_model/base/attributable.rb +76 -0
  15. data/lib/memory_model/base/collectable.rb +22 -0
  16. data/lib/memory_model/base/comparable.rb +16 -0
  17. data/lib/memory_model/base/fieldable/field.rb +35 -0
  18. data/lib/memory_model/base/fieldable/field_set.rb +74 -0
  19. data/lib/memory_model/base/fieldable.rb +45 -0
  20. data/lib/memory_model/base/persistence.rb +15 -0
  21. data/lib/memory_model/base/versionable.rb +17 -0
  22. data/lib/memory_model/base.rb +51 -0
  23. data/lib/memory_model/collection.rb +80 -0
  24. data/lib/memory_model/core_ext/object.rb +5 -0
  25. data/lib/memory_model/version.rb +3 -0
  26. data/lib/memory_model.rb +13 -0
  27. data/memory_model.gemspec +30 -0
  28. data/spec/concerned_inheritance/class_methods_spec.rb +57 -0
  29. data/spec/concerned_inheritance/delegator_spec.rb +52 -0
  30. data/spec/concerned_inheritance/module_methods_spec.rb +27 -0
  31. data/spec/memory_model/base/actionable_spec.rb +359 -0
  32. data/spec/memory_model/base/attributable_spec.rb +143 -0
  33. data/spec/memory_model/base/collectable_spec.rb +24 -0
  34. data/spec/memory_model/base/comparable_spec.rb +155 -0
  35. data/spec/memory_model/base/fieldable/field_set_spec.rb +160 -0
  36. data/spec/memory_model/base/fieldable/field_spec.rb +96 -0
  37. data/spec/memory_model/base/fieldable_spec.rb +23 -0
  38. data/spec/memory_model/base/persistence_spec.rb +37 -0
  39. data/spec/memory_model/base/versionable_spec.rb +31 -0
  40. data/spec/memory_model/base_spec.rb +52 -0
  41. data/spec/memory_model/collection_spec.rb +216 -0
  42. data/spec/memory_model/concerned_inheritance_spec.rb +24 -0
  43. data/spec/memory_model/core_ext/object_spec.rb +12 -0
  44. data/spec/spec_helper.rb +12 -0
  45. data/spec/support/active_model_lint.rb +16 -0
  46. metadata +253 -0
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - "1.9.3"
4
+ - jruby-19mode # JRuby in 1.9 mode
5
+ # uncomment this line if your project needs to run something other than `rake`:
6
+ script: bundle exec rspec
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in memory_model.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,5 @@
1
+ guard 'rspec', after_all_pass: false, all_on_start: false, cli: '--profile' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { "spec" }
5
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Jason Waldrip
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,31 @@
1
+ [![Build Status](https://travis-ci.org/jwaldrip/memory_model.png?branch=master)](https://travis-ci.org/jwaldrip/memory_model)
2
+
3
+ # MemoryModel
4
+
5
+ TODO: Write a gem description
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ gem 'memory_model'
12
+
13
+ And then execute:
14
+
15
+ $ bundle
16
+
17
+ Or install it yourself as:
18
+
19
+ $ gem install memory_model
20
+
21
+ ## Usage
22
+
23
+ TODO: Write usage instructions here
24
+
25
+ ## Contributing
26
+
27
+ 1. Fork it
28
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
29
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
30
+ 4. Push to the branch (`git push origin my-new-feature`)
31
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,28 @@
1
+ module ConcernedInheritance::ClassMethods
2
+
3
+ def inherited_callbacks
4
+ (self.singleton_class.ancestors + self.ancestors).select do |ancestor|
5
+ ancestor.instance_variable_defined? :@inherited_callbacks
6
+ end.map do |ancestor|
7
+ ancestor.instance_variable_get :@inherited_callbacks
8
+ end.flatten
9
+ end
10
+
11
+ private
12
+
13
+ def inherited(subclass=nil, &block)
14
+ if subclass.nil?
15
+ define_inherited_callback(&block)
16
+ else
17
+ run_inherited_callbacks(subclass)
18
+ super(subclass)
19
+ end
20
+ end
21
+
22
+ def run_inherited_callbacks(subclass)
23
+ self.inherited_callbacks.each do |callback|
24
+ ConcernedInheritance::Delegator.new(self, subclass, callback)
25
+ end
26
+ end
27
+
28
+ end
@@ -0,0 +1,21 @@
1
+ class ConcernedInheritance::Delegator < BasicObject
2
+
3
+ attr_reader :baseclass, :subclass
4
+
5
+ def initialize(baseclass, subclass, callback)
6
+ @baseclass = baseclass
7
+ @subclass = subclass
8
+ if callback.not_a?(::Proc)
9
+ raise ::ArgumentError, "#{callback} must be a proc"
10
+ elsif (-1..0).cover?(callback.arity)
11
+ instance_eval(&callback)
12
+ else
13
+ raise ::ArgumentError, "#{callback} must have an arity of 0, got #{callback.arity}"
14
+ end
15
+ end
16
+
17
+ def method_missing(m, *args, &block)
18
+ subclass.send m, *args, &block
19
+ end
20
+
21
+ end
@@ -0,0 +1,11 @@
1
+ module ConcernedInheritance::ModuleMethods
2
+
3
+ def inherited_callbacks
4
+ @inherited_callbacks
5
+ end
6
+
7
+ def inherited(&block)
8
+ define_inherited_callback(&block)
9
+ end
10
+
11
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/core_ext/class/attribute'
3
+ require 'active_support/dependencies/autoload'
4
+
5
+ module ConcernedInheritance
6
+ extend ActiveSupport::Autoload
7
+
8
+ autoload :Delegator
9
+ autoload :ClassMethods
10
+ autoload :ModuleMethods
11
+
12
+ def self.extended(base)
13
+ case base
14
+ when Class
15
+ base.extend ClassMethods
16
+ when Module
17
+ base.extend ModuleMethods
18
+ end
19
+ base.instance_variable_set :@inherited_callbacks, [] unless base.instance_variable_defined? :@inherited_callbacks
20
+ end
21
+
22
+ def define_inherited_callback(&block)
23
+ raise ArgumentError, 'missing required block' unless block_given?
24
+ @inherited_callbacks += [block]
25
+ end
26
+
27
+ end
@@ -0,0 +1,95 @@
1
+ require 'ice_nine'
2
+ require 'ice_nine/core_ext/object'
3
+
4
+ module MemoryModel::Base::Actionable
5
+ extend ActiveSupport::Concern
6
+
7
+ included do
8
+ define_model_callbacks :create, :update, :save, :destroy
9
+ attr_reader :timestamp
10
+ end
11
+
12
+ VALID_IVARS = [
13
+ :@deleted,
14
+ :@attributes,
15
+ :@timestamp,
16
+ :@version
17
+ ]
18
+
19
+ def commit
20
+ @timestamp = Time.now
21
+ @version = SecureRandom.hex(6)
22
+ self.class.insert self
23
+ self
24
+ end
25
+
26
+ def delete
27
+ @deleted = true
28
+ commit
29
+ freeze
30
+ end
31
+
32
+ def deleted?
33
+ !!@deleted
34
+ end
35
+
36
+ def deleted_at
37
+ deleted? ? @timestamp : nil
38
+ end
39
+
40
+ def destroy
41
+ run_callbacks :destroy do
42
+ delete
43
+ end
44
+ end
45
+
46
+ def dup
47
+ deep_dup
48
+ end
49
+
50
+ def deep_dup
51
+ Marshal.load Marshal.dump self
52
+ end
53
+
54
+ def freeze
55
+ instance_variables.reject { |ivar| ivar.in? VALID_IVARS }.each do |ivar|
56
+ remove_instance_variable ivar if instance_variable_defined?(ivar)
57
+ end
58
+ instance_variables.each { |ivar| instance_variable_get(ivar).freeze }
59
+ deep_freeze
60
+ super
61
+ end
62
+
63
+ def save
64
+ callback = persisted? ? :update : :create
65
+ run_callbacks callback do
66
+ run_callbacks :save do
67
+ commit
68
+ end
69
+ end
70
+ end
71
+
72
+ def restore
73
+ instance = frozen? ? self.dup : self
74
+ instance.instance_variable_set :@deleted, false
75
+ instance.save
76
+ instance
77
+ end
78
+
79
+ module ClassMethods
80
+
81
+ def create(attributes={})
82
+ new(attributes).save
83
+ end
84
+
85
+ def delete_all
86
+ all.map(&:delete).reject(&:deleted?).empty?
87
+ end
88
+
89
+ def destroy_all
90
+ all.map(&:destroy).reject(&:deleted?).empty?
91
+ end
92
+
93
+ end
94
+
95
+ end
@@ -0,0 +1,76 @@
1
+ module MemoryModel::Base::Attributable
2
+ extend ActiveSupport::Concern
3
+ include ActiveModel::AttributeMethods
4
+ include ActiveModel::Dirty
5
+
6
+ included do
7
+ attr_reader :attributes
8
+ delegate :to_hash, to: :attributes
9
+ attribute_method_affix :prefix => 'reset_', :suffix => '_to_default!'
10
+ attribute_method_prefix 'clear_'
11
+ end
12
+
13
+ def has_attribute?(key)
14
+ case value = @attributes[key]
15
+ when NilClass, String
16
+ !value.nil?
17
+ else
18
+ value.present?
19
+ end
20
+ end
21
+
22
+ def inspect
23
+ inspection = if @attributes
24
+ fields.reduce([]) { |array, name|
25
+ array << "#{name}: #{attribute_for_inspect(name)}" if has_attribute?(name)
26
+ array
27
+ }.join(", ")
28
+ else
29
+ "not initialized"
30
+ end
31
+ "#<#{[self.class, inspection].join(' ')}>"
32
+ end
33
+
34
+ def read_attribute(name)
35
+ @attributes[name]
36
+ end
37
+
38
+ alias :[] :read_attribute
39
+
40
+ def write_attribute(name, value)
41
+ raise MemoryModel::InvalidFieldError,
42
+ "#{name} is not a valid field" unless fields.include? name
43
+ raise MemoryModel::FieldReadOnlyError,
44
+ "#{name} is read only" if fields[name].options[:readonly]
45
+
46
+ send "#{name}_will_change!" unless value == read_attribute(name) || new_record?
47
+ @attributes[name] = value
48
+ end
49
+
50
+ alias :[]= :write_attribute
51
+
52
+ protected
53
+
54
+ def attribute_for_inspect(attr_name)
55
+ value = read_attribute(attr_name)
56
+
57
+ if value.is_a?(String) && value.length > 50
58
+ "#{value[0..50]}...".inspect
59
+ elsif value.is_a?(Date) || value.is_a?(Time)
60
+ value.to_s
61
+ else
62
+ value.inspect
63
+ end
64
+ end
65
+
66
+ private
67
+
68
+ def reset_attribute_to_default!(attr)
69
+ write_attribute attr, fields.default_values(self).with_indifferent_access[attr]
70
+ end
71
+
72
+ def clear_attribute(attr)
73
+ write_attribute attr, nil
74
+ end
75
+
76
+ end
@@ -0,0 +1,22 @@
1
+ require 'active_support/concern'
2
+
3
+ module MemoryModel::Base::Collectable
4
+ extend ActiveSupport::Concern
5
+ extend ConcernedInheritance
6
+
7
+ inherited do
8
+ instance_variable_set :@collection, baseclass.collection
9
+ end
10
+
11
+ module ClassMethods
12
+ delegate :all, :find, :insert, :<<, :deleted, to: :collection
13
+ delegate :first, :last, to: :all
14
+
15
+ def collection
16
+ return nil if self == MemoryModel::Base
17
+ @collection ||= MemoryModel::Collection.new(self)
18
+ end
19
+
20
+ end
21
+
22
+ end
@@ -0,0 +1,16 @@
1
+ module MemoryModel::Base::Comparable
2
+
3
+ def ==(other_object)
4
+ attributes.slice(*fields.comparable) ==
5
+ other_object.to_hash.with_indifferent_access.slice(*fields.comparable)
6
+ end
7
+
8
+ def !=(other_object)
9
+ !(self == other_object)
10
+ end
11
+
12
+ def ===(other_object)
13
+ other_object.kind_of?(self.class) && self == other_object
14
+ end
15
+
16
+ end
@@ -0,0 +1,35 @@
1
+ class MemoryModel::Base::Fieldable::Field
2
+
3
+ attr_reader :name, :options
4
+ delegate :inspect, to: :to_sym
5
+
6
+ def initialize(name, options={ })
7
+ @name = name.to_sym
8
+ @options = options.reverse_merge!({ readonly: false, comparable: true })
9
+ end
10
+
11
+ def ==(other_object)
12
+ self.to_sym == other_object.to_sym
13
+ end
14
+
15
+ def comparable?
16
+ !!@options[:comparable]
17
+ end
18
+
19
+ def default
20
+ @options[:default]
21
+ end
22
+
23
+ def readonly?
24
+ !!@options[:readonly]
25
+ end
26
+
27
+ def to_sym
28
+ @name.to_sym
29
+ end
30
+
31
+ def to_s
32
+ @name.to_s
33
+ end
34
+
35
+ end
@@ -0,0 +1,74 @@
1
+ require 'active_support/core_ext/hash/keys'
2
+ require 'set'
3
+
4
+ class MemoryModel::Base::Fieldable::FieldSet
5
+
6
+ Field = MemoryModel::Base::Fieldable::Field
7
+
8
+ attr_reader :fields
9
+ delegate :include?, to: :fields
10
+
11
+ def initialize
12
+ @fields = []
13
+ end
14
+
15
+ def [](name)
16
+ @fields.find { |f| f.name == name.to_sym }
17
+ end
18
+
19
+ def <<(attr)
20
+ attr = Field.new(attr) unless attr.is_a? Field
21
+ @fields << attr
22
+ end
23
+
24
+ def add(attr, options={ })
25
+ @fields.delete_if { |f| f == attr }
26
+ @fields << Field.new(attr, options)
27
+ end
28
+
29
+ def comparable
30
+ @fields.select(&:comparable?).map(&:to_sym)
31
+ end
32
+
33
+ def inspect
34
+ to_a.inspect
35
+ end
36
+
37
+ def default_values(model, attributes={ })
38
+ @fields.reduce(attributes.symbolize_keys) do |attrs, field|
39
+ raise MemoryModel::ReadonlyFieldError if attrs[field.name].present? && field.readonly?
40
+ default = field.default.is_a?(Symbol) ? field.default.to_proc : field.default
41
+ attrs[field.name] ||= if default.nil?
42
+ nil
43
+ elsif default.is_a? String
44
+ default
45
+ elsif default.not_a?(::Proc)
46
+ raise ArgumentError, "#{default} must be a string, symbol, lamba or proc"
47
+ elsif default.lambda? && default.arity == 0
48
+ default.call
49
+ elsif default.arity.in? -1..0
50
+ model.instance_eval(&default)
51
+ elsif default.arity == 1
52
+ default.yield model
53
+ else
54
+ raise ArgumentError, "#{default} must have an arity of 0..1, got #{default.arity}"
55
+ end
56
+ attrs
57
+ end
58
+ end
59
+
60
+ def to_a
61
+ @fields.map(&:to_sym)
62
+ end
63
+
64
+ private
65
+
66
+ def method_missing(m, *args, &block)
67
+ if to_a.respond_to? m
68
+ to_a.send m, *args, &block
69
+ else
70
+ super
71
+ end
72
+ end
73
+
74
+ end
@@ -0,0 +1,45 @@
1
+ require 'active_support/concern'
2
+ require 'active_support/dependencies/autoload'
3
+ require 'securerandom'
4
+
5
+ module MemoryModel::Base::Fieldable
6
+ extend ConcernedInheritance
7
+ extend ActiveSupport::Concern
8
+ extend ActiveSupport::Autoload
9
+ include ActiveModel::AttributeMethods
10
+
11
+ autoload :FieldSet
12
+ autoload :Field
13
+
14
+ inherited do
15
+ instance_variable_set :@fields, baseclass.fields
16
+ field :id, readonly: true, default: -> { SecureRandom.uuid }, comparable: false
17
+ end
18
+
19
+ module ClassMethods
20
+ def field(attr, options={ })
21
+ define_attribute_method attr unless instance_method_already_implemented? attr
22
+ fields.add(attr.to_sym, options)
23
+ class_eval <<-RUBY, __FILE__, __LINE__ + 1
24
+ def #{attr}
25
+ read_attribute :#{attr}
26
+ end
27
+
28
+ def #{attr}=(value)
29
+ write_attribute :#{attr}, value
30
+ end
31
+ RUBY
32
+ end
33
+
34
+ def fields
35
+ return nil if self == MemoryModel::Base
36
+ @fields ||= FieldSet.new
37
+ end
38
+
39
+ end
40
+
41
+ def fields
42
+ self.class.fields
43
+ end
44
+
45
+ end
@@ -0,0 +1,15 @@
1
+ module MemoryModel::Base::Persistence
2
+
3
+ def persisted?
4
+ !!self.class.find(self.id)
5
+ rescue MemoryModel::RecordNotFoundError
6
+ false
7
+ end
8
+
9
+ alias :exists? :persisted?
10
+
11
+ def new_record?
12
+ !persisted?
13
+ end
14
+
15
+ end
@@ -0,0 +1,17 @@
1
+ module MemoryModel::Base::Versionable
2
+
3
+ def versions
4
+ instances = self.class.collection.records.select { |i| i.id == self.id }
5
+ instances.reduce({ }) do |hash, instance|
6
+ hash[instance.version] = instance
7
+ hash
8
+ end
9
+ end
10
+
11
+ def version
12
+ @version
13
+ end
14
+
15
+ end
16
+
17
+ # MemoryModel::Base::Immutable # todo!
@@ -0,0 +1,51 @@
1
+ require "concerned_inheritance"
2
+ require 'active_support/core_ext/object'
3
+ require 'active_support/core_ext/hash'
4
+ require 'active_support/dependencies/autoload'
5
+ require 'active_support/core_ext/hash/indifferent_access'
6
+ require 'active_model'
7
+
8
+ class MemoryModel::Base
9
+ extend ActiveSupport::Autoload
10
+ extend ConcernedInheritance
11
+
12
+ autoload :Fieldable
13
+ autoload :Collectable
14
+ autoload :Comparable
15
+ autoload :Actionable
16
+ autoload :Attributable
17
+ autoload :Versionable
18
+ autoload :Persistence
19
+
20
+ # Active Model Additions
21
+ extend ActiveModel::Callbacks
22
+ extend ActiveModel::Naming
23
+ extend ActiveModel::Translation
24
+ include ActiveModel::Conversion
25
+ include ActiveModel::MassAssignmentSecurity
26
+ include ActiveModel::Observing
27
+ include ActiveModel::Serialization
28
+ include ActiveModel::Validations
29
+
30
+ # Memory Model Additions
31
+ include Fieldable
32
+ include Collectable
33
+ include Comparable
34
+ include Actionable
35
+ include Attributable
36
+ include Versionable
37
+ include Persistence
38
+
39
+ # Active Model Callbacks
40
+ define_model_callbacks :initialize, only: [:after]
41
+
42
+ def initialize(attributes={ })
43
+ unless self.class.collection.is_a? MemoryModel::Collection
44
+ raise MemoryModel::InvalidCollectionError, "#{self.class} does not have an assigned collection"
45
+ end
46
+ @attributes = fields.default_values(self, attributes).with_indifferent_access
47
+ @deleted = false
48
+ run_callbacks :initialize
49
+ end
50
+
51
+ end