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

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.
@@ -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"