custom_fields 1.0.0.beta2 → 1.0.0.beta.3

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.
data/README CHANGED
@@ -6,7 +6,7 @@ Manage custom fields to a mongoid document or a collection. This module is one o
6
6
  Requirements
7
7
  =======
8
8
 
9
- ActiveSupport 3.0, MongoDB 1.6 and mongoid 2.0.0.beta17
9
+ ActiveSupport 3.0, MongoDB 1.6 and mongoid 2.0.0.rc.6
10
10
 
11
11
 
12
12
  Example
@@ -15,4 +15,4 @@ Example
15
15
  Coming soon but take a look of the tests to see what CustomFields is capable of.
16
16
 
17
17
 
18
- Copyright (c) 2010 [Didier Lafforgue], released under the MIT license
18
+ Copyright (c) 2011 [Didier Lafforgue], released under the MIT license
@@ -6,9 +6,12 @@ module CustomFields
6
6
  base.extend(ClassMethods)
7
7
  end
8
8
 
9
- # Enhance an embedded collection by providing methods to manage custom fields
9
+ # Enhance an embedded collection OR the instance itself (by passing self) by providing methods to manage custom fields.
10
10
  #
11
11
  # class Company
12
+ #
13
+ # custom_fields_for :self
14
+ #
12
15
  # embeds_many :employees
13
16
  # custom_fields_for :employees
14
17
  # end
@@ -22,15 +25,35 @@ module CustomFields
22
25
  #
23
26
  # company.employees.build :name => 'Michael Scott', :position => 'Regional manager'
24
27
  #
28
+ #
29
+ # company.self_custom_fields.build :label => 'Shipping Address', :_alias => 'address', :kind => 'text'
30
+ #
31
+ # company.metadata.address = '700 S Laflin, 60607 Chicago'
32
+ #
33
+ # other_company.metadata.address # returns a "not defined method" error
34
+ #
25
35
  module ClassMethods
26
36
 
27
37
  def custom_fields_for(collection_name)
28
38
  singular_name = collection_name.to_s.singularize
29
39
 
40
+ if (itself = %w(itself self).include?(collection_name.to_s))
41
+ singular_name = '_metadata'
42
+
43
+ class_eval <<-EOV
44
+ embeds_one :#{singular_name}, :class_name => '::CustomFields::Metadata'
45
+
46
+ def metadata
47
+ self.#{singular_name} || self.build_#{singular_name}
48
+ end
49
+
50
+ EOV
51
+ end
52
+
30
53
  class_eval <<-EOV
31
54
  field :#{singular_name}_custom_fields_counter, :type => Integer, :default => 0
32
55
 
33
- embeds_many :#{singular_name}_custom_fields, :class_name => "::CustomFields::Field"
56
+ embeds_many :#{singular_name}_custom_fields, :class_name => '::CustomFields::Field'
34
57
 
35
58
  validates_associated :#{singular_name}_custom_fields
36
59
 
@@ -39,8 +62,14 @@ module CustomFields
39
62
  def ordered_#{singular_name}_custom_fields
40
63
  self.#{singular_name}_custom_fields.sort { |a, b| (a.position || 0) <=> (b.position || 0) }
41
64
  end
42
-
43
65
  EOV
66
+
67
+ if itself
68
+ class_eval <<-EOV
69
+ alias :self_custom_fields :#{singular_name}_custom_fields
70
+ EOV
71
+ end
72
+
44
73
  end
45
74
 
46
75
  end
