custom_fields 1.0.0.beta.21 → 1.0.0.beta.22

Sign up to get free protection for your applications and to get access to all the features.
@@ -83,6 +83,14 @@ module CustomFields
83
83
  self.custom_fields.detect { |f| f._name == name }
84
84
  end
85
85
 
86
+ def self.custom_field_alias_to_name(value)
87
+ self.custom_fields.detect { |f| f._alias == value }._name
88
+ end
89
+
90
+ def self.custom_field_name_to_alias(value)
91
+ self.custom_fields.detect { |f| f._name == value }._alias
92
+ end
93
+
86
94
  def self.hereditary?
87
95
  false
88
96
  end
@@ -0,0 +1,92 @@
1
+ module CustomFields
2
+ module Types
3
+ module HasMany
4
+
5
+ class ProxyCollection
6
+
7
+ attr_accessor :parent, :target_klass, :field_name, :ids, :values
8
+
9
+ def initialize(parent, target_klass, field_name, options = {})
10
+ self.parent = parent
11
+
12
+ self.target_klass = target_klass
13
+
14
+ self.field_name = field_name
15
+
16
+ self.ids, self.values = [], []
17
+ end
18
+
19
+ def find(id)
20
+ id = BSON::ObjectId(id) unless id.is_a?(BSON::ObjectId)
21
+ self.values.detect { |obj_id| obj_id == id }
22
+ end
23
+
24
+ def update(values)
25
+ values = [] if values.blank? || self.target_klass.nil?
26
+
27
+ self.ids = values.collect { |obj| self.id_for_sure(obj) }.compact
28
+ self.values = values.collect { |obj| self.object_for_sure(obj) }.compact
29
+ end
30
+
31
+ # just before the parent gets saved, reflect the changes to the parent object
32
+ def store_values
33
+ self.parent.write_attribute(self.field_name, self.ids)
34
+ end
35
+
36
+ # once the parent object gets saved, call this method, kind of hook or callback
37
+ def persist
38
+ true
39
+ end
40
+
41
+ def <<(*args)
42
+ args.flatten.compact.each do |obj|
43
+ self.ids << self.id_for_sure(obj)
44
+ self.values << self.object_for_sure(obj)
45
+ end
46
+ end
47
+
48
+ alias :push :<<
49
+
50
+ def size
51
+ self.values.size
52
+ end
53
+
54
+ alias :length :size
55
+
56
+ def method_missing(name, *args, &block)
57
+ self.values.send(name, *args, &block)
58
+ end
59
+
60
+ protected
61
+
62
+ def id_for_sure(id_or_object)
63
+ id_or_object.respond_to?(:_id) ? id_or_object._id : id_or_object
64
+ end
65
+
66
+ def object_for_sure(id_or_object)
67
+ if id_or_object.respond_to?(:_id)
68
+ id_or_object
69
+ else
70
+ self.collection.find(id_or_object)
71
+ end
72
+ rescue # target_klass does not exist anymore or the target element has been removed since
73
+ nil
74
+ end
75
+
76
+ def collection(reload_embedded = false)
77
+ if self.target_klass.embedded?
78
+ parent_target_klass = self.target_klass._parent
79
+
80
+ parent_target_klass = parent_target_klass.reload if reload_embedded
81
+
82
+ parent_target_klass.send(self.target_klass.association_name)
83
+ else
84
+ self.target_klass
85
+ end
86
+ end
87
+
88
+ end
89
+
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,93 @@
1
+ module CustomFields
2
+ module Types
3
+ module HasMany
4
+
5
+ class ReverseLookupProxyCollection < ProxyCollection
6
+
7
+ attr_accessor :reverse_lookup_field, :previous_state
8
+
9
+ def initialize(parent, target_klass, field_name, options = {})
10
+ super
11
+
12
+ self.reverse_lookup_field = options[:reverse_lookup_field].to_sym
13
+
14
+ if self.parent.new_record?
15
+ self.previous_state = { :ids => [], :values => [] }
16
+ else
17
+ self.reload
18
+ end
19
+ end
20
+
21
+ def store_values
22
+ true # do nothing
23
+ end
24
+
25
+ def persist
26
+ (self.previous_state[:ids] - self.ids).each do |id|
27
+ self.set_foreign_key_and_position(id, nil)
28
+ end
29
+
30
+ self.ids.each_with_index do |id, position|
31
+ self.set_foreign_key_and_position(id, self.parent._id, position)
32
+ end
33
+
34
+ if self.target_klass.embedded?
35
+ self.target_klass._parent.save!(:validate => false)
36
+ end
37
+
38
+ self.reset_previous_state
39
+ end
40
+
41
+ def <<(id_or_object)
42
+ object = self.object_for_sure(id_or_object)
43
+
44
+ foreign_key = object.send(self.reverse_lookup_field)
45
+
46
+ if foreign_key && foreign_key != self.parent._id
47
+ raise ArgumentError, "Object #{object} cannot be added: already has a different foreign key"
48
+ end
49
+
50
+ self.ids << self.id_for_sure(object._id)
51
+ self.values << self.object_for_sure(object)
52
+ end
53
+
54
+ def reload
55
+ self.update(self.reverse_collection)
56
+
57
+ self.reset_previous_state
58
+ end
59
+
60
+ protected
61
+
62
+ def reverse_collection
63
+ self.collection.where(self.reverse_lookup_field => self.parent._id).order_by([[:"#{self.reverse_lookup_field}_position", :asc]])
64
+ end
65
+
66
+ def set_foreign_key_and_position(object_id, value, position = nil)
67
+ if self.target_klass.embedded?
68
+ object = self.collection.find(object_id)
69
+ else
70
+ object = self.previous_state[:values].detect { |o| o._id == object_id }
71
+ end
72
+
73
+ object.send("#{self.reverse_lookup_field}=".to_sym, value)
74
+
75
+ object.send("#{self.reverse_lookup_field}_position=".to_sym, position)
76
+
77
+ if self.target_klass.embedded?
78
+ object.save(:validate => false)
79
+ end
80
+ end
81
+
82
+ def reset_previous_state
83
+ self.previous_state = {
84
+ :ids => self.ids.clone,
85
+ :values => self.values.clone
86
+ }
87
+ end
88
+
89
+ end
90
+
91
+ end
92
+ end
93
+ end
@@ -6,6 +6,7 @@ module CustomFields
6
6
 
