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.
- data/lib/custom_fields/proxy_class_enabler.rb +8 -0
- data/lib/custom_fields/types/has_many/proxy_collection.rb +92 -0
- data/lib/custom_fields/types/has_many/reverse_lookup_proxy_collection.rb +93 -0
- data/lib/custom_fields/types/has_many.rb +54 -73
- data/lib/custom_fields/types/has_one.rb +3 -0
- data/lib/custom_fields/version.rb +1 -1
- data/lib/custom_fields.rb +2 -0
- metadata +5 -3
@@ -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
|
-
|
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} ||=
|
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
|
-
|
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
|
-
|
57
|
-
|
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
|
-
|
73
|
-
self.values = values.collect { |obj| self.object_for_sure(obj) }.compact
|
74
|
-
end
|
69
|
+
EOF
|
75
70
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
84
|
-
|
85
|
-
|
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)
|
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.
|
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-
|
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: -
|
90
|
+
hash: -2478047520408348975
|
89
91
|
segments:
|
90
92
|
- 0
|
91
93
|
version: "0"
|