@@ -0,0 +1,31 @@
1
+ # encoding: utf-8
2
+ module Mongoid #:nodoc:
3
+
4
+ # This is the base module for all domain objects that need to be persisted to
5
+ # the database as documents.
6
+ module Document
7
+
8
+ # Reloads the +Document+ attributes from the database. If the document has
9
+ # not been saved then an error will get raised if the configuration option
10
+ # was set.
11
+ #
12
+ # @example Reload the document.
13
+ # person.reload
14
+ #
15
+ # @raise [ Errors::DocumentNotFound ] If the document was deleted.
16
+ #
17
+ # @return [ Document ] The document, reloaded.
18
+ def reload_with_custom_fields
19
+ reload_without_custom_fields.tap do
20
+ instance_variable_names.each do |name|
21
+ if name =~ /_proxy_class$/
22
+ remove_instance_variable("#{name}")
23
+ end
24
+ end
25
+ end
26
+ end
27
+
28
+ alias_method_chain :reload, :custom_fields
29
+
30
+ end
31
+ end
@@ -0,0 +1,45 @@
1
+ # encoding: utf-8
2
+ module Mongoid # :nodoc:
3
+ module Relations #:nodoc:
4
+
5
+ module Accessors
6
+
7
+ # Create a relation from an object and metadata.
8
+ #
9
+ # @example Create the relation.
10
+ # person.create_relation(document, metadata)
11
+ #
12
+ # @param [ Document, Array<Document ] object The relation target.
13
+ # @param [ Metadata ] metadata The relation metadata.
14
+ #
15
+ # @return [ Proxy ] The relation.
16
+ #
17
+ # @since 2.0.0.rc.1
18
+ def create_relation_with_custom_fields(object, metadata)
19
+ if custom_fields?(self, metadata.name)
20
+ metadata = metadata.clone # 2 parent instances should not share the exact same option instance
21
+
22
+ custom_fields = self.send(:"ordered_#{custom_fields_association_name(metadata.name)}")
23
+
24
+ klass = metadata.klass.to_klass_with_custom_fields(custom_fields, self, metadata.name)
25
+
26
+ metadata.instance_variable_set(:@klass, klass)
27
+ end
28
+
29
+ create_relation_without_custom_fields(object, metadata)
30
+ end
31
+
32
+ alias_method_chain :create_relation, :custom_fields
33
+
34
+ def custom_fields_association_name(association_name)
35
+ "#{association_name.to_s.singularize}_custom_fields".to_sym
36
+ end
37
+
38
+ def custom_fields?(object, association_name)
39
+ object.respond_to?(custom_fields_association_name(association_name))
40
+ end
41
+
42
+ end
43
+
44
+ end
45
+ end
@@ -3,7 +3,7 @@ module CustomFields
3
3
  class Field
4
4
  include ::Mongoid::Document
5
5
  include ::Mongoid::Timestamps
6
-
6
+
7
7
  # types ##
8
8
  include Types::Default
9
9
  include Types::String
@@ -12,7 +12,7 @@ module CustomFields
12
12
  include Types::Boolean
13
13
  include Types::Date
14
14
  include Types::File
15
-
15
+
16
16
  ## fields ##
17
17
  field :label
18
18
  field :_alias
@@ -20,63 +20,106 @@ module CustomFields
20
20
  field :kind
21
21
  field :hint
22
22
  field :position, :type => Integer, :default => 0
23
-
23
+
24
24
  ## validations ##
25
25
  validates_presence_of :label, :kind
26
- validate :uniqueness_of_label
27
-
26
+ validates_exclusion_of :_alias, :in => Mongoid.destructive_fields
27
+ validate :uniqueness_of_label_and_alias
28
+
29
+ ## other accessors ##
30
+ attr_accessor :association_name # missing in 2.0.0 rc
31
+
28
32
  ## methods ##
29
-
33
+
30
34
  def field_type
31
35
  self.class.field_types[self.kind.downcase.to_sym]
32
36
  end
33
-
37
+
34
38
  def apply(klass)
35
- return unless self.valid?
36
-
39
+ return false unless self.valid?
40
+
37
41
  klass.field self._name, :type => self.field_type if self.field_type
38
-
42
+
39
43
  apply_method_name = :"apply_#{self.kind.downcase}_type"
40
-
44
+
41
45
  if self.respond_to?(apply_method_name)
