toystore 0.9.0 → 0.10.0

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/lib/toy.rb CHANGED
@@ -72,12 +72,14 @@ module Toy
72
72
  autoload 'Cloneable', 'toy/cloneable'
73
73
  autoload 'Equality', 'toy/equality'
74
74
  autoload 'Inspect', 'toy/inspect'
75
+ autoload 'Inheritance', 'toy/inheritance'
75
76
  autoload 'Logger', 'toy/logger'
76
77
  autoload 'MassAssignmentSecurity', 'toy/mass_assignment_security'
77
78
  autoload 'Persistence', 'toy/persistence'
78
79
  autoload 'Querying', 'toy/querying'
79
80
  autoload 'Reloadable', 'toy/reloadable'
80
81
  autoload 'Serialization', 'toy/serialization'
82
+ autoload 'AssociationSerialization','toy/association_serialization'
81
83
  autoload 'Timestamps', 'toy/timestamps'
82
84
  autoload 'Validations', 'toy/validations'
83
85
 
@@ -100,4 +102,4 @@ require 'toy/plugins'
100
102
  require 'toy/object'
101
103
  require 'toy/store'
102
104
 
103
- Toy::IdentityMap.enabled = false
105
+ Toy::IdentityMap.enabled = false
@@ -0,0 +1,50 @@
1
+ module Toy
2
+ module AssociationSerialization
3
+ extend ActiveSupport::Concern
4
+ include Serialization
5
+
6
+ def serializable_hash(options = nil)
7
+ options ||= {}
8
+ super.tap { |hash|
9
+ serializable_add_includes(options) do |association, records, opts|
10
+ hash[association] = records.is_a?(Enumerable) ?
11
+ records.map { |r| r.serializable_hash(opts) } :
12
+ records.serializable_hash(opts)
13
+ end
14
+ }
15
+ end
16
+
17
+ private
18
+
19
+ # Add associations specified via the <tt>:includes</tt> option.
20
+ # Expects a block that takes as arguments:
21
+ # +association+ - name of the association
22
+ # +records+ - the association record(s) to be serialized
23
+ # +opts+ - options for the association records
24
+ def serializable_add_includes(options = {})
25
+ return unless include_associations = options.delete(:include)
26
+
27
+ base_only_or_except = { :except => options[:except],
28
+ :only => options[:only] }
29
+
30
+ include_has_options = include_associations.is_a?(Hash)
31
+ associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
32
+
33
+ for association in associations
34
+ records = if self.class.list?(association)
35
+ send(association).to_a
36
+ elsif self.class.reference?(association) || self.class.parent_reference?(association)
37
+ send(association)
38
+ end
39
+
40
+ unless records.nil?
41
+ association_options = include_has_options ? include_associations[association] : base_only_or_except
42
+ opts = options.merge(association_options)
43
+ yield(association, records, opts)
44
+ end
45
+ end
46
+
47
+ options[:include] = include_associations
48
+ end
49
+ end
50
+ end
@@ -28,7 +28,12 @@ module Toy
28
28
 
29
29
  def default
30
30
  if options.key?(:default)
31
- options[:default].respond_to?(:call) ? options[:default].call : options[:default]
31
+ default = options[:default]
32
+ if default.respond_to?(:call)
33
+ backwards_compatible_call(default)
34
+ else
35
+ default
36
+ end
32
37
  else
33
38
  type.respond_to?(:store_default) ? type.store_default : nil
34
39
  end
@@ -64,5 +69,15 @@ module Toy
64
69
  name == other.name
65
70
  end
66
71
  alias :== :eql?
72
+
73
+ private
74
+
75
+ def backwards_compatible_call(block)
76
+ if block.arity == 1
77
+ block.call(model)
78
+ else
79
+ block.call
80
+ end
81
+ end
67
82
  end
68
- end
83
+ end
@@ -17,5 +17,9 @@ module Toy
17
17
  end
18
18
  super other
19
19
  end
20
+
21
+ def hash
22
+ id.hash
23
+ end
20
24
  end
21
- end
25
+ end
@@ -8,6 +8,11 @@ module Toy
8
8
  def next_key(object)
9
9
  raise NotImplementedError, "#{self.class.name}#next_key isn't implemented."
10
10
  end
11
+
12
+ def eql?(other)
13
+ self.class == other.class && key_type == other.key_type
14
+ end
15
+ alias :== :eql?
11
16
  end
12
17
  end
13
18
  end
@@ -52,10 +52,9 @@ module Toy
52
52
  def get_from_identity_map(id)
53
53
  IdentityMap.repository[id] if IdentityMap.enabled?
54
54
  end
55
+ private :get_from_identity_map
55
56
 
56
57
  def load(id, attrs)
57
- return nil if attrs.nil?
58
-
59
58
  if IdentityMap.enabled? && instance = IdentityMap.repository[id]
60
59
  instance
61
60
  else
