diff --git a/CHANGELOG.md b/CHANGELOG.md index b4c2913db..7496c6ca1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # UNRELEASED +* attributes initialisation has changed, to provide type for + a computed attribute you now need to use a hash -- e.g. + + attributes :attr1 => :key1, :attr2 => {:type => :string}, + :attr3 => {:key => :key3, :type => :integer} + attribute :attr4, :key => :key4 + attribute :attr5, :key => :key5, :type => :string + * ActiveModel::Serializable was created it has the shared code between AM::Serializer and AM::ArraySerializer. Basically enable objects to be serializable by implementing an options method to handle the options diff --git a/README.md b/README.md index 65a3b79f4..37731de3f 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -[![Build Status](https://api.travis-ci.org/rails-api/active_model_serializers.png)](https://travis-ci.org/rails-api/active_model_serializers) [![Code Climate](https://codeclimate.com/github/rails-api/active_model_serializers.png)](https://codeclimate.com/github/rails-api/active_model_serializers) [![Coverage Status](https://coveralls.io/repos/rails-api/active_model_serializers/badge.png?branch=master)](https://coveralls.io/r/rails-api/active_model_serializers) +[![Build Status](https://api.travis-ci.org/rails-api/active_model_serializers.png)](https://travis-ci.org/rails-api/active_model_serializers) [![Code Climate](https://codeclimate.com/github/rails-api/active_model_serializers.png)](https://codeclimate.com/github/rails-api/active_model_serializers) [![Coverage Status](https://coveralls.io/repos/rails-api/active_model_serializers/badge.png?branch=master)](https://coveralls.io/r/rails-api/active_model_serializers) # Purpose @@ -13,7 +13,7 @@ content. In short, **serializers replace hash-driven development with object-oriented development.** -# Installing +# Installing The easiest way to install `ActiveModel::Serializers` is to add it to your `Gemfile`: @@ -28,7 +28,7 @@ Then, install it on the command line: $ bundle install ``` -#### Ruby 1.8 is no longer supported! +#### Ruby 1.8 is no longer supported! If you must use a ruby 1.8 version (MRI 1.8.7, REE, Rubinius 1.8, or JRuby 1.8), you need to use version 0.8.x. Versions after 0.9.0 do not support ruby 1.8. To specify version 0.8, include this in your Gemfile: @@ -177,7 +177,7 @@ In an initializer: ActiveSupport.on_load(:active_model_serializers) do # Disable for all serializers (except ArraySerializer) ActiveModel::Serializer.root = false - + # Disable for ArraySerializer ActiveModel::ArraySerializer.root = false end @@ -287,13 +287,27 @@ class PostSerializer < ActiveModel::Serializer end ``` +If you would like the key in the outputted JSON to be different from its name +in ActiveRecord, you can use the `:key` option to customize it: + +```ruby +class PostSerializer < ActiveModel::Serializer + # look up :author_name and :msg_size on the model, but use :author and :size in the JSON + attributes :id, :body, {author_name: :author, msg_size: {key: :size}} + + # look up :subject on the model, but use +title+ in the JSON + attribute :subject, key: :title + has_many :comments +end +``` + The type of a computed attribute (like :full_name above) is not easily calculated without some sophisticated static code analysis. To specify the type of a computed attribute: ```ruby class PersonSerializer < ActiveModel::Serializer - attributes :first_name, :last_name, {full_name: :string} + attributes :first_name, :last_name, {full_name: {type: :string}} def full_name "#{object.first_name} #{object.last_name}" @@ -301,19 +315,6 @@ class PersonSerializer < ActiveModel::Serializer end ``` -If you would like the key in the outputted JSON to be different from its name -in ActiveRecord, you can use the `:key` option to customize it: - -```ruby -class PostSerializer < ActiveModel::Serializer - attributes :id, :body - - # look up :subject on the model, but use +title+ in the JSON - attribute :subject, key: :title - has_many :comments -end -``` - If you would like to add meta information to the outputted JSON, use the `:meta` option: diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index e2a6228b6..6383e9700 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -63,6 +63,9 @@ def to_s class_attribute :_attributes self._attributes = {} + class_attribute :_attribute_types + self._attribute_types = {} + class_attribute :_associations self._associations = {} @@ -86,7 +89,9 @@ def attributes(*attrs) attrs.each do |attr| if Hash === attr - attr.each {|attr_real, key| attribute(attr_real, key: key) } + attr.each do |attr_real, key_or_hash| + attribute(attr_real, (key_or_hash.is_a?(Hash) ? key_or_hash : {key: key_or_hash})) + end else attribute attr end @@ -98,6 +103,8 @@ def attribute(attr, options={}) attr = attr.keys[0] if attr.is_a? Hash + self._attribute_types.update(attr => options[:type]) if options[:type] + unless method_defined?(attr) define_method attr do object.read_attribute_for_serialization(attr.to_sym) @@ -208,11 +215,7 @@ def schema else # Computed attribute (method on serializer or model). We cannot # infer the type, so we put nil, unless specified in the attribute declaration - if name != key - attrs[name] = key - else - attrs[key] = nil - end + attrs[key] = _attribute_types[name] end end diff --git a/test/serializer_test.rb b/test/serializer_test.rb index 5da28d8aa..0b86e948e 100644 --- a/test/serializer_test.rb +++ b/test/serializer_test.rb @@ -73,6 +73,23 @@ def test_attribute_method_with_name_as_serializer_prefix }, hash) end + def test_attributes_method_with_computed_attribute_with_type + user = User.new + user_serializer = UserAttributesWithComputedSerializer.new(user, scope: {}) + + hash = user_serializer.as_json + + assert_equal UserAttributesWithComputedSerializer.schema, { + attributes: { first_name: :string, last_name: :string, full_name: :string }, + associations: {} + } + + assert_equal({ + user_attributes_with_computed: { first_name: "Jose", last_name: "Valim", + full_name: "Jose Valim" } + }, hash) + end + def test_serializer_receives_scope user = User.new user_serializer = UserSerializer.new(user, scope: { scope: true }) @@ -647,7 +664,7 @@ def can_edit; end def can_view; end def drafts; end - attributes :name, :age, { can_edit: :boolean }, :can_view + attributes :name, :age, { can_edit: {type: :boolean} }, :can_view has_many :posts, serializer: Class.new has_many :drafts, serializer: Class.new has_one :parent, serializer: Class.new diff --git a/test/test_fakes.rb b/test/test_fakes.rb index a0a244c13..d18746b1e 100644 --- a/test/test_fakes.rb +++ b/test/test_fakes.rb @@ -100,6 +100,22 @@ def serializable_hash end end +class UserWithColumns < User + def self.columns_hash + { "first_name" => Struct.new(:type).new(:string), "last_name" => Struct.new(:type).new(:string) } + end +end + +class UserAttributesWithComputedSerializer < ActiveModel::Serializer + attributes :first_name, :last_name, full_name: {type: :string} + + def self.model_class; UserWithColumns; end + + def serializable_hash + attributes.merge(:full_name => "#{attributes[:first_name]} #{attributes[:last_name]}") + end +end + class CommentSerializer def initialize(comment, options={}) @object = comment