Skip to content

Commit

Permalink
went enough far for the proof of concept, now merging the good stuff …
Browse files Browse the repository at this point in the history
…into the master branch
  • Loading branch information
did committed Dec 14, 2011
1 parent d3ca2c7 commit b6eb97b
Show file tree
Hide file tree
Showing 17 changed files with 428 additions and 59 deletions.
2 changes: 1 addition & 1 deletion MIT-LICENSE
@@ -1,4 +1,4 @@
Copyright (c) 2010 [Didier Lafforgue]
Copyright (c) 2011 [Didier Lafforgue]

Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
Expand Down
1 change: 1 addition & 0 deletions lib/custom_fields.rb
Expand Up @@ -21,6 +21,7 @@ def self.options

require 'custom_fields/version'
require 'custom_fields/extensions/mongoid/relations/referenced/many'
require 'custom_fields/accessors_builder'
require 'custom_fields/types/default'
require 'custom_fields/types/string'
require 'custom_fields/types/text'
Expand Down
56 changes: 56 additions & 0 deletions lib/custom_fields/accessors_builder.rb
@@ -0,0 +1,56 @@
module CustomFields
module AccessorsBuilder

extend ActiveSupport::Concern

module InstanceMethods

#
# TODO
#
def build_custom_fields_accessors_module
puts "build_custom_fields_accessors_module version = #{self.custom_fields_recipe['version']}" # DEBUG

Module.new.tap do |accessors_module|
accessors_module.class_eval <<-EOV
def self.version
#{self.custom_fields_recipe['version']}
end
EOV

self.custom_fields_recipe['rules'].each do |rule|
# puts "rule = #{rule.inspect}" # DEBUG

# next if rule['type'] != 'string' # DEBUG
# next if self.singleton_methods.include?(rule['name'].to_sym)

self.send(:"apply_#{rule['type']}_custom_field", rule['name'], accessors_module)
end
end
end

#
# TODO
#
def custom_fields_accessors_module
name = "#{self.custom_fields_recipe['name']}Accessors"

# puts "accessors_module name = #{name}"

m = Object.const_defined?(name) ? Object.const_get(name) : nil

if m.nil? || m.version != self.custom_fields_recipe['version']
Object.send(:remove_const, name) if m

m = build_custom_fields_accessors_module

Object.const_set(name, m)
end

m
end

end

end
end
Expand Up @@ -9,10 +9,14 @@ class Many < Relations::Many

def build_with_custom_fields(attributes = {}, options = {}, type = nil)
if base.custom_fields_for?(metadata.name)
puts "build powered by custom_fields #{attributes.inspect}" # DEBUG
# puts "build powered by custom_fields #{attributes.inspect}" # DEBUG

default_attribute = {
:custom_fields_recipe => base.custom_fields_recipe_for(metadata.name)
:custom_fields_recipe => {
'name' => "#{metadata.name.to_s.classify}#{base._id}",
'rules' => base.custom_fields_recipe_for(metadata.name),
'version' => 0
}
}

build_without_custom_fields(default_attribute, options, type).tap do |doc|
Expand Down
1 change: 0 additions & 1 deletion lib/custom_fields/field.rb
Expand Up @@ -13,7 +13,6 @@ class Field
include Types::Boolean
include Types::File

# include Types::File
# include Types::Category
# include Types::HasOne
# include Types::HasMany
Expand Down
41 changes: 37 additions & 4 deletions lib/custom_fields/source.rb
Expand Up @@ -6,7 +6,6 @@ module Source

included do
cattr_accessor :_custom_fields_for

self._custom_fields_for = []

attr_accessor :_custom_fields_diff
Expand Down Expand Up @@ -51,10 +50,20 @@ def custom_fields_recipe_for(name)
# TODO
#
def initialize_custom_fields_diff(name)
@_custom_fields_diff ||= {}
self._custom_fields_diff ||= {}
self._custom_fields_diff[name] = { '$set' => {}, '$unset' => {}, '$rename' => {} }
end

#
# TODO
#
def custom_fields_changed?(name)
self._custom_fields_diff[name] &&
(!self._custom_fields_diff[name]['$set'].empty? ||
!self._custom_fields_diff[name]['$unset'].empty? ||
!self._custom_fields_diff[name]['$rename'].empty?)
end

#
# TODO
#
Expand All @@ -68,18 +77,42 @@ def collect_custom_fields_diff(name, fields)
end
end

# #
# # TODO
# #
# def custom_fields_version(name)
# self.send(:"#{name}_custom_fields_version") || 0
# end
#
# # Everytime the source is modified
# # we bump the version.
# #
# # @param [ String, Symbol ] name The name of the relation.
# #
# def bump_custom_fields_version(name)
# if self.custom_fields_changed?(name)
# version = self.custom_fields_version(name)
# self.send(:"#{name}_custom_fields_version=", version + 1)
#
# puts "new version for #{name} => #{version} => #{version + 1}" # DEBUG
# end
# end

#
# TODO
#
def apply_custom_fields_diff(name)
# puts "==> apply_custom_fields_recipes for #{name}, #{fields.size}"

attributes = self._custom_fields_diff[name]
return unless self.custom_fields_changed?(name) # no need to update them if no changes

operations = self._custom_fields_diff[name]
operations['$inc'] = { 'custom_fields_recipe.version' => 1 }
collection, selector = self.send(name).collection, self.send(name).criteria.selector

