module ProjectAdapter def adapters @adapters ||= {} end def has_adapter(*adapter_namespaces) adapter_namespaces.each do |adapter_namespace| adapter_module = Houston::Adapters[adapter_namespace] raise ArgumentError, "#{adapter_module} should respond to `adapters`" unless adapter_module.respond_to?(:adapters) raise ArgumentError, "#{adapter_module} should respond to `adapter`" unless adapter_module.respond_to?(:adapter) raise ArgumentError, "#{adapter_namespace} has already been defined" if adapters[adapter_module.name] adapter = Adapter.new(self, adapter_module) adapters[adapter_module.name] = adapter adapter.define_methods! end end class Adapter def initialize(model, adapter_module) @model = model @namespace = adapter_module @name = adapter_module.name @attribute_name = name.demodulize.underscore end attr_reader :model, :namespace, :name, :attribute_name def title name.demodulize.titleize end def validation_method :"#{attribute_name}_configuration_is_valid" end def before_update_method :"#{attribute_name}_before_update" end def adapter_method :"#{attribute_name}_adapter" end def params_method :"parameters_for_#{attribute_name}_adapter" end def concern_name :"#{name.demodulize}Concern" end def adapter_name_method :"#{attribute_name}_name" end def prop_name "adapter.#{attribute_name.to_s.camelize(:lower)}" end def define_methods! concern = ProjectAdapter.const_set(concern_name, Module.new) concern.extend ActiveSupport::Concern class_methods = concern.const_set(:ClassMethods, Module.new) class_methods.module_eval <<-RUBY, __FILE__ , __LINE__ + 1 def with_#{attribute_name}(value=nil) if value where [ "COALESCE(projects.props->>'#{prop_name}', 'None') = ?", value ] else where "COALESCE(projects.props->>'#{prop_name}', 'None') != 'None'" end end RUBY concern.module_eval <<-RUBY, __FILE__ , __LINE__ + 1 included do validate :#{validation_method} before_update :#{before_update_method} end def #{adapter_name_method} props["#{prop_name}"] || "None" end def has_#{attribute_name}? #{adapter_name_method} != "None" end def #{validation_method} #{adapter_method}.errors_with_parameters(self, *#{params_method}.values).each do |attribute, messages| errors.add(attribute, messages) if messages.any? end end def #{before_update_method} return true unless #{attribute_name}.respond_to?(:before_update) #{attribute_name}.before_update(self) end def #{attribute_name} @#{attribute_name} ||= #{adapter_method} .build(self, *#{params_method}.values) .extend(FeatureSupport) end def #{params_method} #{adapter_method}.parameters.each_with_object({}) do |parameter, hash| hash[parameter] = props[parameter.to_s] end end def #{adapter_method} #{namespace}.adapter(#{adapter_name_method}) end RUBY model.send :include, concern end end end