mongoid_alize 0.2.0 → 0.3.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 (32) hide show
  1. data/config/locales/en.yml +1 -0
  2. data/lib/mongoid/alize/callback.rb +48 -13
  3. data/lib/mongoid/alize/callbacks/from/many.rb +5 -5
  4. data/lib/mongoid/alize/callbacks/from/one.rb +14 -31
  5. data/lib/mongoid/alize/errors/invalid_configuration.rb +16 -0
  6. data/lib/mongoid/alize/from_callback.rb +4 -2
  7. data/lib/mongoid/alize/instance_helpers.rb +25 -0
  8. data/lib/mongoid/alize/macros.rb +65 -42
  9. data/lib/mongoid/alize/to_callback.rb +104 -9
  10. data/lib/mongoid_alize.rb +3 -7
  11. data/spec/app/models/head.rb +17 -1
  12. data/spec/app/models/mock_object.rb +6 -0
  13. data/spec/app/models/person.rb +16 -0
  14. data/spec/helpers/macros_helper.rb +11 -7
  15. data/spec/mongoid/alize/callback_spec.rb +62 -6
  16. data/spec/mongoid/alize/callbacks/from/one_spec.rb +41 -43
  17. data/spec/mongoid/alize/callbacks/to/many_from_many_spec.rb +23 -17
  18. data/spec/mongoid/alize/callbacks/to/many_from_one_spec.rb +158 -60
  19. data/spec/mongoid/alize/callbacks/to/one_from_many_spec.rb +110 -42
  20. data/spec/mongoid/alize/callbacks/to/one_from_one_spec.rb +167 -47
  21. data/spec/mongoid/alize/instance_helpers_spec.rb +36 -0
  22. data/spec/mongoid/alize/macros_spec.rb +90 -17
  23. data/spec/mongoid/alize/to_callback_spec.rb +87 -18
  24. data/spec/mongoid_alize_spec.rb +245 -26
  25. data/spec/spec_helper.rb +3 -1
  26. metadata +6 -8
  27. data/lib/mongoid/alize/callbacks/to/many_from_many.rb +0 -16
  28. data/lib/mongoid/alize/callbacks/to/many_from_one.rb +0 -15
  29. data/lib/mongoid/alize/callbacks/to/one_from_many.rb +0 -15
  30. data/lib/mongoid/alize/callbacks/to/one_from_one.rb +0 -15
  31. data/lib/mongoid/alize/to_many_callback.rb +0 -50
  32. data/lib/mongoid/alize/to_one_callback.rb +0 -43
@@ -7,4 +7,5 @@ en:
7
7
  "%{name} does not exist on the %{inverse_klass} model."
8
8
  already_defined_field:
9
9
  "%{name} is already defined on the %{klass} model."
10
+ invalid_configuration:
10
11
 
@@ -6,30 +6,34 @@ module Mongoid
6
6
 
7
7
  attr_accessor :klass
8
8
  attr_accessor :relation
9
- attr_accessor :reflect
9
+ attr_accessor :metadata
10
10
 
11
11
  attr_accessor :inverse_klass
12
12
  attr_accessor :inverse_relation
13
+ attr_accessor :inverse_metadata
14
+
15
+ attr_accessor :debug
13
16
 
14
17
  def initialize(_klass, _relation, _fields)
18
+ self.debug = ENV["ALIZE_DEBUG"]
19
+
15
20
  self.klass = _klass
16
21
  self.relation = _relation
17
22
  self.fields = _fields
18
23
 
19
- self.reflect = _klass.relations[_relation.to_s]
20
- self.inverse_klass = self.reflect.klass
21
- self.inverse_relation = self.reflect.inverse
22
-
23
- self.klass.send(:attr_accessor, :force_denormalization)
24
- self.inverse_klass.send(:attr_accessor, :force_denormalization)
24
+ self.metadata = _klass.relations[_relation.to_s]
25
+ if !(self.metadata.polymorphic? &&
26
+ self.metadata.stores_foreign_key?)
27
+ self.inverse_klass = self.metadata.klass
28
+ self.inverse_relation = self.metadata.inverse
29
+ self.inverse_metadata = self.inverse_klass.relations[inverse_relation.to_s]
30
+ end
25
31
  end