7
7
  included do
8
8
  field :target
9
+ field :reverse_lookup
9
10
 
10
11
  validates_presence_of :target, :if => :has_many?
11
12
 
@@ -14,21 +15,43 @@ module CustomFields
14
15
 
15
16
  module InstanceMethods
16
17
 
18
+ def target_klass
19
+ self.target.constantize rescue nil
20
+ end
21
+
22
+ def reverse_has_many?
23
+ self.reverse_lookup && !self.reverse_lookup.strip.blank?
24
+ end
25
+
26
+ def safe_reverse_lookup
27
+ if self.reverse_lookup =~ /^custom_field_[0-9]+$/
28
+ self.reverse_lookup
29
+ else
30
+ self.target_klass.custom_field_alias_to_name(self.reverse_lookup)
31
+ end
32
+ end
33
+
34
+ def reverse_lookup_alias
35
+ self.target_klass.custom_field_name_to_alias(self.reverse_lookup)
36
+ end
37
+
17
38
  def apply_has_many_type(klass)
18
39
  klass.class_eval <<-EOF
19
40
 
20
41
  before_validation :store_#{self.safe_alias.singularize}_ids
21
42
 
43
+ after_save :persist_#{self.safe_alias}
44
+
22
45
  def #{self.safe_alias}=(ids_or_objects)
23
- if @_#{self._name}.nil?
24
- @_#{self._name} = ProxyCollection.new('#{self.target.to_s}', ids_or_objects)
25
- else
26
- @_#{self._name}.update(ids_or_objects)
27
- end
46
+ self.#{self.safe_alias}.update(ids_or_objects)
28
47
  end
29
48
 
30
49
  def #{self.safe_alias}
31
- @_#{self._name} ||= ProxyCollection.new('#{self.target.to_s}', read_attribute(:#{self._name}))
50
+ @_#{self._name} ||= build_#{self.safe_alias.singularize}_proxy_collection
51
+ end
52
+
53
+ def #{self.safe_alias}_klass
54
+ '#{self.target.to_s}'.constantize rescue nil
32
55
  end
33
56
 
34
57
  def #{self.safe_alias.singularize}_ids
@@ -36,84 +59,42 @@ module CustomFields
36
59
  end
37
60
 
38
61
  def store_#{self.safe_alias.singularize}_ids
