toystore 0.9.0 → 0.10.0

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