26
32
 
27
33
  def attach
28
34
  # implement in subclasses
29
35
  end
30
36
 
31
- protected
32
-
33
37
  def callback_attached?(callback_type, callback_name)
34
38
  !!klass.send(:"_#{callback_type}_callbacks").
35
39
  map(&:raw_filter).include?(callback_name)
@@ -42,6 +46,7 @@ module Mongoid
42
46
  def alias_callback
43
47
  unless callback_defined?(aliased_callback_name)
44
48
  klass.send(:alias_method, aliased_callback_name, callback_name)
49
+ klass.send(:public, aliased_callback_name)
45
50
  end
46
51
  end
47
52
 
@@ -53,13 +58,27 @@ module Mongoid
53
58
  "denormalize_#{direction}_#{relation}"
54
59
  end
55
60
 
56
- def joined_fields
57
- (fields + [:_id]).map {|f| "'#{f}'" }.join(", ")
61
+ def define_fields_method
62
+ _fields = fields
63
+ if fields.is_a?(Proc)
64
+ klass.send(:define_method, fields_method_name) do |inverse|
65
+ _fields.bind(self).call(inverse).map(&:to_s)
66
+ end
67
+ else
68
+ klass.send(:define_method, fields_method_name) do |inverse|
69
+ _fields.map(&:to_s)
70
+ end
71
+ end
58
72
  end
59
73
 
60
- def joined_field_values(source)
74
+ def fields_method_name
75
+ "#{callback_name}_fields"
76
+ end
77
+
78
+ def field_values(source, options={})
79
+ extras = options[:id] ? "['_id']" : "[]"
61
80
  <<-RUBY
