diff --git a/README.md b/README.md index bb7d88713..43c45f77d 100644 --- a/README.md +++ b/README.md @@ -214,6 +214,35 @@ def default_serializer_options end ``` + +## Camelization + +The `camelize` option takes either the value `:lower` which yields `camelCase` +and `true` that yields `CamelCase` keys and roots. + +It is possible to configure camelization of keys and roots in two different ways. + +#### 1. Enable camelization globally for all, or per class + +In an initializer: + +```ruby +ActiveSupport.on_load(:active_model_serializers) do + # Enable camelization + ActiveModel::Serializer.camelize = true +end +``` + +#### 2. Subclass the serializer, and specify using it + +```ruby +class MySerializer < ActiveModel::Serializer + self.camelize = :lower + # OR + camelize :lower +end +``` + ## Getting the old version If you find that your project is already relying on the old rails to_json diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index e2a6228b6..9a7c5dc74 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -60,6 +60,8 @@ def to_s end end + class_attribute :_camelize + class_attribute :_attributes self._attributes = {} @@ -94,7 +96,7 @@ def attributes(*attrs) end def attribute(attr, options={}) - self._attributes = _attributes.merge(attr.is_a?(Hash) ? attr : {attr => options[:key] || attr.to_s.gsub(/\?$/, '').to_sym}) + self._attributes = _attributes.merge(attr.is_a?(Hash) ? attr : {attr => options[:key] || camelize_value(attr.to_s.gsub(/\?$/, ''))}) attr = attr.keys[0] if attr.is_a? Hash @@ -257,6 +259,17 @@ def root(name) end alias_method :root=, :root + # Sets camelization for attribute names + # + # :lower = camelCaseLikeThis + # true = CamelCaseLikeThis + # false = dont_camel_case_anything + # + def camelize(camelize) + self._camelize = camelize + end + alias_method :camelize=, :camelize + # Used internally to create a new serializer object based on controller # settings and options for a given resource. These settings are typically # set during the request lifecycle or by the controller class, and should @@ -289,6 +302,17 @@ def build_json(controller, resource, options) serializer.new(resource, options) end + + def camelize_value(value) + return nil unless value + if self._camelize.to_s == 'lower' + value.to_s.camelize(:lower) + elsif self._camelize + value.to_s.camelize + else + value.to_s + end.to_sym + end end attr_reader :object, :options @@ -308,12 +332,13 @@ def root_name return false if self._root == false class_name = self.class.name.demodulize.underscore.sub(/_serializer$/, '').to_sym unless self.class.name.blank? - - if self._root == true + class_name = if self._root == true class_name else self._root || class_name end + + camelize_value class_name end def url_options @@ -382,7 +407,7 @@ def include!(name, options={}) association = association_class.new(name, options, self.options) if association.embed_ids? - node[association.key] = association.serialize_ids + node[camelize_value(association.key)] = association.serialize_ids if association.embed_in_root? && hash.nil? raise IncludeError.new(self.class, association.name) @@ -390,7 +415,7 @@ def include!(name, options={}) merge_association hash, association.root, association.serializables, unique_values end elsif association.embed_objects? - node[association.key] = association.serialize + node[camelize_value(association.key)] = association.serialize end end @@ -447,6 +472,10 @@ def instrument(name, payload = {}, &block) ActiveSupport::Notifications.instrument(event_name, payload, &block) end + def camelize_value(value) + self.class.camelize_value value + end + private def default_embed_options diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index 1f2b0b53f..e63977f17 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -48,19 +48,30 @@ def initialize(name, options={}, serializer_options={}) alias embed_in_root? embed_in_root def key - if key = options[:key] + + key = if key = options[:key] key elsif use_id_key? id_key else name end + + camelize_value key end private attr_reader :embed_key, :serializer_class, :options, :serializer_options + def camelize_value(value) + if serializer_class && serializer_class.respond_to?(:camelize_value) + return serializer_class.camelize_value value + end + + ActiveModel::Serializer.camelize_value value + end + def find_serializable(object) if serializer_class serializer_class.new(object, serializer_options) @@ -73,11 +84,11 @@ def find_serializable(object) class HasMany < Association #:nodoc: def root - options[:root] || name + camelize_value options[:root] || name end def id_key - "#{name.to_s.singularize}_ids".to_sym + camelize_value "#{name.to_s.singularize}_ids".to_sym end def serializables @@ -111,17 +122,19 @@ def initialize(name, options={}, serializer_options={}) end def root - if root = options[:root] + rootname = if root = options[:root] root elsif polymorphic? object.class.to_s.pluralize.demodulize.underscore.to_sym else name.to_s.pluralize.to_sym end + + camelize_value rootname end def id_key - "#{name}_id".to_sym + camelize_value "#{name}_id".to_sym end def embeddable? @@ -137,7 +150,7 @@ def serialize if object if polymorphic? { - :type => polymorphic_key, + camelize_value("type").to_sym => polymorphic_key, polymorphic_key => find_serializable(object).serializable_hash } else @@ -149,17 +162,16 @@ def serialize def serialize_ids if object serializer = find_serializable(object) - id = - if serializer.respond_to?(embed_key) - serializer.send(embed_key) - else - object.read_attribute_for_serialization(embed_key) - end + id = if serializer.respond_to?(embed_key) + serializer.send(embed_key) + else + object.read_attribute_for_serialization(embed_key) + end if polymorphic? { - type: polymorphic_key, - id: id + camelize_value("type").to_sym => polymorphic_key, + camelize_value("id").to_sym => id } else id @@ -177,7 +189,7 @@ def use_id_key? end def polymorphic_key - object.class.to_s.demodulize.underscore.to_sym + camelize_value object.class.to_s.demodulize.underscore.to_sym end end end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 4ae2d743a..de78ac1d7 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -70,7 +70,6 @@ def active_model_serializer Array.send(:include, ActiveModel::ArraySerializerSupport) Set.send(:include, ActiveModel::ArraySerializerSupport) - { active_record: 'ActiveRecord::Relation', mongoid: 'Mongoid::Criteria' diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 5da28d8aa..270eddc06 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -287,6 +287,63 @@ def test_custom_root assert_equal({ my_blog: { author: nil } }, serializer.new(blog, scope: user).as_json) end + def test_camelization + camel = Camel.new + serializer = Class.new(ActiveModel::Serializer) do + camelize true + root :camel_upper + attributes :first_name, :last_name + end + + assert_equal({ + CamelUpper: { + FirstName: "Camel", + LastName: "Case", + } + }, serializer.new(camel).as_json) + end + + def test_camelization_lower + camel = Camel.new + serializer = Class.new(ActiveModel::Serializer) do + camelize :lower + root :camel_lower + attributes :first_name, :last_name + end + + assert_equal({ + camelLower: { + firstName: "Camel", + lastName: "Case", + } + }, serializer.new(camel).as_json) + end + + def test_camelization_global_config + begin + ActiveSupport.on_load(:active_model_serializers) do + self.camelize = :lower + end + + camel = Camel.new + serializer = Class.new(ActiveModel::Serializer) do + root :not_camel + attributes :first_name, :last_name + end + + assert_equal({ + notCamel: { + firstName: "Camel", + lastName: "Case" + } + }, serializer.new(camel).as_json) + ensure + ActiveSupport.on_load(:active_model_serializers) do + self.camelize = false + end + end + end + def test_nil_root_object user = User.new blog = nil diff --git a/test/test_fakes.rb b/test/test_fakes.rb index a0a244c13..7c1c72443 100644 --- a/test/test_fakes.rb +++ b/test/test_fakes.rb @@ -166,6 +166,16 @@ class BlogWithRootSerializer < BlogSerializer root true end +class Camel < Model + + def initialize(attributes = {}) + attributes.reverse_merge!(first_name: "Camel", last_name: 'Case') + super(attributes) + end + + attr_accessor :first_name, :last_name +end + class CustomPostSerializer < ActiveModel::Serializer attributes :title end