42
46
  self.send(apply_method_name, klass)
43
47
  else
44
48
  apply_default_type(klass)
45
49
  end
50
+
51
+ true
46
52
  end
47
-
53
+
48
54
  def safe_alias
49
55
  self.set_alias
50
- self._alias
56
+ self._alias
57
+ end
58
+
59
+ def write_attributes_with_invalidation(attrs = nil)
60
+ if self.association_name.to_s == '_metadata_custom_fields'
61
+ target_name = 'metadata'
62
+ else
63
+ target_name = self.association_name.to_s.gsub('_custom_fields', '').pluralize
64
+ end
65
+
66
+ klass = self._parent.send(target_name).metadata.klass
67
+
68
+ write_attributes_without_invalidation(attrs)
69
+
70
+ klass.apply_custom_field(self)
51
71
  end
52
-
72
+
73
+ alias_method_chain :write_attributes, :invalidation
74
+
53
75
  protected
54
-
55
- def uniqueness_of_label
56
- duplicate = self.siblings.detect { |f| f.label == self.label && f._id != self._id }
57
- if not duplicate.nil?
76
+
77
+ def uniqueness_of_label_and_alias
78
+ if self.siblings.any? { |f| f.label == self.label && f._id != self._id }
58
79
  self.errors.add(:label, :taken)
59
80
  end
81
+
82
+ if self.siblings.any? { |f| f._alias == self._alias && f._id != self._id }
83
+ self.errors.add(:_alias, :taken)
84
+ end
60
85
  end
61
-
86
+
62
87
  def set_unique_name!
63
88
  self._name ||= "custom_field_#{self.increment_counter!}"
64
89
  end
65
-
90
+
66
91
  def set_alias
67
92
  return if self.label.blank? && self._alias.blank?
68
93
  self._alias = (self._alias.blank? ? self.label : self._alias).parameterize('_').downcase
69
94
  end
70
-
95
+
71
96
  def increment_counter!
72
97
  next_value = (self._parent.send(:"#{self.association_name}_counter") || 0) + 1
73
98
  self._parent.send(:"#{self.association_name}_counter=", next_value)
74
99
  next_value
75
100
  end
76
-
101
+
77
102
  def siblings
78
103
  self._parent.send(self.association_name)
79
104
  end
105
+
106
+ def parentize_with_custom_fields(object)
107
+ object_name = object.class.to_s.underscore
108
+
109
+ self.association_name = self.metadata ? self.metadata.name : self.relations[object_name].inverse_of
110
+
111
+ if !self.relations.key?(object_name)
112
+ self.singleton_class.embedded_in object_name.to_sym, :inverse_of => self.association_name
113
+ end
114
+
115
+ parentize_without_custom_fields(object)
116
+
117
+ self.send(:set_unique_name!)
118
+ self.send(:set_alias)
119
+ end
120
+
121
+ alias_method_chain :parentize, :custom_fields
122
+
80
123
  end
81
-
124
+
82
125
  end
@@ -0,0 +1,31 @@
1
+ module CustomFields
2
+
3
+ class Metadata
4
+
5
+ include ::Mongoid::Document
6
+ include CustomFields::ProxyClassEnabler
7
+
8
+ ## other accessors ##
9
+ attr_accessor :association_name # missing in 2.0.0 rc
10
+
11
+ protected
12
+
13
+ def parentize_with_custom_fields(object)
14
+ object_name = object.class.to_s.underscore
15
+
16
+ self.association_name = self.metadata ? self.metadata.name : self.relations[object_name].inverse_of
17
+
18
+ if !self.relations.key?(object_name)
19
+ self.singleton_class.embedded_in object_name.to_sym, :inverse_of => self.association_name
20
+ end
21
+
22
+ parentize_without_custom_fields(object)
23
+ end
24
+
25
+ alias_method_chain :parentize, :custom_fields
26
+
27
+
28
+
29
+ end
30
+
31
+ end
@@ -5,33 +5,73 @@ module CustomFields
5
5
 