62
- [#{joined_fields}].inject({}) { |hash, name|
81
+ (#{fields_method_name}(#{source}) + #{extras}).inject({}) { |hash, name|
63
82
  hash[name] = #{source}.send(name)
64
83
  hash
65
84
  }
@@ -73,6 +92,22 @@ module Mongoid
73
92
  def force_check
74
93
  "force || self.force_denormalization"
75
94
  end
95
+
96
+ def fields_to_s
97
+ if fields.is_a?(Proc)
98
+ "Proc Given"
99
+ else
100
+ fields.join(", ")
101
+ end
102
+ end
103
+
104
+ def to_s
105
+ "#{self.class.name}" +
106
+ "\nModel: #{self.klass}, Relation: #{self.relation}" + (self.metadata.polymorphic? ?
107
+ "\nPolymorphic" :
108
+ "\nInverse: #{self.inverse_klass}, Relation: #{self.inverse_relation}") +
109
+ "\nFields: #{fields_to_s}"
110
+ end
76
111
  end
77
112
  end
78
113
  end
@@ -9,10 +9,12 @@ module Mongoid
9
9
  def define_callback
10
10
  klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
11
11
  def #{callback_name}#{force_param}
12
- self.#{prefixed_name} = self.#{relation}.map do |model|
13
- #{joined_field_values("model")}
12
+ self.#{prefixed_name} = self.#{relation}.map do |relation|
13
+ #{field_values("relation", :id => true)}
14
14
  end
15
+ true
15
16
  end
17
+
16
18
  protected :#{callback_name}
17
19
  CALLBACK
18
20
  end
@@ -22,10 +24,8 @@ module Mongoid
22
24
  klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
23
25
  field :#{prefixed_name}, :type => Array, :default => []
24
26
  CALLBACK
25
- end
26
27
 
27
- def prefixed_name
28
- "#{relation}_fields"
28
+ define_fields_method
29
29
  end
30
30
  end
31
31
  end
@@ -7,19 +7,18 @@ module Mongoid
7
7
  protected
8
8
 
9
9
  def define_callback
10
- field_sets = ""
11
- fields.each do |name|
12
- field_sets << "self.send(:#{prefixed_field_name(name)}=,
13
- relation && relation.send(:#{name}))\n"
14
- end
15
-
16
10
  klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
17
11
  def #{callback_name}#{force_param}
18
12
  if #{force_check} ||
19
- #{!reflect.stores_foreign_key?} ||
20
- self.#{reflect.key}_changed?
21
- relation = self.#{relation}
22
- #{field_sets}
13
+ #{!metadata.stores_foreign_key?} ||
14
+ self.#{metadata.key}_changed?
15
+
16
+ if relation = self.#{relation}
17
+ self.#{self.prefixed_name} = #{field_values("relation")}
18
+ else
19
+ self.#{self.prefixed_name} = nil
20
+ end
21
+
23
22
  end
24
23
  true
25
24
  end
@@ -29,28 +28,12 @@ module Mongoid
29
28
  end
30
29
 
31
30
  def define_fields
32
- fields.each do |name|
33
- prefixed_name = prefixed_field_name(name)
34
- unless field_defined?(prefixed_name, klass)
35
- klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
36
- field :#{prefixed_name}, :type => #{inverse_field_type(name)}
37
- CALLBACK
38
- end
39
- end
40
- end
41
-
42
- def prefixed_field_name(name)
43
- "#{relation}_#{name}"
44
- end
45
-
46
- def inverse_field_type(name)
47
- name = name.to_s
48
-
49
- name = "_id" if name == "id"
50
- name = "_type" if name == "type"
31
+ ensure_field_not_defined!(prefixed_name, klass)
32
+ klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
33
+ field :#{prefixed_name}, :type => Hash, :default => nil
34
+ CALLBACK
51
35
 
52
- field = inverse_klass.fields[name]
53
- (field && field.options[:type]) ? field.type : String
36
+ define_fields_method
54
37
  end
55
38
  end
56
39
  end
@@ -0,0 +1,16 @@
1
+ module Mongoid
2
+ module Alize
3
+ module Errors
4
+
5
+ class InvalidConfiguration < AlizeError
6
+ def initialize(reason, klass, relation)
7
+ super(
8
+ translate("invalid_configuration.#{reason}",
9
+ { :klass => klass, :relation => relation })
10
+ )
11
+ end
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -10,8 +10,6 @@ module Mongoid
10
10
  set_callback
11
11
  end
12
12
 
13
- protected
14
-
15
13
  def set_callback
16
14
  unless callback_attached?("save", aliased_callback_name)
17
15
  klass.set_callback(:save, :before, aliased_callback_name)
@@ -28,6 +26,10 @@ module Mongoid
28
26
  !!klass.fields[prefixed_name]
29
27
  end
30
28
 
29
+ def prefixed_name
30
+ "#{relation}_fields"
31
+ end
32
+
31
33
  def direction
32
34
  "from"
33
35
  end
@@ -0,0 +1,25 @@
1
+ module Mongoid
2
+ module Alize
3
+ module InstanceHelpers
4
+
5
+ attr_accessor :force_denormalization
6
+
7
+ def denormalize_from_all
8
+ run_alize_callbacks(self.class.alize_from_callbacks)
9
+ end
10
+
11
+ def denormalize_to_all
12
+ run_alize_callbacks(self.class.alize_to_callbacks)
13
+ end
14
+
15
+ private
16
+
17
+ def run_alize_callbacks(callbacks)
18
+ callbacks.each do |callback|
19
+ self.send(callback.aliased_callback_name)
20
+ end
21
+ end
22
+
23
+ end
24
+ end
25
+ end
@@ -2,67 +2,90 @@ module Mongoid
2
2
  module Alize
3
3
  module Macros
4
4
 
5
- def alize(relation, *fields)
6
-
7
- fields = default_alize_fields(relation) if fields.empty?
8
-
9
- one = Mongoid::Relations::One
10
- many = Mongoid::Relations::Many
5
+ attr_accessor :alize_from_callbacks, :alize_to_callbacks
11
6
 
12
- def (many = many.dup).==(klass)
13
- [Mongoid::Relations::Many,
14
- Mongoid::Relations::Referenced::Many].map(&:name).include?(klass.name)
7
+ def alize(relation, *fields)
8
+ alize_from(relation, *fields)
9
+ metadata = self.relations[relation.to_s]
10
+ unless _alize_unknown_inverse?(metadata)
11
+ metadata.klass.alize_to(metadata.inverse, *fields)
15
12
  end
13
+ end
16
14
 
17
- klass = self
18
- reflect = klass.relations[relation.to_s]
15
+ def alize_from(relation, *fields)
16
+ one, many = _alize_relation_types
19
17
 
20
18
  from_one = Mongoid::Alize::Callbacks::From::One
21
19
  from_many = Mongoid::Alize::Callbacks::From::Many
22
20
 
23
- relation_superclass = reflect.relation.superclass
21
+ klass = self
22
+ metadata = klass.relations[relation.to_s]
23
+ relation_superclass = metadata.relation.superclass
24
+
24
25
  callback_klass =
25
26
  case [relation_superclass]
26
27
  when [one] then from_one
27
28
  when [many] then from_many
28
29
  end
29
30
 
30
- callback_klass.
31
- new(klass, relation, fields).
32
- attach
33
-
34
- inverse_klass = reflect.klass
35
- inverse_relation = reflect.inverse
36
-
37
- if inverse_klass &&
38
- (inverse_reflect = inverse_klass.relations[inverse_relation.to_s])
39
-
40
- to_one_from_one = Mongoid::Alize::Callbacks::To::OneFromOne
41
- to_one_from_many = Mongoid::Alize::Callbacks::To::OneFromMany
42
- to_many_from_one = Mongoid::Alize::Callbacks::To::ManyFromOne
43
- to_many_from_many = Mongoid::Alize::Callbacks::To::ManyFromMany
44
-
45
- inverse_relation_superclass = inverse_reflect.relation.superclass
46
- inverse_callback_klass =
47
- case [relation_superclass, inverse_relation_superclass]
48
- when [one, one] then to_one_from_one
49
- when [many, one] then to_many_from_one
50
- when [one, many] then to_one_from_many
51
- when [many, many] then to_many_from_many
52
- end
53
-
54
- inverse_callback_klass.
55
- new(inverse_klass, inverse_relation, fields).
56
- attach
31
+ options = fields.extract_options!
32
+ if options[:fields]
33
+ fields = options[:fields]
34
+ elsif fields.empty? && !_alize_unknown_inverse?(metadata)
35
+ fields = metadata.klass.default_alize_fields
36
+ end
37
+
38
+ (klass.alize_from_callbacks ||= []) << callback =
39
+ callback_klass.new(klass, relation, fields)
40
+ callback.attach
41
+
42
+ end
43
+
44
+ def alize_to(relation, *fields)
45
+ one, many = _alize_relation_types
46
+
47
+ klass = self
48
+ metadata = klass.relations[relation.to_s]
49
+ relation_superclass = metadata.relation.superclass
50
+
51
+ options = fields.extract_options!
52
+ if options[:fields]
53
+ fields = options[:fields]
54
+ elsif fields.empty?
55
+ fields = klass.default_alize_fields
57
56
  end
57
+
58
+ (klass.alize_to_callbacks ||= []) << callback =
59
+ Mongoid::Alize::ToCallback.new(klass, relation, fields)
60
+ callback.attach
61
+
58
62
  end
59
63
 
60
- def default_alize_fields(relation)
61
- self.relations[relation.to_s].klass.
62
- fields.reject { |name, field|
64
+ def default_alize_fields
65
+ self.fields.reject { |name, field|
63
66
  name =~ /^_/
64
67
  }.keys
65
68
  end
69
+
70
+ private
71
+
72
+ def _alize_unknown_inverse?(metadata)
73
+ (metadata.polymorphic? && metadata.stores_foreign_key?) ||
74
+ metadata.klass.nil? ||
75
+ metadata.inverse.nil?
76
+ end
77
+
78
+ def _alize_relation_types
79
+ one = Mongoid::Relations::One
80
+ many = Mongoid::Relations::Many
81
+
82
+ def (many = many.dup).==(klass)
83
+ [Mongoid::Relations::Many,
84
+ Mongoid::Relations::Referenced::Many].map(&:name).include?(klass.name)
85
+ end
86
+
87
+ [one, many]
88
+ end
66
89
  end
67
90
  end
68
91
  end
@@ -3,6 +3,8 @@ module Mongoid
3
3
  class ToCallback < Callback
4
4
 
5
5
  def attach
6
+ define_fields
7
+
6
8
  define_callback
7
9
  alias_callback
8
10
  set_callback
@@ -12,7 +14,107 @@ module Mongoid
12
14
  set_destroy_callback
13
15
  end
14
16
 
15
- protected
17
+ def define_callback
18
+ klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
19
+
20
+ def #{callback_name}#{force_param}
21
+
22
+ #{iterable_relation}.each do |relation|
23
+ next if relation.attributes.frozen?
24
+
25
+ is_one = #{is_one?}
26
+ if is_one
27
+ field_values = #{field_values("self")}
28
+ else
29
+ field_values = #{field_values("self", :id => true)}
30
+ end
31
+
32
+ prefixed_name = #{prefixed_name}
33
+ if is_one
34
+ relation.set(prefixed_name, field_values)
35
+ else
36
+ #{pull_from_inverse}
37
+ relation.push(prefixed_name, field_values)
38
+ end
39
+
40
+ end
41
+
42
+ #{debug ? "puts \"#{callback_name}\"": ""}
43
+ true
44
+ end
45
+ protected :#{callback_name}
46
+ CALLBACK
47
+ end
48
+
49
+ def define_destroy_callback
50
+ klass.class_eval <<-CALLBACK, __FILE__, __LINE__ + 1
51
+
52
+ def #{destroy_callback_name}
53
+ #{iterable_relation}.each do |relation|
54
+ next if relation.attributes.frozen?
55
+
56
+ is_one = #{is_one?}
57
+ prefixed_name = #{prefixed_name}
58
+ if is_one
59
+ relation.set(prefixed_name, nil)
60
+ else
61
+ #{pull_from_inverse}
62
+ end
63
+ end
64
+
65
+ #{debug ? "puts \"#{destroy_callback_name}\"": ""}
66
+ true
67
+ end
68
+ protected :#{destroy_callback_name}
69
+ CALLBACK
70
+ end
71
+
72
+ def pull_from_inverse
73
+ <<-RUBIES
74
+ relation.pull(prefixed_name, { "_id" => self.id })
75
+ if _f = relation.send(prefixed_name)
76
+ _f.reject! do |hash|
77
+ hash["_id"] == self.id
78
+ end
79
+ end
80
+ RUBIES
81
+ end
82
+
83
+ def prefixed_name
84
+ if inverse_relation
85
+ ":#{inverse_relation}_fields"
86
+ else
87
+ <<-RUBIES
88
+ (#{find_relation}[0].to_s + '_fields')
89
+ RUBIES
90
+ end
91
+ end
92
+
93
+ def is_one?
94
+ if inverse_relation
95
+ if self.inverse_metadata.relation.superclass == Mongoid::Relations::One
96
+ "true"
97
+ else
98
+ "false"
99
+ end
100
+ else
101
+ <<-RUBIES
102
+ (#{find_relation}[1].relation.superclass == Mongoid::Relations::One)
103
+ RUBIES
104
+ end
105
+ end
106
+
107
+ def find_relation
108
+ "relation.class.relations.find { |name, metadata| metadata.inverse == :#{relation} && metadata.class_name == self.class.name }"
109
+ end
110
+
111
+ def iterable_relation
112
+ "[self.#{relation}].flatten.compact"
113
+ end
114
+
115
+ def define_fields
116
+ define_fields_method
117
+ end
16
118
 
17
119
  def set_callback
18
120
  unless callback_attached?("save", aliased_callback_name)
@@ -29,6 +131,7 @@ module Mongoid
29
131
  def alias_destroy_callback
30
132
  unless callback_defined?(aliased_destroy_callback_name)
31
133
  klass.send(:alias_method, aliased_destroy_callback_name, destroy_callback_name)
134
+ klass.send(:public, aliased_destroy_callback_name)
32
135
  end
33
136
  end
34
137
 
@@ -40,14 +143,6 @@ module Mongoid
40
143
  "_#{aliased_destroy_callback_name}"
41
144
  end
42
145
 
43
- def plain_relation
44
- "self.#{relation}"
45
- end
46
-
47
- def surrounded_relation
48
- "self.#{relation} ? [self.#{relation}] : []"
49
- end
50
-
51
146
  def direction
52
147
  "to"
53
148
  end