@@ -0,0 +1,29 @@
1
+ module Toy
2
+ module Inheritance
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ DuplicatedInstanceVariables = [
7
+ :attributes,
8
+ :key_factory,
9
+ :lists,
10
+ :references,
11
+ :adapter,
12
+ ]
13
+
14
+ def inherited(subclass)
15
+ DuplicatedInstanceVariables.each do |name|
16
+ subclass.instance_variable_set("@#{name}", send(name).dup) if respond_to?(name)
17
+ end
18
+
19
+ subclass.attribute(:type, String, :default => subclass.name)
20
+
21
+ super
22
+ end
23
+ end
24
+
25
+ def type
26
+ read_attribute(:type)
27
+ end
28
+ end
29
+ end
@@ -2,11 +2,24 @@ module Toy
2
2
  module Inspect
3
3
  extend ActiveSupport::Concern
4
4
 
5
+ module ClassMethods
6
+ def inspect
7
+ keys = attributes.keys - ['id']
8
+ nice_string = keys.sort.map do |name|
9
+ type = attributes[name].type
10
+ "#{name}:#{type}"
11
+ end.join(" ")
12
+ "#{name}(id:#{attributes['id'].type} #{nice_string})"
13
+ end
14
+ end
15
+
5
16
  def inspect
6
- attributes_as_nice_string = self.class.attributes.keys.map(&:to_s).sort.map do |name|
17
+ keys = self.class.attributes.keys - ['id']
18
+ attributes_as_nice_string = keys.map(&:to_s).sort.map do |name|
7
19
  "#{name}: #{read_attribute(name).inspect}"
8
- end.join(", ")
9
- "#<#{self.class}:#{object_id} #{attributes_as_nice_string}>"
20
+ end
21
+ attributes_as_nice_string.unshift("id: #{read_attribute(:id).inspect}")
22
+ "#<#{self.class}:#{object_id} #{attributes_as_nice_string.join(', ')}>"
10
23
  end
11
24
  end
12
- end
25
+ end
@@ -11,6 +11,12 @@ module Toy
11
11
  include Equality
12
12
  include Inspect
13
13
  include Logger
14
+ include Inheritance
15
+ include Serialization
16
+ end
17
+
18
+ def persisted?
19
+ false
14
20
  end
15
21
  end
16
22
  end
@@ -4,7 +4,9 @@ module Toy
4
4
 
5
5
  module ClassMethods
6
6
  def get(id)
7
- load(id, adapter.read(id))
7
+ if (attrs = adapter.read(id))
8
+ load(id, attrs)
9
+ end
8
10
  end
9
11
 
10
12
  def get!(id)
@@ -29,8 +31,23 @@ module Toy
29
31
  alias :has_key? :key?
30
32
 
31
33
  def load(id, attrs)
32
- attrs && allocate.initialize_from_database(attrs.update('id' => id))
34
+ attrs ||= {}
35
+ instance = constant_from_attrs(attrs).allocate
36
+ instance.initialize_from_database(attrs.update('id' => id))
33
37
  end
38
+
39
+ def constant_from_attrs(attrs)
40
+ return self if attrs.nil?
41
+
42
+ type = attrs[:type] || attrs['type']
43
+
44
+ return self if type.nil?
45
+
46
+ type.constantize
47
+ rescue NameError
48
+ self
49
+ end
50
+ private :constant_from_attrs
34
51
  end
35
52
  end
36
- end
53
+ end
@@ -120,17 +120,31 @@ module Toy
120
120
  def create_accessors
121
121
  model.class_eval """
122
122
  def #{name}
123
- #{instance_variable} ||= self.class.references[:#{name}].new_proxy(self)
123
+ #{instance_variable} ||= self.class.references[:#{name}].new_proxy(self).target
124
124
  end
125
125
 
126
126
  def #{name}=(record)
127
- #{name}.replace(record)
127
+ self.class.references[:#{name}].new_proxy(self).replace(record)
128
+ #{instance_variable} = record
128
129
  end
129
130
 
130
131
  def #{name}?
131
- #{name}.present?
132
+ !!#{name}
133
+ end
134
+
135
+ def build_#{name}(attrs={})
136
+ self.class.references[:#{name}].new_proxy(self).build(attrs)
137
+ end
138
+
139
+ def create_#{name}(attrs={})
140
+ self.class.references[:#{name}].new_proxy(self).create(attrs)
141
+ end
142
+
143
+ def reset_#{name}
144
+ #{instance_variable} = nil
145
+ self.class.references[:#{name}].new_proxy(self).reset
132
146
  end
133
147
  """
134
148
  end
135
149
  end
136
- end
150
+ end
@@ -7,11 +7,11 @@ module Toy
7
7
  initialize_attributes_with_defaults
8
8
  send(:attributes=, attrs, new_record?)
9
9
  self.class.lists.each_key { |name| send(name).reset }
10
- self.class.references.each_key { |name| send(name).reset }
10
+ self.class.references.each_key { |name| send("reset_#{name}") }
11
11
  else
12
12
  raise NotFound.new(id)
13
13
  end
14
14
  self
15
15
  end
16
16
  end
17
- end
17
+ end
@@ -37,46 +37,7 @@ module Toy
37
37
  end