39
- write_attribute(:#{self._name}, #{self.safe_alias.singularize}_ids)
62
+ self.#{self.safe_alias}.store_values
40
63
  end
41
- EOF
42
- end
43
-
44
- def add_has_many_validation(klass)
45
- if self.required?
46
- klass.validates_length_of self.safe_alias.to_sym, :minimum => 1, :too_short => :blank
47
- end
48
- end
49
-
50
- end
51
-
52
- class ProxyCollection
53
-
54
- attr_accessor :target_klass, :ids, :values
55
64
 
56
- def initialize(target_klass_name, array = [])
57
- self.target_klass = target_klass_name.constantize rescue nil
58
-
59
- array = [] if self.target_klass.nil?
60
-
61
- self.update(array || [])
62
- end
63
-
64
- def find(id)
65
- id = BSON::ObjectId(id) unless id.is_a?(BSON::ObjectId)
66
- self.values.detect { |obj_id| obj_id == id }
67
- end
68
-
69
- def update(values)
70
- values = [] if values.blank?
65
+ def persist_#{self.safe_alias}
66
+ self.#{self.safe_alias}.persist
67
+ end
71
68
 
72
- self.ids = values.collect { |obj| self.id_for_sure(obj) }.compact
73
- self.values = values.collect { |obj| self.object_for_sure(obj) }.compact
74
- end
69
+ EOF
75
70
 
76
- def <<(*args)
77
- args.flatten.compact.each do |obj|
78
- self.ids << self.id_for_sure(obj)
79
- self.values << self.object_for_sure(obj)
71
+ if reverse_has_many?
72
+ klass.class_eval <<-EOF
73
+ def build_#{self.safe_alias.singularize}_proxy_collection
74
+ ::CustomFields::Types::HasMany::ReverseLookupProxyCollection.new(self, self.#{self.safe_alias}_klass, '#{self._name}', {
75
+ :reverse_lookup_field => '#{self.safe_reverse_lookup}'
76
+ })
77
+ end
78
+ EOF
79
+ else
80
+ klass.class_eval <<-EOF
81
+ def build_#{self.safe_alias.singularize}_proxy_collection
82
+ ::CustomFields::Types::HasMany::ProxyCollection.new(self, self.#{self.safe_alias}_klass, '#{self._name}').tap do |collection|
83
+ collection.update(self.#{self._name})
84
+ end
85
+ end
86
+ EOF
80
87
  end
81
88
  end
82
89
 
83
- alias :push :<<
84
-
85
- def size
86
- self.values.size
87
- end
88
-
89
- alias :length :size
90
-
91
- def method_missing(name, *args, &block)
92
- self.values.send(name, *args, &block)
93
- end
94
-
95
- protected
96
-
97
- def id_for_sure(id_or_object)
98
- id_or_object.respond_to?(:_id) ? id_or_object._id : id_or_object
99
- end
100
-
101
- def object_for_sure(id_or_object)
102
- if id_or_object.respond_to?(:_id)
103
- id_or_object
104
- else
105
- if self.target_klass.embedded?
106
- self.target_klass._parent.reload.send(self.target_klass.association_name).find(id_or_object)
107
- else
108
- self.target_klass.find(id_or_object)
109
- end
90
+ def add_has_many_validation(klass)
91
+ if self.required?
92
+ klass.validates_length_of self.safe_alias.to_sym, :minimum => 1, :too_short => :blank
110
93
  end
111
- rescue # target_klass does not exist anymore or the target element has been removed since
112
- nil
113
94
  end
114
95
 
115
96
  end
116
97
 
117
98
  end
118
99
  end
119
- end
100
+ end
@@ -15,6 +15,9 @@ module CustomFields
15
15
  module InstanceMethods
16
16
 
17
17
  def apply_has_one_type(klass)
18
+
19
+ klass.field :"#{self._name}_position", :type => Integer, :default => 0 # needed by the has_many reverse
20
+
18
21
  klass.class_eval <<-EOF
19
22
 
20
23
  def #{self.safe_alias}=(id_or_object)
@@ -1,5 +1,5 @@
1
1
  module Mongoid
2
2
  module CustomFields
3
- VERSION = "1.0.0.beta.21"
3
+ VERSION = "1.0.0.beta.22"
4
4
  end
5
5
  end
data/lib/custom_fields.rb CHANGED
@@ -31,6 +31,8 @@ require 'custom_fields/types/boolean'
31
31
  require 'custom_fields/types/date'
32
32
  require 'custom_fields/types/file'
33
33
  require 'custom_fields/types/has_one'
34
+ require 'custom_fields/types/has_many/proxy_collection'
35
+ require 'custom_fields/types/has_many/reverse_lookup_proxy_collection'
34
36
  require 'custom_fields/types/has_many'
35
37
  require 'custom_fields/proxy_class_enabler'
36
38
  require 'custom_fields/field'
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: custom_fields
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease: 6
5
- version: 1.0.0.beta.21
5
+ version: 1.0.0.beta.22
6
6
  platform: ruby
7
7
  authors:
8
8
  - Didier Lafforgue
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-07-03 00:00:00 +02:00
13
+ date: 2011-08-11 00:00:00 -07:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -61,6 +61,8 @@ files:
61
61
  - lib/custom_fields/types/date.rb
62
62
  - lib/custom_fields/types/default.rb
63
63
  - lib/custom_fields/types/file.rb
64
+ - lib/custom_fields/types/has_many/proxy_collection.rb
65
+ - lib/custom_fields/types/has_many/reverse_lookup_proxy_collection.rb
64
66
  - lib/custom_fields/types/has_many.rb
65
67
  - lib/custom_fields/types/has_one.rb
66
68
  - lib/custom_fields/types/string.rb
@@ -85,7 +87,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
85
87
  requirements:
86
88
  - - ">="
87
89
  - !ruby/object:Gem::Version
88
- hash: -3396321950106991976
90
+ hash: -2478047520408348975
89
91
  segments:
90
92
  - 0
91
93
  version: "0"