# puts "selector = #{selector.inspect}, memo = #{attributes.inspect}"

collection.update selector, attributes, :multi => true
collection.update selector, operations, :multi => true
end

end
Expand Down
133 changes: 118 additions & 15 deletions lib/custom_fields/target.rb
Expand Up @@ -5,36 +5,139 @@ module Target
extend ActiveSupport::Concern

included do
include CustomFields::Types::Default::TargetMethods
include CustomFields::Types::String::TargetMethods
include CustomFields::Types::Text::TargetMethods
include CustomFields::Types::Date::TargetMethods
include CustomFields::Types::Boolean::TargetMethods
include CustomFields::Types::File::TargetMethods

field :custom_fields_recipe, :type => Array
## modules ##
include AccessorsBuilder
include Types::Default::TargetMethods
include Types::String::TargetMethods
include Types::Text::TargetMethods
include Types::Date::TargetMethods
include Types::Boolean::TargetMethods
include Types::File::TargetMethods

## fields ##
field :custom_fields_recipe, :type => Hash

## callbacks ##
after_initialize :enhance_with_custom_fields

## accessors ##
attr_accessor :enhanced_with_custom_fields
end

# module Foo
#
# def location=(value); end
# def location; end
#
# def main_author=(value); end
# def main_author; end
#
# def posted_at=(value); end
# def formtatted_posted_at=(value); end
# def posted_at; end
# def formatted_posted_at; end
#
# def published=(value); end
# def published; end
#
# end

module InstanceMethods

#
# TODO
#
def enhanced_with_custom_fields?
!!self.enhanced_with_custom_fields
end

protected

#
# TODO
#
def enhance_with_custom_fields
puts "[enhance_with_custom_fields] #{self.singleton_methods.inspect}" # DEBUG
# puts "[enhance_with_custom_fields] #{self.singleton_methods.inspect} / #{self.enhanced_with_custom_fields?} / #{self.custom_fields_recipe.inspect}" # DEBUG

return if self.custom_fields_recipe.blank? || self.enhanced_with_custom_fields?

# singleton_class.send(:include, Foo)
singleton_class.send(:include, self.custom_fields_accessors_module)

return if self.custom_fields_recipe.nil?
# self.custom_fields_recipe.each do |rule|
# # puts "rule = #{rule.inspect}" # DEBUG
#
# # next if self.singleton_methods.include?(rule['name'].to_sym)
#
# self.send(:"apply_#{rule['type']}_custom_field", rule['name'])
# end

self.custom_fields_recipe.each do |rule|
puts "rule = #{rule.inspect}" # DEBUG
# prepare_custom_fields_validation

next if self.singleton_methods.include?(rule['name'].to_sym)
# validate_presence_of_custom_fields

self.send(:"apply_#{rule['type']}_custom_field", rule['name'])
end
self.enhanced_with_custom_fields = true
end

#
# TODO
#
def prepare_custom_fields_validation
# tie _validators to the singleton class instead of the class
self.singleton_class.cattr_accessor :_validators
self.singleton_class._validators = self.class._validators.clone
end

#
# TODO
#
def validate_presence_of_custom_fields
puts "singleton_class = #{self.singleton_class.object_id}"
puts self.singleton_class.validators.inspect

# puts "[enhance_with_custom_fields] AFTER #{self.singleton_methods.inspect}" # DEBUG
names = self.custom_fields_recipe.find_all do |rule|
!!rule['required'] && self.singleton_class.validators_on(rule['name']).empty?
end.map { |rule| rule['name'] }

puts "validates_presence_of #{names.inspect}" # DEBUG

return if names.empty?

self.singleton_class.validates_presence_of names
end

#
# TODO
#
# def apply_custom_field_rule(rule)
# unless self.singleton_methods.include?(rule['name'].to_sym)
# self.send(:"apply_#{rule['type']}_custom_field", rule['name'])
# end
# end

#
# TODO
#
# def add_custom_field_validation(rule)
# puts "required? #{!!rule['required'].inspect} && validators #{self.singleton_class.validators_on(rule['name']).inspect}"
# if !!rule['required'] && self.singleton_class.validators_on(rule['name']).empty?
# puts "ADD VALIDATES_PRESENCE_OF #{} for the singleton class"
# end
# end

#
# TODO
#

# def validate_presence_of_custom_fields
# puts "[validate_presence_of_custom_fields]" # DEBUG
#
# self.custom_fields_recipe.each do |rule|
#
# end
# end

end

module ClassMethods
Expand Down
22 changes: 16 additions & 6 deletions lib/custom_fields/types/boolean.rb
Expand Up @@ -9,14 +9,24 @@ module Boolean
#
module TargetMethods

def apply_boolean_custom_field(name)
def apply_boolean_custom_field(name, accessors_module)
# puts "...define singleton methods :#{name} & :#{name}=" # DEBUG

# getter
define_singleton_method(name) { get_boolean(name) }

# setter
define_singleton_method(:"#{name}=") { |value| set_boolean(name, value) }
# # getter
# define_singleton_method(name) { get_boolean(name) }
#
# # setter
# define_singleton_method(:"#{name}=") { |value| set_boolean(name, value) }

accessors_module.class_eval <<-EOV
def #{name}
get_boolean('#{name}')
end
def #{name}=(value)
set_boolean('#{name}', value)
end
EOV
end

protected
Expand Down

0 comments on commit b6eb97b

Please sign in to comment.