38
38
  end
39
39
 
40
- serializable_add_includes(options) do |association, records, opts|
41
- hash[association] = records.is_a?(Enumerable) ?
42
- records.map { |r| r.serializable_hash(opts) } :
43
- records.serializable_hash(opts)
44
- end
45
-
46
40
  hash
47
41
  end
48
-
49
- private
50
-
51
- # Add associations specified via the <tt>:includes</tt> option.
52
- # Expects a block that takes as arguments:
53
- # +association+ - name of the association
54
- # +records+ - the association record(s) to be serialized
55
- # +opts+ - options for the association records
56
- def serializable_add_includes(options = {})
57
- return unless include_associations = options.delete(:include)
58
-
59
- base_only_or_except = { :except => options[:except],
60
- :only => options[:only] }
61
-
62
- include_has_options = include_associations.is_a?(Hash)
63
- associations = include_has_options ? include_associations.keys : Array.wrap(include_associations)
64
-
65
- for association in associations
66
- records = if self.class.list?(association)
67
- send(association).to_a
68
- elsif self.class.reference?(association) || self.class.parent_reference?(association)
69
- send(association)
70
- end
71
-
72
- unless records.nil?
73
- association_options = include_has_options ? include_associations[association] : base_only_or_except
74
- opts = options.merge(association_options)
75
- yield(association, records, opts)
76
- end
77
- end
78
-
79
- options[:include] = include_associations
80
- end
81
42
  end
82
- end
43
+ end
@@ -13,14 +13,14 @@ module Toy
13
13
 
14
14
  include Callbacks
15
15
  include Validations
16
- include Serialization
17
16
  include Timestamps
18
17
 
19
18
  include Lists
20
19
  include References
20
+ include AssociationSerialization
21
21
 
22
22
  include IdentityMap
23
23
  include Caching
24
24
  end
25
25
  end
26
- end
26
+ end
@@ -1,6 +1,8 @@
1
1
  module Toy
2
2
  module Timestamps
3
3
  extend ActiveSupport::Concern
4
+ include Attributes
5
+ include Callbacks
4
6
 
5
7
  module ClassMethods
6
8
  def timestamps
@@ -19,4 +21,4 @@ module Toy
19
21
  end
20
22
  end
21
23
  end
22
- end
24
+ end
@@ -1,3 +1,3 @@
1
1
  module Toy
2
- VERSION = "0.9.0"
3
- end
2
+ VERSION = "0.10.0"
3
+ end
@@ -11,7 +11,7 @@ log_path.mkpath
11
11
  require 'rubygems'
12
12
  require 'bundler'
13
13
 
14
- Bundler.require(:default, :development)
14
+ Bundler.require(:default, :test)
15
15
 
16
16
  require 'toy'
17
17
  require 'support/constants'
@@ -20,7 +20,6 @@ require 'support/identity_map_matcher'
20
20
  require 'support/name_and_number_key_factory'
21
21
 
22
22
  Logger.new(log_path.join('test.log')).tap do |log|
23
- LogBuddy.init(:logger => log)
24
23
  Toy.logger = log
25
24
  end
26
25
 
@@ -35,4 +34,4 @@ RSpec.configure do |c|
35
34
  Toy.reset
36
35
  Toy.key_factory = nil
37
36
  end
38
- end
37
+ end
@@ -4,35 +4,35 @@ module Support
4
4
 
5
5
  module ClassMethods
6
6
  def uses_constants(*constants)
7
- before { create_constants(*constants) }
7
+ before { create_constants *constants }
8
+ after { remove_constants *constants }
8
9
  end
9
10
  end
10
11
 
11
12
  def create_constants(*constants)
12
- constants.each { |constant| create_constant(constant) }
13
+ constants.each { |constant| create_constant constant }
13
14
  end
14
15
 
15
16
  def remove_constants(*constants)
16
- constants.each { |constant| remove_constant(constant) }
17
+ constants.each { |constant| remove_constant constant }
17
18
  end
18
19
 
19
- def create_constant(constant)
20
- remove_constant(constant)
21
- Kernel.const_set(constant, Model(constant))
20
+ def create_constant(constant, superclass=nil)
21
+ Object.const_set constant, Model(superclass)
22
22
  end
23
23
 
24
24
  def remove_constant(constant)
25
- Kernel.send(:remove_const, constant) if Kernel.const_defined?(constant)
25
+ if Object.const_defined?(constant)
26
+ Object.send :remove_const, constant
27
+ end
26
28
  end
27
29
 
28
- def Model(name=nil)
29
- Class.new.tap do |model|
30
- model.class_eval """
31
- def self.name; '#{name}' end
32
- def self.to_s; '#{name}' end
33
- """ if name
34
- model.send(:include, Toy::Store)
30
+ def Model(superclass=nil)
31
+ if superclass.nil?
32
+ Class.new { include Toy::Store }
33
+ else
34
+ Class.new(superclass) { include Toy::Store }
35
35
  end
36
36
  end
37
37
  end
38
- end
38
+ end