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