custom_fields 1.0.0.beta2 → 1.0.0.beta.3

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