diff --git a/lib/mongoid/attributes/dynamic.rb b/lib/mongoid/attributes/dynamic.rb index 294b418a0f..ad7ad71ef2 100644 --- a/lib/mongoid/attributes/dynamic.rb +++ b/lib/mongoid/attributes/dynamic.rb @@ -20,7 +20,7 @@ module Dynamic def respond_to?(name, include_private = false) super || ( attributes && - attributes.has_key?(name.to_s.reader) + attributes.has_key?(__dynamic_reader(name)) ) end @@ -33,7 +33,7 @@ def respond_to?(name, include_private = false) # # @param [ String ] name The name of the field. def define_dynamic_reader(name) - return unless name.valid_method_name? + return unless valid_method_name?(name) class_eval do define_method(name) do @@ -69,7 +69,7 @@ def define_dynamic_before_type_cast_reader(name) # # @param [ String ] name The name of the field. def define_dynamic_writer(name) - return unless name.valid_method_name? + return unless valid_method_name?(name) class_eval do define_method("#{name}=") do |value| @@ -120,23 +120,53 @@ def inspect_dynamic_fields # # @return [ Object ] The result of the method call. def method_missing(name, *args) - attr = name.to_s - return super unless attributes.has_key?(attr.reader) - if attr.writer? - getter = attr.reader - define_dynamic_writer(getter) - write_attribute(getter, args.first) - elsif attr.before_type_cast? - define_dynamic_before_type_cast_reader(attr.reader) - attribute_will_change!(attr.reader) - read_attribute_before_type_cast(attr.reader) + reader = __dynamic_reader(name) + return super unless attributes.has_key?(reader) + if __dynamic_writer?(name) + define_dynamic_writer(reader) + write_attribute(reader, args.first) + elsif name.to_s.ends_with?('_before_type_cast') + define_dynamic_before_type_cast_reader(reader) + attribute_will_change!(reader) + read_attribute_before_type_cast(reader) else - getter = attr.reader - define_dynamic_reader(getter) - attribute_will_change!(attr.reader) - read_raw_attribute(getter) + define_dynamic_reader(reader) + attribute_will_change!(reader) + read_raw_attribute(reader) end end + + private + + # Get the string as a getter string. + # + # @example Get the reader/getter + # "model=".reader + # + # @return [ String ] The string stripped of "=". + def __dynamic_reader(method) + method.to_s.delete("=").sub(/_before_type_cast\z/, '') + end + + # Is this string a writer? + # + # @example Is the string a setter method? + # "model=".writer? + # + # @return [ true | false ] If the string contains "=". + def __dynamic_writer?(method) + method.to_s.include?("=") + end + + # Is this string a valid_method_name? + # + # @example Is the string a valid Ruby identifier for use as a method name + # "model=".valid_method_name? + # + # @return [ true | false ] If the string contains a valid Ruby identifier. + def valid_method_name?(method) + !method.to_s.match?(/[@$"-]/) + end end end end diff --git a/lib/mongoid/extensions/string.rb b/lib/mongoid/extensions/string.rb index b541b3adc9..783c37b995 100644 --- a/lib/mongoid/extensions/string.rb +++ b/lib/mongoid/extensions/string.rb @@ -88,47 +88,6 @@ def numeric? (self =~ /\A(?:NaN|-?Infinity)\z/) == 0 end - # Get the string as a getter string. - # - # @example Get the reader/getter - # "model=".reader - # - # @return [ String ] The string stripped of "=". - def reader - delete("=").sub(/\_before\_type\_cast\z/, '') - end - - # Is this string a writer? - # - # @example Is the string a setter method? - # "model=".writer? - # - # @return [ true | false ] If the string contains "=". - def writer? - include?("=") - end - - # Is this string a valid_method_name? - # - # @example Is the string a valid Ruby identifier for use as a method name - # "model=".valid_method_name? - # - # @return [ true | false ] If the string contains a valid Ruby identifier. - def valid_method_name? - /[@$"-]/ !~ self - end - - # Does the string end with _before_type_cast? - # - # @example Is the string a setter method? - # "price_before_type_cast".before_type_cast? - # - # @return [ true | false ] If the string ends with "_before_type_cast" - def before_type_cast? - ends_with?("_before_type_cast") - end - - # Is the object not to be converted to bson on criteria creation? # # @example Is the object unconvertable? diff --git a/spec/mongoid/attributes/dynamic_spec.rb b/spec/mongoid/attributes/dynamic_spec.rb index 8dff0e1b3d..82e605ed42 100644 --- a/spec/mongoid/attributes/dynamic_spec.rb +++ b/spec/mongoid/attributes/dynamic_spec.rb @@ -150,4 +150,62 @@ it_behaves_like 'dynamic field' end + + describe "#reader" do + + context "when string is a reader" do + + it "returns self" do + expect("attribute".reader).to eq("attribute") + end + end + + context "when string is a writer" do + + it "returns the reader" do + expect("attribute=".reader).to eq("attribute") + end + end + + context "when the string is before_type_cast" do + + it "returns the reader" do + expect("attribute_before_type_cast".reader).to eq("attribute") + end + end + end + + describe "#writer?" do + + context "when string is a reader" do + + it "returns false" do + expect("attribute".writer?).to be false + end + end + + context "when string is a writer" do + + it "returns true" do + expect("attribute=".writer?).to be true + end + end + end + + describe "#before_type_cast?" do + + context "when string is a reader" do + + it "returns false" do + expect("attribute".before_type_cast?).to be false + end + end + + context "when string is before_type_cast" do + + it "returns true" do + expect("attribute_before_type_cast".before_type_cast?).to be true + end + end + end end diff --git a/spec/mongoid/extensions/string_spec.rb b/spec/mongoid/extensions/string_spec.rb index 5f057ad17a..89c5de2a4f 100644 --- a/spec/mongoid/extensions/string_spec.rb +++ b/spec/mongoid/extensions/string_spec.rb @@ -199,30 +199,6 @@ class Patient end end - describe "#reader" do - - context "when string is a reader" do - - it "returns self" do - expect("attribute".reader).to eq("attribute") - end - end - - context "when string is a writer" do - - it "returns the reader" do - expect("attribute=".reader).to eq("attribute") - end - end - - context "when the string is before_type_cast" do - - it "returns the reader" do - expect("attribute_before_type_cast".reader).to eq("attribute") - end - end - end - describe "#numeric?" do context "when the string is an integer" do @@ -319,39 +295,4 @@ class Patient end end end - - describe "#writer?" do - - context "when string is a reader" do - - it "returns false" do - expect("attribute".writer?).to be false - end - end - - context "when string is a writer" do - - it "returns true" do - expect("attribute=".writer?).to be true - end - end - end - - describe "#before_type_cast?" do - - context "when string is a reader" do - - it "returns false" do - expect("attribute".before_type_cast?).to be false - end - end - - context "when string is before_type_cast" do - - it "returns true" do - expect("attribute_before_type_cast".before_type_cast?).to be true - end - end - end - end