6
6
  included do
7
7
 
8
- cattr_accessor :klass_with_custom_fields
8
+ def self.to_klass_with_custom_fields(fields, parent, association_name)
9
+ target_name = "#{association_name}_proxy_class"
9
10
 
10
- def self.to_klass_with_custom_fields(fields)
11
- return klass_with_custom_fields unless klass_with_custom_fields.nil?
11
+ klass = parent.instance_variable_get(:"@#{target_name}")
12
+
13
+ if klass.nil?
14
+ klass = self.build_proxy_class_with_custom_fields(fields, parent, association_name)
15
+
16
+ parent.instance_variable_set(:"@#{target_name}", klass)
17
+ end
18
+
19
+ klass
20
+ end
21
+
22
+ def self.build_proxy_class_with_custom_fields(fields, parent, association_name)
23
+ (klass = Class.new(self)).class_eval <<-EOF
12
24
 
13
- klass = Class.new(self)
14
- klass.class_eval <<-EOF
15
25
  cattr_accessor :custom_fields, :_parent, :association_name
16
26
 
17
27
  def self.model_name
18
28
  @_model_name ||= ActiveModel::Name.new(self.superclass)
19
29
  end
20
30
 
21
- def custom_fields
22
- self.class.custom_fields
31
+ def self.apply_custom_field(field)
32
+ return unless field.valid?
33
+
34
+ (self.custom_fields ||= []) << field
35
+
36
+ field.apply(self)
37
+ end
38
+
39
+ def self.lookup_custom_field(name)
40
+ self.custom_fields.detect { |f| f._name == name }
23
41
  end
24
42
 
25
43
  def self.hereditary?
26
44
  false
27
45
  end
46
+
47
+ def custom_fields
48
+ self.class.custom_fields
49
+ end
50
+
51
+ def aliased_attributes
52
+ hash = { :created_at => self.created_at, :updated_at => self.updated_at } rescue {}
53
+
54
+ self.custom_fields.each do |field|
55
+ case field.kind
56
+ when 'file' then hash[field._alias] = self.send(field._name.to_sym).url
57
+ else
58
+ hash[field._alias] = self.send(field._name.to_sym)
59
+ end
60
+ end
61
+
62
+ hash
63
+ end
28
64
  EOF
29
65
 
30
- klass.custom_fields = fields
66
+ # copy scopes from the parent class
67
+ klass.write_inheritable_attribute(:scopes, self.scopes)
68
+
69
+ klass.association_name = association_name
70
+ klass._parent = parent
31
71
 
32
- [*fields].each { |field| field.apply(klass) }
72
+ [*fields].each { |field| klass.apply_custom_field(field) }
33
73
 
34
- klass_with_custom_fields = klass
74
+ klass
35
75
  end
36
76
 
37
77
  end
@@ -29,11 +29,12 @@ module CustomFields
29
29
  end
30
30
 
31
31
  def apply_category_type(klass)
32
- klass.cattr_accessor :"#{self.safe_alias}_items"
32
+ klass.class_eval <<-EOF
33
33
 
34
- klass.send("#{self.safe_alias}_items=", self.ordered_category_items)
34
+ def self.#{self.safe_alias}_items
35
+ self.lookup_custom_field('#{self._name}').ordered_category_items
36
+ end
35
37
 
36
- klass.class_eval <<-EOF
37
38
  def self.#{self.safe_alias}_names
38
39
  self.#{self.safe_alias}_items.collect(&:name)
39
40
  end
@@ -58,7 +59,6 @@ module CustomFields
58
59
  def #{self.safe_alias}=(id)
59
60
  category = self.class.#{self.safe_alias}_items.find { |item| item.name == id || item._id.to_s == id.to_s }
60
61
  category_id = category ? category._id : nil
