mongoid_alize 0.2.0 → 0.3.1

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