From a1a4985149ebf0fb6769cacec507fae57f9b81ad Mon Sep 17 00:00:00 2001 From: Geoff Buesing Date: Thu, 19 Mar 2009 12:50:58 -0500 Subject: [PATCH] Namespace Extlib versions of class_inheritable_accessor methods with extlib_prefix, as done in Wycats' Rails fork, so that their inclusion won't overwrite existing ActiveSupport implementations, if present. Check for existence of Class extensions on a per-method basis. --- lib/couchrest/core/document.rb | 4 +- lib/couchrest/mixins/callbacks.rb | 6 +- lib/couchrest/mixins/views.rb | 6 +- lib/couchrest/support/class.rb | 295 +++++++++++++-------------- spec/couchrest/support/class_spec.rb | 59 ------ 5 files changed, 148 insertions(+), 222 deletions(-) delete mode 100644 spec/couchrest/support/class_spec.rb diff --git a/lib/couchrest/core/document.rb b/lib/couchrest/core/document.rb index 187f2dd..1f889b3 100644 --- a/lib/couchrest/core/document.rb +++ b/lib/couchrest/core/document.rb @@ -5,10 +5,10 @@ module CouchRest include CouchRest::Mixins::Attachments # def self.inherited(subklass) - # subklass.send(:class_inheritable_accessor, :database) + # subklass.send(:extlib_inheritable_accessor, :database) # end - class_inheritable_accessor :database + extlib_inheritable_accessor :database attr_accessor :database # override the CouchRest::Model-wide default_database diff --git a/lib/couchrest/mixins/callbacks.rb b/lib/couchrest/mixins/callbacks.rb index cd22a74..2516702 100644 --- a/lib/couchrest/mixins/callbacks.rb +++ b/lib/couchrest/mixins/callbacks.rb @@ -426,10 +426,10 @@ module CouchRest def define_callbacks(*symbols) terminator = symbols.pop if symbols.last.is_a?(String) symbols.each do |symbol| - self.class_inheritable_accessor("_#{symbol}_terminator") + self.extlib_inheritable_accessor("_#{symbol}_terminator") self.send("_#{symbol}_terminator=", terminator) self.class_eval <<-RUBY_EVAL, __FILE__, __LINE__ + 1 - class_inheritable_accessor :_#{symbol}_callbacks + extlib_inheritable_accessor :_#{symbol}_callbacks self._#{symbol}_callbacks = CallbackChain.new(:#{symbol}) def self.#{symbol}_callback(*filters, &blk) @@ -480,4 +480,4 @@ module CouchRest end end end -end \ No newline at end of file +end diff --git a/lib/couchrest/mixins/views.rb b/lib/couchrest/mixins/views.rb index afadbfc..852afd5 100644 --- a/lib/couchrest/mixins/views.rb +++ b/lib/couchrest/mixins/views.rb @@ -4,9 +4,9 @@ module CouchRest def self.included(base) base.extend(ClassMethods) - base.send(:class_inheritable_accessor, :design_doc) - base.send(:class_inheritable_accessor, :design_doc_slug_cache) - base.send(:class_inheritable_accessor, :design_doc_fresh) + base.send(:extlib_inheritable_accessor, :design_doc) + base.send(:extlib_inheritable_accessor, :design_doc_slug_cache) + base.send(:extlib_inheritable_accessor, :design_doc_fresh) end module ClassMethods diff --git a/lib/couchrest/support/class.rb b/lib/couchrest/support/class.rb index 1862c31..ecbab76 100644 --- a/lib/couchrest/support/class.rb +++ b/lib/couchrest/support/class.rb @@ -25,167 +25,152 @@ # example, an array without those additions being shared with either their # parent, siblings, or children, which is unlike the regular class-level # attributes that are shared across the entire hierarchy. -module CouchRest - module ClassExtension - def self.included(base) - if CouchRest::ClassExtension::InstanceMethods.instance_methods.all? {|m| base.respond_to?(m)} - # do nothing - elsif CouchRest::ClassExtension::InstanceMethods.instance_methods.any? {|m| base.respond_to?(m)} - raise RuntimeError, "Conflicting extentions to Class, work it out" - else - base.send(:include, CouchRest::ClassExtension::InstanceMethods) - end - end - - module InstanceMethods - # Defines class-level and instance-level attribute reader. - # - # @param *syms Array of attributes to define reader for. - # @return List of attributes that were made into cattr_readers - # - # @api public - # - # @todo Is this inconsistent in that it does not allow you to prevent - # an instance_reader via :instance_reader => false - def cattr_reader(*syms) - syms.flatten.each do |sym| - next if sym.is_a?(Hash) - class_eval(<<-RUBY, __FILE__, __LINE__ + 1) - unless defined? @@#{sym} - @@#{sym} = nil - end - - def self.#{sym} - @@#{sym} - end - - def #{sym} - @@#{sym} - end - RUBY +class Class + # Defines class-level and instance-level attribute reader. + # + # @param *syms Array of attributes to define reader for. + # @return List of attributes that were made into cattr_readers + # + # @api public + # + # @todo Is this inconsistent in that it does not allow you to prevent + # an instance_reader via :instance_reader => false + def cattr_reader(*syms) + syms.flatten.each do |sym| + next if sym.is_a?(Hash) + class_eval(<<-RUBY, __FILE__, __LINE__ + 1) + unless defined? @@#{sym} + @@#{sym} = nil end - end - - # Defines class-level (and optionally instance-level) attribute writer. - # - # @param Boolean}]> Array of attributes to define writer for. - # @option syms :instance_writer if true, instance-level attribute writer is defined. - # @return List of attributes that were made into cattr_writers - # - # @api public - def cattr_writer(*syms) - options = syms.last.is_a?(Hash) ? syms.pop : {} - syms.flatten.each do |sym| - class_eval(<<-RUBY, __FILE__, __LINE__ + 1) - unless defined? @@#{sym} - @@#{sym} = nil + + def self.#{sym} + @@#{sym} + end + + def #{sym} + @@#{sym} + end + RUBY end - - def self.#{sym}=(obj) - @@#{sym} = obj - end - RUBY - - unless options[:instance_writer] == false - class_eval(<<-RUBY, __FILE__, __LINE__ + 1) - def #{sym}=(obj) - @@#{sym} = obj - end - RUBY + end unless Class.respond_to?(:cattr_reader) + + # Defines class-level (and optionally instance-level) attribute writer. + # + # @param Boolean}]> Array of attributes to define writer for. + # @option syms :instance_writer if true, instance-level attribute writer is defined. + # @return List of attributes that were made into cattr_writers + # + # @api public + def cattr_writer(*syms) + options = syms.last.is_a?(Hash) ? syms.pop : {} + syms.flatten.each do |sym| + class_eval(<<-RUBY, __FILE__, __LINE__ + 1) + unless defined? @@#{sym} + @@#{sym} = nil + end + + def self.#{sym}=(obj) + @@#{sym} = obj + end + RUBY + + unless options[:instance_writer] == false + class_eval(<<-RUBY, __FILE__, __LINE__ + 1) + def #{sym}=(obj) + @@#{sym} = obj end + RUBY + end + end + end unless Class.respond_to?(:cattr_writer) + + # Defines class-level (and optionally instance-level) attribute accessor. + # + # @param *syms Boolean}]> Array of attributes to define accessor for. + # @option syms :instance_writer if true, instance-level attribute writer is defined. + # @return List of attributes that were made into accessors + # + # @api public + def cattr_accessor(*syms) + cattr_reader(*syms) + cattr_writer(*syms) + end unless Class.respond_to?(:cattr_accessor) + + # Defines class-level inheritable attribute reader. Attributes are available to subclasses, + # each subclass has a copy of parent's attribute. + # + # @param *syms Array of attributes to define inheritable reader for. + # @return Array of attributes converted into inheritable_readers. + # + # @api public + # + # @todo Do we want to block instance_reader via :instance_reader => false + # @todo It would be preferable that we do something with a Hash passed in + # (error out or do the same as other methods above) instead of silently + # moving on). In particular, this makes the return value of this function + # less useful. + def extlib_inheritable_reader(*ivars) + instance_reader = ivars.pop[:reader] if ivars.last.is_a?(Hash) + + ivars.each do |ivar| + self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def self.#{ivar} + return @#{ivar} if self.object_id == #{self.object_id} || defined?(@#{ivar}) + ivar = superclass.#{ivar} + return nil if ivar.nil? && !#{self}.instance_variable_defined?("@#{ivar}") + @#{ivar} = ivar && !ivar.is_a?(Module) && !ivar.is_a?(Numeric) && !ivar.is_a?(TrueClass) && !ivar.is_a?(FalseClass) ? ivar.dup : ivar end - end - - # Defines class-level (and optionally instance-level) attribute accessor. - # - # @param *syms Boolean}]> Array of attributes to define accessor for. - # @option syms :instance_writer if true, instance-level attribute writer is defined. - # @return List of attributes that were made into accessors - # - # @api public - def cattr_accessor(*syms) - cattr_reader(*syms) - cattr_writer(*syms) - end - - # Defines class-level inheritable attribute reader. Attributes are available to subclasses, - # each subclass has a copy of parent's attribute. - # - # @param *syms Array of attributes to define inheritable reader for. - # @return Array of attributes converted into inheritable_readers. - # - # @api public - # - # @todo Do we want to block instance_reader via :instance_reader => false - # @todo It would be preferable that we do something with a Hash passed in - # (error out or do the same as other methods above) instead of silently - # moving on). In particular, this makes the return value of this function - # less useful. - def class_inheritable_reader(*ivars) - instance_reader = ivars.pop[:reader] if ivars.last.is_a?(Hash) - - ivars.each do |ivar| - self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def self.#{ivar} - return @#{ivar} if self.object_id == #{self.object_id} || defined?(@#{ivar}) - ivar = superclass.#{ivar} - return nil if ivar.nil? && !#{self}.instance_variable_defined?("@#{ivar}") - @#{ivar} = ivar && !ivar.is_a?(Module) && !ivar.is_a?(Numeric) && !ivar.is_a?(TrueClass) && !ivar.is_a?(FalseClass) && !ivar.is_a?(Symbol) ? ivar.dup : ivar - end - RUBY - unless instance_reader == false - self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{ivar} - self.class.#{ivar} - end - RUBY + RUBY + unless instance_reader == false + self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{ivar} + self.class.#{ivar} end - end - end - - # Defines class-level inheritable attribute writer. Attributes are available to subclasses, - # each subclass has a copy of parent's attribute. - # - # @param *syms Boolean}]> Array of attributes to - # define inheritable writer for. - # @option syms :instance_writer if true, instance-level inheritable attribute writer is defined. - # @return An Array of the attributes that were made into inheritable writers. - # - # @api public - # - # @todo We need a style for class_eval <<-HEREDOC. I'd like to make it - # class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere. - def class_inheritable_writer(*ivars) - instance_writer = ivars.pop[:instance_writer] if ivars.last.is_a?(Hash) - ivars.each do |ivar| - self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def self.#{ivar}=(obj) - @#{ivar} = obj - end - RUBY - unless instance_writer == false - self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 - def #{ivar}=(obj) self.class.#{ivar} = obj end - RUBY - end - end - end - - # Defines class-level inheritable attribute accessor. Attributes are available to subclasses, - # each subclass has a copy of parent's attribute. - # - # @param *syms Boolean}]> Array of attributes to - # define inheritable accessor for. - # @option syms :instance_writer if true, instance-level inheritable attribute writer is defined. - # @return An Array of attributes turned into inheritable accessors. - # - # @api public - def class_inheritable_accessor(*syms) - class_inheritable_reader(*syms) - class_inheritable_writer(*syms) + RUBY end end - end + end unless Class.respond_to?(:extlib_inheritable_reader) + + # Defines class-level inheritable attribute writer. Attributes are available to subclasses, + # each subclass has a copy of parent's attribute. + # + # @param *syms Boolean}]> Array of attributes to + # define inheritable writer for. + # @option syms :instance_writer if true, instance-level inheritable attribute writer is defined. + # @return An Array of the attributes that were made into inheritable writers. + # + # @api public + # + # @todo We need a style for class_eval <<-HEREDOC. I'd like to make it + # class_eval(<<-RUBY, __FILE__, __LINE__), but we should codify it somewhere. + def extlib_inheritable_writer(*ivars) + instance_writer = ivars.pop[:writer] if ivars.last.is_a?(Hash) + ivars.each do |ivar| + self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def self.#{ivar}=(obj) + @#{ivar} = obj + end + RUBY + unless instance_writer == false + self.class_eval <<-RUBY, __FILE__, __LINE__ + 1 + def #{ivar}=(obj) self.class.#{ivar} = obj end + RUBY + end + end + end unless Class.respond_to?(:extlib_inheritable_writer) + + # Defines class-level inheritable attribute accessor. Attributes are available to subclasses, + # each subclass has a copy of parent's attribute. + # + # @param *syms Boolean}]> Array of attributes to + # define inheritable accessor for. + # @option syms :instance_writer if true, instance-level inheritable attribute writer is defined. + # @return An Array of attributes turned into inheritable accessors. + # + # @api public + def extlib_inheritable_accessor(*syms) + extlib_inheritable_reader(*syms) + extlib_inheritable_writer(*syms) + end unless Class.respond_to?(:extlib_inheritable_accessor) end -Class.send(:include, CouchRest::ClassExtension) diff --git a/spec/couchrest/support/class_spec.rb b/spec/couchrest/support/class_spec.rb deleted file mode 100644 index 7805f42..0000000 --- a/spec/couchrest/support/class_spec.rb +++ /dev/null @@ -1,59 +0,0 @@ -require File.join(File.dirname(__FILE__), '..', '..', 'spec_helper') -require File.join(File.dirname(__FILE__), '..', '..', '..', 'lib', 'couchrest', 'support', 'class') - -describe CouchRest::ClassExtension do - - before :all do - class FullyDefinedClassExtensions - def self.respond_to?(method) - if CouchRest::ClassExtension::InstanceMethods.instance_methods.include?(method) - true - else - super - end - end - end - - class PartDefinedClassExtensions - def self.respond_to?(method) - methods = CouchRest::ClassExtension::InstanceMethods.instance_methods - methods.delete('cattr_reader') - - if methods.include?(method) - false - else - super - end - end - end - - class NoClassExtensions - def self.respond_to?(method) - if CouchRest::ClassExtension::InstanceMethods.instance_methods.include?(method) - false - else - super - end - end - end - - - end - - it "should not include InstanceMethods if the class extensions are already defined" do - FullyDefinedClassExtensions.send(:include, CouchRest::ClassExtension) - FullyDefinedClassExtensions.ancestors.should_not include(CouchRest::ClassExtension::InstanceMethods) - end - - it "should raise RuntimeError if the class extensions are only partially defined" do - lambda { - PartDefinedClassExtensions.send(:include, CouchRest::ClassExtension) - }.should raise_error(RuntimeError) - end - - it "should include class extensions if they are not already defined" do - NoClassExtensions.send(:include, CouchRest::ClassExtension) - NoClassExtensions.ancestors.should include(CouchRest::ClassExtension::InstanceMethods) - end - -end \ No newline at end of file