61
-
62
62
  write_attribute(:#{self._name}, category_id)
63
63
  end
64
64
 
@@ -1,5 +1,5 @@
1
1
  module Mongoid
2
2
  module CustomFields
3
- VERSION = "1.0.0.beta2"
3
+ VERSION = "1.0.0.beta.3"
4
4
  end
5
5
  end
data/lib/custom_fields.rb CHANGED
@@ -4,10 +4,8 @@ require 'active_support'
4
4
  require 'carrierwave/orm/mongoid'
5
5
 
6
6
  require 'custom_fields/version'
7
- require 'custom_fields/extensions/mongoid/hierarchy'
8
- require 'custom_fields/extensions/mongoid/associations/proxy'
9
- require 'custom_fields/extensions/mongoid/associations/references_many'
10
- require 'custom_fields/extensions/mongoid/associations/embeds_many'
7
+ require 'custom_fields/extensions/mongoid/document'
8
+ require 'custom_fields/extensions/mongoid/relations/accessors'
11
9
  require 'custom_fields/types/default'
12
10
  require 'custom_fields/types/string'
13
11
  require 'custom_fields/types/text'
@@ -17,6 +15,7 @@ require 'custom_fields/types/date'
17
15
  require 'custom_fields/types/file'
18
16
  require 'custom_fields/proxy_class_enabler'
19
17
  require 'custom_fields/field'
18
+ require 'custom_fields/metadata'
20
19
  require 'custom_fields/custom_fields_for'
21
20
 
22
21
  module Mongoid
@@ -27,3 +26,7 @@ module Mongoid
27
26
  end
28
27
  end
29
28
  end
29
+
30
+ ActiveSupport::Inflector.inflections do |inflect|
31
+ inflect.uncountable 'metadata'
32
+ end
metadata CHANGED
@@ -1,14 +1,15 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: custom_fields
3
3
  version: !ruby/object:Gem::Version
4
- hash: -1848230054
5
- prerelease: true
4
+ hash: 62196357
5
+ prerelease: 6
6
6
  segments:
7
7
  - 1
8
8
  - 0
9
9
  - 0
10
- - beta2
11
- version: 1.0.0.beta2
10
+ - beta
11
+ - 3
12
+ version: 1.0.0.beta.3
12
13
  platform: ruby
13
14
  authors:
14
15
  - Didier Lafforgue
@@ -16,7 +17,7 @@ autorequire:
16
17
  bindir: bin
17
18
  cert_chain: []
18
19
 
19
- date: 2010-10-19 00:00:00 +02:00
20
+ date: 2011-01-27 00:00:00 +01:00
20
21
  default_executable:
21
22
  dependencies:
22
23
  - !ruby/object:Gem::Dependency
@@ -82,11 +83,10 @@ files:
82
83
  - MIT-LICENSE
83
84
  - README
84
85
  - lib/custom_fields/custom_fields_for.rb
85
- - lib/custom_fields/extensions/mongoid/associations/embeds_many.rb
86
- - lib/custom_fields/extensions/mongoid/associations/proxy.rb
87
- - lib/custom_fields/extensions/mongoid/associations/references_many.rb
88
- - lib/custom_fields/extensions/mongoid/hierarchy.rb
86
+ - lib/custom_fields/extensions/mongoid/document.rb
87
+ - lib/custom_fields/extensions/mongoid/relations/accessors.rb
89
88
  - lib/custom_fields/field.rb
89
+ - lib/custom_fields/metadata.rb
90
90
  - lib/custom_fields/proxy_class_enabler.rb
91
91
  - lib/custom_fields/types/boolean.rb
92
92
  - lib/custom_fields/types/category.rb
@@ -129,7 +129,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
129
129
  requirements: []
130
130
 
131
131
  rubyforge_project: nowarning
132
- rubygems_version: 1.3.7
132
+ rubygems_version: 1.4.2
133
133
  signing_key:
134
134
  specification_version: 3
135
135
  summary: Custom fields extension for Mongoid
@@ -1,31 +0,0 @@
1
- # encoding: utf-8
2
- module Mongoid #:nodoc:
3
- module Associations #:nodoc:
4
- class EmbedsMany < Proxy
5
-
6
- def initialize_with_custom_fields(parent, options, target_array = nil)
7
- if custom_fields?(parent, options.name)
8
- options = options.clone # 2 parent instances should not share the exact same option instance
9
-
10
- custom_fields = parent.send(:"ordered_#{custom_fields_association_name(options.name)}")
11
-
12
- klass = options.klass.to_klass_with_custom_fields(custom_fields)
13
- klass._parent = parent
14
- klass.association_name = options.name
15
-
16
- options.instance_eval <<-EOF
17
- def klass=(klass); @klass = klass; end
18
- def klass; @klass || class_name.constantize; end
19
- EOF
20
-
21
- options.klass = klass
22
- end
23
-
24
- initialize_without_custom_fields(parent, options, target_array)
25
- end
26
-
27
- alias_method_chain :initialize, :custom_fields
28
-
29
- end
30
- end
31
- end
@@ -1,20 +0,0 @@
1
- # encoding: utf-8
2
- module Mongoid #:nodoc
3
- module Associations #:nodoc
4
- class Proxy #:nodoc
5
-
6
- def custom_fields_association_name(association_name)
7
- "#{association_name.to_s.singularize}_custom_fields".to_sym
8
- end
9
-
10
- def custom_fields?(object, association_name)
11
- object.respond_to?(custom_fields_association_name(association_name))
12
- end
13
-
14
- def klass
15
- @klass
16
- end
17
-
18
- end
19
- end
20
- end
@@ -1,33 +0,0 @@
1
- # encoding: utf-8
2
- module Mongoid #:nodoc:
3
- module Associations #:nodoc:
4
- # Represents an relational one-to-many association with an object in a
5
- # separate collection or database.
6
- class ReferencesMany < Proxy
7
-
8
- def initialize_with_custom_fields(parent, options, target_array = nil)
9
- if custom_fields?(parent, options.name)
10
- options = options.clone # 2 parent instances should not share the exact same option instance
11
-
12
- custom_fields = parent.send(:"ordered_#{custom_fields_association_name(options.name)}")
13
-
14
- klass = options.klass.to_klass_with_custom_fields(custom_fields)
15
- klass._parent = parent
16
- klass.association_name = options.name
17
-
18
- options.instance_eval <<-EOF
19
- def klass=(klass); @klass = klass; end
20
- def klass; @klass || class_name.constantize; end
21
- EOF
22
-
23
- options.klass = klass
24
- end
25
-
26
- initialize_without_custom_fields(parent, options, target_array)
27
- end
28
-
29
- alias_method_chain :initialize, :custom_fields
30
-
31
- end
32
- end
33
- end
@@ -1,28 +0,0 @@
1
- # encoding: utf-8
2
- module Mongoid #:nodoc
3
- module Hierarchy #:nodoc
4
- module InstanceMethods
5
-
6
- def parentize_with_custom_fields(object, association_name)
7
- if association_name.to_s.ends_with?('_custom_fields')
8
- self.singleton_class.associations = {}
9
- self.singleton_class.embedded_in object.class.to_s.underscore.to_sym, :inverse_of => association_name
10
- end
11
-
12
- parentize_without_custom_fields(object, association_name)
13
-
14
- if self.embedded? && self.instance_variable_get(:"@association_name").nil?
15
- self.instance_variable_set(:"@association_name", association_name) # weird bug with proxy class
16
- end
17
-
18
- if association_name.to_s.ends_with?('_custom_fields')
19
- self.send(:set_unique_name!)
20
- self.send(:set_alias)
21
- end
22
- end
23
-
24
- alias_method_chain :parentize, :custom_fields
25
-
26
- end
27
- end
28
- end