diff options
Diffstat (limited to 'activemodel/lib')
-rw-r--r-- | activemodel/lib/active_model/attribute_methods.rb | 33 | ||||
-rw-r--r-- | activemodel/lib/active_model/serialization.rb | 46 | ||||
-rw-r--r-- | activemodel/lib/active_model/serializers/xml.rb | 40 |
3 files changed, 104 insertions, 15 deletions
diff --git a/activemodel/lib/active_model/attribute_methods.rb b/activemodel/lib/active_model/attribute_methods.rb index 6ee5e04267..bdc0eb4a0d 100644 --- a/activemodel/lib/active_model/attribute_methods.rb +++ b/activemodel/lib/active_model/attribute_methods.rb @@ -314,6 +314,7 @@ module ActiveModel end end end + attribute_method_matchers_cache.clear end # Removes all the previously dynamically defined methods from the class @@ -321,6 +322,7 @@ module ActiveModel generated_attribute_methods.module_eval do instance_methods.each { |m| undef_method(m) } end + attribute_method_matchers_cache.clear end # Returns true if the attribute methods defined have been generated. @@ -338,6 +340,29 @@ module ActiveModel end private + # The methods +method_missing+ and +respond_to?+ of this module are + # invoked often in a typical rails, both of which invoke the method + # +match_attribute_method?+. The latter method iterates through an + # array doing regular expression matches, which results in a lot of + # object creations. Most of the times it returns a +nil+ match. As the + # match result is always the same given a +method_name+, this cache is + # used to alleviate the GC, which ultimately also speeds up the app + # significantly (in our case our test suite finishes 10% faster with + # this cache). + def attribute_method_matchers_cache + @attribute_method_matchers_cache ||= {} + end + + def attribute_method_matcher(method_name) + if attribute_method_matchers_cache.key?(method_name) + attribute_method_matchers_cache[method_name] + else + match = nil + attribute_method_matchers.detect { |method| match = method.match(method_name) } + attribute_method_matchers_cache[method_name] = match + end + end + class AttributeMethodMatcher attr_reader :prefix, :suffix, :method_missing_target @@ -411,12 +436,8 @@ module ActiveModel # Returns a struct representing the matching attribute method. # The struct's attributes are prefix, base and suffix. def match_attribute_method?(method_name) - self.class.attribute_method_matchers.each do |method| - if (match = method.match(method_name)) && attribute_method?(match.attr_name) - return match - end - end - nil + match = self.class.send(:attribute_method_matcher, method_name) + match && attribute_method?(match.attr_name) ? match : nil end # prevent method_missing from calling private methods with #send diff --git a/activemodel/lib/active_model/serialization.rb b/activemodel/lib/active_model/serialization.rb index 4a174cb62a..9260c5082d 100644 --- a/activemodel/lib/active_model/serialization.rb +++ b/activemodel/lib/active_model/serialization.rb @@ -69,18 +69,46 @@ module ActiveModel def serializable_hash(options = nil) options ||= {} - only = Array.wrap(options[:only]).map(&:to_s) - except = Array.wrap(options[:except]).map(&:to_s) - attribute_names = attributes.keys.sort - if only.any? - attribute_names &= only - elsif except.any? - attribute_names -= except + if only = options[:only] + attribute_names &= Array.wrap(only).map(&:to_s) + elsif except = options[:except] + attribute_names -= Array.wrap(except).map(&:to_s) + end + + method_names = Array.wrap(options[:methods]).select { |n| respond_to?(n) } + hash = Hash[(attribute_names + method_names).map { |n| [n, send(n)] }] + + serializable_add_includes(options) do |association, records, opts| + hash[association] = if records.is_a?(Enumerable) + records.map { |a| a.serializable_hash(opts) } + else + records.serializable_hash(opts) + end end - method_names = Array.wrap(options[:methods]).map { |n| n if respond_to?(n.to_s) }.compact - Hash[(attribute_names + method_names).map { |n| [n, send(n)] }] + hash end + + private + # Add associations specified via the <tt>:include</tt> option. + # + # Expects a block that takes as arguments: + # +association+ - name of the association + # +records+ - the association record(s) to be serialized + # +opts+ - options for the association records + def serializable_add_includes(options = {}) + return unless include = options[:include] + + unless include.is_a?(Hash) + include = Hash[Array.wrap(include).map { |n| [n, {}] }] + end + + include.each do |association, opts| + if records = send(association) + yield association, records, opts + end + end + end end end diff --git a/activemodel/lib/active_model/serializers/xml.rb b/activemodel/lib/active_model/serializers/xml.rb index 9812af43d6..64dda3bcee 100644 --- a/activemodel/lib/active_model/serializers/xml.rb +++ b/activemodel/lib/active_model/serializers/xml.rb @@ -101,6 +101,7 @@ module ActiveModel @builder.tag!(*args) do add_attributes_and_methods + add_includes add_extra_behavior add_procs yield @builder if block_given? @@ -120,6 +121,45 @@ module ActiveModel end end + def add_includes + @serializable.send(:serializable_add_includes, options) do |association, records, opts| + add_associations(association, records, opts) + end + end + + # TODO This can likely be cleaned up to simple use ActiveSupport::XmlMini.to_tag as well. + def add_associations(association, records, opts) + merged_options = opts.merge(options.slice(:builder, :indent)) + merged_options[:skip_instruct] = true + + if records.is_a?(Enumerable) + tag = ActiveSupport::XmlMini.rename_key(association.to_s, options) + type = options[:skip_types] ? { } : {:type => "array"} + association_name = association.to_s.singularize + merged_options[:root] = association_name + + if records.empty? + @builder.tag!(tag, type) + else + @builder.tag!(tag, type) do + records.each do |record| + if options[:skip_types] + record_type = {} + else + record_class = (record.class.to_s.underscore == association_name) ? nil : record.class.name + record_type = {:type => record_class} + end + + record.to_xml merged_options.merge(record_type) + end + end + end + else + merged_options[:root] = association.to_s + records.to_xml(merged_options) + end + end + def add_procs if procs = options.delete(:procs) Array.wrap(procs).each do |proc| |