From 14f51f2ea930130ad695eb86c4f2f13512433271 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 16 Sep 2013 17:11:30 -0300 Subject: [PATCH 01/73] Remove everything, rewrite of AMS starts here --- .travis.yml | 18 - CHANGELOG.md | 113 -- CONTRIBUTING.md | 20 - DESIGN.textile | 586 ------- Gemfile | 6 - Gemfile.edge | 9 - MIT-LICENSE.txt | 21 - README.md | 716 -------- Rakefile | 18 - active_model_serializers.gemspec | 28 - bench/perf.rb | 43 - cruft.md | 19 - lib/action_controller/serialization.rb | 58 - lib/active_model/array_serializer.rb | 65 - lib/active_model/serializable.rb | 49 - lib/active_model/serializer.rb | 475 ----- lib/active_model/serializer/associations.rb | 185 -- lib/active_model/serializer/caching.rb | 37 - lib/active_model/serializer/version.rb | 5 - lib/active_model_serializers.rb | 95 - lib/active_record/serializer_override.rb | 16 - lib/generators/resource_override.rb | 13 - lib/generators/serializer/USAGE | 9 - .../serializer/serializer_generator.rb | 36 - .../serializer/templates/serializer.rb | 8 - test/array_serializer_test.rb | 85 - test/association_test.rb | 592 ------- test/caching_test.rb | 96 -- test/generators_test.rb | 73 - test/no_serialization_scope_test.rb | 34 - test/serialization_scope_name_test.rb | 99 -- test/serialization_test.rb | 394 ----- test/serializer_support_test.rb | 51 - test/serializer_test.rb | 1521 ----------------- test/test_fakes.rb | 202 --- test/test_helper.rb | 41 - 36 files changed, 5836 deletions(-) delete mode 100644 .travis.yml delete mode 100644 CHANGELOG.md delete mode 100644 CONTRIBUTING.md delete mode 100644 DESIGN.textile delete mode 100644 Gemfile delete mode 100644 Gemfile.edge delete mode 100644 MIT-LICENSE.txt delete mode 100644 README.md delete mode 100644 Rakefile delete mode 100644 active_model_serializers.gemspec delete mode 100644 bench/perf.rb delete mode 100644 cruft.md delete mode 100644 lib/action_controller/serialization.rb delete mode 100644 lib/active_model/array_serializer.rb delete mode 100644 lib/active_model/serializable.rb delete mode 100644 lib/active_model/serializer.rb delete mode 100644 lib/active_model/serializer/associations.rb delete mode 100644 lib/active_model/serializer/caching.rb delete mode 100644 lib/active_model/serializer/version.rb delete mode 100644 lib/active_model_serializers.rb delete mode 100644 lib/active_record/serializer_override.rb delete mode 100644 lib/generators/resource_override.rb delete mode 100644 lib/generators/serializer/USAGE delete mode 100644 lib/generators/serializer/serializer_generator.rb delete mode 100644 lib/generators/serializer/templates/serializer.rb delete mode 100644 test/array_serializer_test.rb delete mode 100644 test/association_test.rb delete mode 100644 test/caching_test.rb delete mode 100644 test/generators_test.rb delete mode 100644 test/no_serialization_scope_test.rb delete mode 100644 test/serialization_scope_name_test.rb delete mode 100644 test/serialization_test.rb delete mode 100644 test/serializer_support_test.rb delete mode 100644 test/serializer_test.rb delete mode 100644 test/test_fakes.rb delete mode 100644 test/test_helper.rb diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index a001825b..00000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -language: ruby -rvm: - - 1.9.3 - - 2.0.0 - - jruby-19mode - - rbx-19mode -gemfile: - - Gemfile - - Gemfile.edge -matrix: - allow_failures: - - gemfile: Gemfile.edge -notifications: - email: false - campfire: - on_success: change - rooms: - - secure: "TP0fJ4aqXCRD7CaAgaYW7Pa22j4/uLChdBb59ob/sJvHtfi4Zx3I54xWApmp\nZl1KItFGCV8oQZhQl5hAmHJfJ+1gCNeBvIKwY6TsIyTmyDg1KcJUcJDrwYxO\ntAeYI2PvU5PtKMmpnfnwFQMxL+2nfWJWNzboBCDr4YvoFI+rN+A=" diff --git a/CHANGELOG.md b/CHANGELOG.md deleted file mode 100644 index b4c2913d..00000000 --- a/CHANGELOG.md +++ /dev/null @@ -1,113 +0,0 @@ -# UNRELEASED - -* 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 - of the serialization and a serialize method that returns an object to - be converted to json by the module. This also removes duplicate code. - https://github.com/rails-api/active_model_serializers/commit/6c6bc8872d3b0f040a200854fa5530a775824dbf - -* ActiveModel::Serializer::Caching module was created it enables - Serializers to be able to cache to\_json and serialize calls. This - also helps removing duplicate code. - https://github.com/rails-api/active_model_serializers/commit/3e27110df78696ac48cafd1568f72216f348a188 - -* We got rid of the Association.refine method which generated - subclasses. - https://github.com/rails-api/active_model_serializers/commit/24923722d4f215c7cfcdf553fd16582e28e3801b - -* Associations doesn't know anymore about the source serializer. - That didn't make any sense. - https://github.com/rails-api/active_model_serializers/commit/2252e8fe6dbf45660c6a35f35e2423792f2c3abf - https://github.com/rails-api/active_model_serializers/commit/87eadd09b9a988bc1d9b30d9a501ef7e3fc6bb87 - https://github.com/rails-api/active_model_serializers/commit/79a6e13e8f7fae2eb4f48e83a9633e74beb6739e - -* Passing options[:hash] is not public API of include!. That was - removed. - https://github.com/rails-api/active_model_serializers/commit/5cbf9317051002a32c90c3f995b8b2f126f70d0c - -* ActiveModel::Serializer::Associations::Config is now - ActiveModel::Serializer::Association but it's an internal - thing so shouldn't bother. - ActiveModel::Serializer::Associations::Has\* are now - ActiveModel::Serializer::Association::Has\* and inherit from - ActiveModel::Serializer::Association - https://github.com/rails-api/active_model_serializers/commit/f5de334ddf1f3b9764d914a717311532021785d2 - https://github.com/rails-api/active_model_serializers/commit/3dd422d99e8c57f113880da34f6abe583c4dadf9 - -* serialize\_ids call methods on the corresponding serializer if they - are defined, instead of talking directly with the serialized object. - Serializers are decorators so we shouldn't talk directly with - serialized objects. - -* Array items are not wrapped anymore in root element. - -* Remove support for ruby 1.8 versions. - -* Require rails >= 3.2. - -# VERSION 0.8.1 - -* Fix bug whereby a serializer using 'options' would blow up. - -# VERSION 0.8.0 - -* Attributes can now have optional types. - -* A new DefaultSerializer ensures that POROs behave the same way as ActiveModels. - -* If you wish to override ActiveRecord::Base#to\_Json, you can now require - 'active\_record/serializer\_override'. We don't recommend you do this, but - many users do, so we've left it optional. - -* Fixed a bug where ActionController wouldn't always have MimeResponds. - -* An optinal caching feature allows you to cache JSON & hashes that AMS uses. - Adding 'cached true' to your Serializers will turn on this cache. - -* URL helpers used inside of Engines now work properly. - -* Serializers now can filter attributes with `only` and `except`: - - ``` - UserSerializer.new(user, only: [:first_name, :last_name]) - UserSerializer.new(user, except: :first_name) - ``` - -* Basic Mongoid support. We now include our mixins in the right place. - -* On Ruby 1.8, we now generate an `id` method that properly serializes `id` - columns. See issue #127 for more. - -* Add an alias for `scope` method to be the name of the context. By default - this is `current_user`. The name is automatically set when using - `serialization_scope` in the controller. - -* Pass through serialization options (such as `:include`) when a model - has no serializer defined. - -# VERSION 0.7.0 - -* ```embed_key``` option to allow embedding by attributes other than IDs -* Fix rendering nil with custom serializer -* Fix global ```self.root = false``` -* Add support for specifying the serializer for an association as a String -* Able to specify keys on the attributes method -* Serializer Reloading via ActiveSupport::DescendantsTracker -* Reduce double map to once; Fixes datamapper eager loading. - -# VERSION 0.6.0 - -* Serialize sets properly -* Add root option to ArraySerializer -* Support polymorphic associations -* Support :each_serializer in ArraySerializer -* Add `scope` method to easily access the scope in the serializer -* Fix regression with Rails 3.2.6; add Rails 4 support -* Allow serialization_scope to be disabled with serialization_scope nil -* Array serializer should support pure ruby objects besides serializers - -# VERSION 0.5.0 (May 16, 2012) - -* First tagged version -* Changes generators to always generate an ApplicationSerializer diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 9811ef28..00000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,20 +0,0 @@ -Contributing to AMS -=================== - -First of all, **thank you**! - -Now, for the details: - -Please file issues on the [GitHub Issues -list](https://github.com/rails-api/active_model_serializers/issues). - -Please discuss new features or ask for feedback about a new feature [on -rails-api-core](https://groups.google.com/forum/#!forum/rails-api-core). - -If you want a feature implemented, the best way to get it done is to submit a -pull request that implements it. Tests and docs would be nice. - -Please include a CHANGELOG with all entries that change behavior. - -:heart: :sparkling_heart: :heart: - diff --git a/DESIGN.textile b/DESIGN.textile deleted file mode 100644 index 559982e4..00000000 --- a/DESIGN.textile +++ /dev/null @@ -1,586 +0,0 @@ -This was the original design document for serializers. It is useful mostly for historical purposes as the public API has changed. - -h2. Rails Serializers - -This guide describes how to use Active Model serializers to build non-trivial JSON services in Rails. By reading this guide, you will learn: - -* When to use the built-in Active Model serialization -* When to use a custom serializer for your models -* How to use serializers to encapsulate authorization concerns -* How to create serializer templates to describe the application-wide structure of your serialized JSON -* How to build resources not backed by a single database table for use with JSON services - -This guide covers an intermediate topic and assumes familiarity with Rails conventions. It is suitable for applications that expose a -JSON API that may return different results based on the authorization status of the user. - -h3. Serialization - -By default, Active Record objects can serialize themselves into JSON by using the `to_json` method. This method takes a series of additional -parameter to control which properties and associations Rails should include in the serialized output. - -When building a web application that uses JavaScript to retrieve JSON data from the server, this mechanism has historically been the primary -way that Rails developers prepared their responses. This works great for simple cases, as the logic for serializing an Active Record object -is neatly encapsulated in Active Record itself. - -However, this solution quickly falls apart in the face of serialization requirements based on authorization. For instance, a web service -may choose to expose additional information about a resource only if the user is entitled to access it. In addition, a JavaScript front-end -may want information that is not neatly described in terms of serializing a single Active Record object, or in a different format than. - -In addition, neither the controller nor the model seems like the correct place for logic that describes how to serialize an model object -*for the current user*. - -Serializers solve these problems by encapsulating serialization in an object designed for this purpose. If the default +to_json+ semantics, -with at most a few configuration options serve your needs, by all means continue to use the built-in +to_json+. If you find yourself doing -hash-driven-development in your controllers, juggling authorization logic and other concerns, serializers are for you! - -h3. The Most Basic Serializer - -A basic serializer is a simple Ruby object named after the model class it is serializing. - -
-class PostSerializer
-  def initialize(post, scope)
-    @post, @scope = post, scope
-  end
-
-  def as_json
-    { post: { title: @post.name, body: @post.body } }
-  end
-end
-
- -A serializer is initialized with two parameters: the model object it should serialize and an authorization scope. By default, the -authorization scope is the current user (+current_user+) but you can use a different object if you want. The serializer also -implements an +as_json+ method, which returns a Hash that will be sent to the JSON encoder. - -Rails will transparently use your serializer when you use +render :json+ in your controller. - -
-class PostsController < ApplicationController
-  def show
-    @post = Post.find(params[:id])
-    render json: @post
-  end
-end
-
- -Because +respond_with+ uses +render :json+ under the hood for JSON requests, Rails will automatically use your serializer when -you use +respond_with+ as well. - -h4. +serializable_hash+ - -In general, you will want to implement +serializable_hash+ and +as_json+ to allow serializers to embed associated content -directly. The easiest way to implement these two methods is to have +as_json+ call +serializable_hash+ and insert the root. - -
-class PostSerializer
-  def initialize(post, scope)
-    @post, @scope = post, scope
-  end
-
-  def serializable_hash
-    { title: @post.name, body: @post.body }
-  end
-
-  def as_json
-    { post: serializable_hash }
-  end
-end
-
- -h4. Authorization - -Let's update our serializer to include the email address of the author of the post, but only if the current user has superuser -access. - -
-class PostSerializer
-  def initialize(post, scope)
-    @post, @scope = post, scope
-  end
-
-  def as_json
-    { post: serializable_hash }
-  end
-
-  def serializable_hash
-    hash = post
-    hash.merge!(super_data) if super?
-    hash
-  end
-
-private
-  def post
-    { title: @post.name, body: @post.body }
-  end
-
-  def super_data
-    { email: @post.email }
-  end
-
-  def super?
-    @scope.superuser?
-  end
-end
-
- -h4. Testing - -One benefit of encapsulating our objects this way is that it becomes extremely straight-forward to test the serialization -logic in isolation. - -
-require "ostruct"
-
-class PostSerializerTest < ActiveSupport::TestCase
-  # For now, we use a very simple authorization structure. These tests will need
-  # refactoring if we change that.
-  plebe = OpenStruct.new(super?: false)
-  god   = OpenStruct.new(super?: true)
-
-  post  = OpenStruct.new(title: "Welcome to my blog!", body: "Blah blah blah", email: "tenderlove@gmail.com")
-
-  test "a regular user sees just the title and body" do
-    json = PostSerializer.new(post, plebe).to_json
-    hash = JSON.parse(json)
-
-    assert_equal post.title, hash.delete("title")
-    assert_equal post.body, hash.delete("body")
-    assert_empty hash
-  end
-
-  test "a superuser sees the title, body and email" do
-    json = PostSerializer.new(post, god).to_json
-    hash = JSON.parse(json)
-
-    assert_equal post.title, hash.delete("title")
-    assert_equal post.body, hash.delete("body")
-    assert_equal post.email, hash.delete("email")
-    assert_empty hash
-  end
-end
-
- -It's important to note that serializer objects define a clear interface specifically for serializing an existing object. -In this case, the serializer expects to receive a post object with +name+, +body+ and +email+ attributes and an authorization -scope with a +super?+ method. - -By defining a clear interface, it's must easier to ensure that your authorization logic is behaving correctly. In this case, -the serializer doesn't need to concern itself with how the authorization scope decides whether to set the +super?+ flag, just -whether it is set. In general, you should document these requirements in your serializer files and programatically via tests. -The documentation library +YARD+ provides excellent tools for describing this kind of requirement: - -
-class PostSerializer
-  # @param [~body, ~title, ~email] post the post to serialize
-  # @param [~super] scope the authorization scope for this serializer
-  def initialize(post, scope)
-    @post, @scope = post, scope
-  end
-
-  # ...
-end
-
- -h3. Attribute Sugar - -To simplify this process for a number of common cases, Rails provides a default superclass named +ActiveModel::Serializer+ -that you can use to implement your serializers. - -For example, you will sometimes want to simply include a number of existing attributes from the source model into the outputted -JSON. In the above example, the +title+ and +body+ attributes were always included in the JSON. Let's see how to use -+ActiveModel::Serializer+ to simplify our post serializer. - -
-class PostSerializer < ActiveModel::Serializer
-  attributes :title, :body
-
-  def serializable_hash
-    hash = attributes
-    hash.merge!(super_data) if super?
-    hash
-  end
-
-private
-  def super_data
-    { email: @post.email }
-  end
-
-  def super?
-    @scope.superuser?
-  end
-end
-
- -First, we specified the list of included attributes at the top of the class. This will create an instance method called -+attributes+ that extracts those attributes from the post model. - -NOTE: Internally, +ActiveModel::Serializer+ uses +read_attribute_for_serialization+, which defaults to +read_attribute+, which defaults to +send+. So if you're rolling your own models for use with the serializer, you can use simple Ruby accessors for your attributes if you like. - -Next, we use the attributes method in our +serializable_hash+ method, which allowed us to eliminate the +post+ method we hand-rolled -earlier. We could also eliminate the +as_json+ method, as +ActiveModel::Serializer+ provides a default +as_json+ method for -us that calls our +serializable_hash+ method and inserts a root. But we can go a step further! - -
-class PostSerializer < ActiveModel::Serializer
-  attributes :title, :body
-
-private
-  def attributes
-    hash = super
-    hash.merge!(email: post.email) if super?
-    hash
-  end
-
-  def super?
-    @scope.superuser?
-  end
-end
-
- -The superclass provides a default +initialize+ method as well as a default +serializable_hash+ method, which uses -+attributes+. We can call +super+ to get the hash based on the attributes we declared, and then add in any additional -attributes we want to use. - -NOTE: +ActiveModel::Serializer+ will create an accessor matching the name of the current class for the resource you pass in. In this case, because we have defined a PostSerializer, we can access the resource with the +post+ accessor. - -h3. Associations - -In most JSON APIs, you will want to include associated objects with your serialized object. In this case, let's include -the comments with the current post. - -
-class PostSerializer < ActiveModel::Serializer
-  attributes :title, :body
-  has_many :comments
-
-private
-  def attributes
-    hash = super
-    hash.merge!(email: post.email) if super?
-    hash
-  end
-
-  def super?
-    @scope.superuser?
-  end
-end
-
- -The default +serializable_hash+ method will include the comments as embedded objects inside the post. - -
-{
-  post: {
-    title: "Hello Blog!",
-    body: "This is my first post. Isn't it fabulous!",
-    comments: [
-      {
-        title: "Awesome",
-        body: "Your first post is great"
-      }
-    ]
-  }
-}
-
- -Rails uses the same logic to generate embedded serializations as it does when you use +render :json+. In this case, -because you didn't define a +CommentSerializer+, Rails used the default +as_json+ on your comment object. - -If you define a serializer, Rails will automatically instantiate it with the existing authorization scope. - -
-class CommentSerializer
-  def initialize(comment, scope)
-    @comment, @scope = comment, scope
-  end
-
-  def serializable_hash
-    { title: @comment.title }
-  end
-
-  def as_json
-    { comment: serializable_hash }
-  end
-end
-
- -If we define the above comment serializer, the outputted JSON will change to: - -
-{
-  post: {
-    title: "Hello Blog!",
-    body: "This is my first post. Isn't it fabulous!",
-    comments: [{ title: "Awesome" }]
-  }
-}
-
- -Let's imagine that our comment system allows an administrator to kill a comment, and we only want to allow -users to see the comments they're entitled to see. By default, +has_many :comments+ will simply use the -+comments+ accessor on the post object. We can override the +comments+ accessor to limit the comments used -to just the comments we want to allow for the current user. - -
-class PostSerializer < ActiveModel::Serializer
-  attributes :title. :body
-  has_many :comments
-
-private
-  def attributes
-    hash = super
-    hash.merge!(email: post.email) if super?
-    hash
-  end
-
-  def comments
-    post.comments_for(scope)
-  end
-
-  def super?
-    @scope.superuser?
-  end
-end
-
- -+ActiveModel::Serializer+ will still embed the comments, but this time it will use just the comments -for the current user. - -NOTE: The logic for deciding which comments a user should see still belongs in the model layer. In general, you should encapsulate concerns that require making direct Active Record queries in scopes or public methods on your models. - -h4. Modifying Associations - -You can also rename associations if required. Say for example you have an association that -makes sense to be named one thing in your code, but another when data is serialized. -You can use the option to specify a different name for an association. -Here is an example: - -
-class UserSerializer < ActiveModel::Serializer
-  has_many :followed_posts, key: :posts
-  has_one :owned_account, key: :account
-end
-
- -Using the :key without a :serializer option will use implicit detection -to determine a serializer. In this example, you'd have to define two classes: PostSerializer -and AccountSerializer. You can also add the :serializer option -to set it explicitly: - -
-class UserSerializer < ActiveModel::Serializer
-  has_many :followed_posts, key: :posts, serializer: CustomPostSerializer
-  has_one :owne_account, key: :account, serializer: PrivateAccountSerializer
-end
-
- -h3. Customizing Associations - -Not all front-ends expect embedded documents in the same form. In these cases, you can override the -default +serializable_hash+, and use conveniences provided by +ActiveModel::Serializer+ to avoid having to -build up the hash manually. - -For example, let's say our front-end expects the posts and comments in the following format: - -
-{
-  post: {
-    id: 1
-    title: "Hello Blog!",
-    body: "This is my first post. Isn't it fabulous!",
-    comments: [1,2]
-  },
-  comments: [
-    {
-      id: 1
-      title: "Awesome",
-      body: "Your first post is great"
-    },
-    {
-      id: 2
-      title: "Not so awesome",
-      body: "Why is it so short!"
-    }
-  ]
-}
-
- -We could achieve this with a custom +as_json+ method. We will also need to define a serializer for comments. - -
-class CommentSerializer < ActiveModel::Serializer
-  attributes :id, :title, :body
-
-  # define any logic for dealing with authorization-based attributes here
-end
-
-class PostSerializer < ActiveModel::Serializer
-  attributes :title, :body
-  has_many :comments
-
-  def as_json
-    { post: serializable_hash }.merge!(associations)
-  end
-
-  def serializable_hash
-    post_hash = attributes
-    post_hash.merge!(association_ids)
-    post_hash
-  end
-
-private
-  def attributes
-    hash = super
-    hash.merge!(email: post.email) if super?
-    hash
-  end
-
-  def comments
-    post.comments_for(scope)
-  end
-
-  def super?
-    @scope.superuser?
-  end
-end
-
- -Here, we used two convenience methods: +associations+ and +association_ids+. The first, -+associations+, creates a hash of all of the define associations, using their defined -serializers. The second, +association_ids+, generates a hash whose key is the association -name and whose value is an Array of the association's keys. - -The +association_ids+ helper will use the overridden version of the association, so in -this case, +association_ids+ will only include the ids of the comments provided by the -+comments+ method. - - -h3. Special Association Serializers - -So far, associations defined in serializers use either the +as_json+ method on the model -or the defined serializer for the association type. Sometimes, you may want to serialize -associated models differently when they are requested as part of another resource than -when they are requested on their own. - -For instance, we might want to provide the full comment when it is requested directly, -but only its title when requested as part of the post. To achieve this, you can define -a serializer for associated objects nested inside the main serializer. - -
-class PostSerializer < ActiveModel::Serializer
-  class CommentSerializer < ActiveModel::Serializer
-    attributes :id, :title
-  end
-
-  # same as before
-  # ...
-end
-
- -In other words, if a +PostSerializer+ is trying to serialize comments, it will first -look for +PostSerializer::CommentSerializer+ before falling back to +CommentSerializer+ -and finally +comment.as_json+. - -h3. Overriding the Defaults - -h4. Authorization Scope - -By default, the authorization scope for serializers is +:current_user+. This means -that when you call +render json: @post+, the controller will automatically call -its +current_user+ method and pass that along to the serializer's initializer. - -If you want to change that behavior, simply use the +serialization_scope+ class -method. - -
-class PostsController < ApplicationController
-  serialization_scope :current_app
-end
-
- -You can also implement an instance method called (no surprise) +serialization_scope+, -which allows you to define a dynamic authorization scope based on the current request. - -WARNING: If you use different objects as authorization scopes, make sure that they all implement whatever interface you use in your serializers to control what the outputted JSON looks like. - -h3. Using Serializers Outside of a Request - -The serialization API encapsulates the concern of generating a JSON representation of -a particular model for a particular user. As a result, you should be able to easily use -serializers, whether you define them yourself or whether you use +ActiveModel::Serializer+ -outside a request. - -For instance, if you want to generate the JSON representation of a post for a user outside -of a request: - -
-user = get_user # some logic to get the user in question
-PostSerializer.new(post, user).to_json # reliably generate JSON output
-
- -If you want to generate JSON for an anonymous user, you should be able to use whatever -technique you use in your application to generate anonymous users outside of a request. -Typically, that means creating a new user and not saving it to the database: - -
-user = User.new # create a new anonymous user
-PostSerializer.new(post, user).to_json
-
- -In general, the better you encapsulate your authorization logic, the more easily you -will be able to use the serializer outside of the context of a request. For instance, -if you use an authorization library like Cancan, which uses a uniform +user.can?(action, model)+, -the authorization interface can very easily be replaced by a plain Ruby object for -testing or usage outside the context of a request. - -h3. Collections - -So far, we've talked about serializing individual model objects. By default, Rails -will serialize collections, including when using the +associations+ helper, by -looping over each element of the collection, calling +serializable_hash+ on the element, -and then grouping them by their type (using the plural version of their class name -as the root). - -For example, an Array of post objects would serialize as: - -
-{
-  posts: [
-    {
-      title: "FIRST POST!",
-      body: "It's my first pooooost"
-    },
-    { title: "Second post!",
-      body: "Zomg I made it to my second post"
-    }
-  ]
-}
-
- -If you want to change the behavior of serialized Arrays, you need to create -a custom Array serializer. - -
-class ArraySerializer < ActiveModel::ArraySerializer
-  def serializable_array
-    serializers.map do |serializer|
-      serializer.serializable_hash
-    end
-  end
-
-  def as_json
-    hash = { root => serializable_array }
-    hash.merge!(associations)
-    hash
-  end
-end
-
- -When generating embedded associations using the +associations+ helper inside a -regular serializer, it will create a new ArraySerializer with the -associated content and call its +serializable_array+ method. In this case, those -embedded associations will not recursively include associations. - -When generating an Array using +render json: posts+, the controller will invoke -the +as_json+ method, which will include its associations and its root. diff --git a/Gemfile b/Gemfile deleted file mode 100644 index 79ab2b93..00000000 --- a/Gemfile +++ /dev/null @@ -1,6 +0,0 @@ -source 'https://rubygems.org' - -# Specify gem dependencies in active_model_serializers.gemspec -gemspec - -gem "coveralls", require: false diff --git a/Gemfile.edge b/Gemfile.edge deleted file mode 100644 index d4e1c028..00000000 --- a/Gemfile.edge +++ /dev/null @@ -1,9 +0,0 @@ -source 'http://rubygems.org' - -gemspec - -gem 'rails', github: 'rails/rails' - -# Current dependencies of edge rails -gem 'journey', github: 'rails/journey' -gem 'activerecord-deprecated_finders' , github: 'rails/activerecord-deprecated_finders' diff --git a/MIT-LICENSE.txt b/MIT-LICENSE.txt deleted file mode 100644 index 75e6db1a..00000000 --- a/MIT-LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (c) 2011-2012 José Valim & Yehuda Katz - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - diff --git a/README.md b/README.md deleted file mode 100644 index bb7d8871..00000000 --- a/README.md +++ /dev/null @@ -1,716 +0,0 @@ -[![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 - -The purpose of `ActiveModel::Serializers` is to provide an object to -encapsulate serialization of `ActiveModel` objects, including `ActiveRecord` -objects. - -Serializers know about both a model and the `current_user`, so you can -customize serialization based upon whether a user is authorized to see the -content. - -In short, **serializers replace hash-driven development with object-oriented -development.** - -# Installing - -The easiest way to install `ActiveModel::Serializers` is to add it to your -`Gemfile`: - -```ruby -gem "active_model_serializers" -``` - -Then, install it on the command line: - -``` -$ bundle install -``` - -#### 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: - -```ruby -gem "active_model_serializers", "~> 0.8.0" -``` - - -# Creating a Serializer - -The easiest way to create a new serializer is to generate a new resource, which -will generate a serializer at the same time: - -``` -$ rails g resource post title:string body:string -``` - -This will generate a serializer in `app/serializers/post_serializer.rb` for -your new model. You can also generate a serializer for an existing model with -the serializer generator: - -``` -$ rails g serializer post -``` - -### Support for POROs and other ORMs. - -Currently `ActiveModel::Serializers` adds serialization support to all models -that descend from `ActiveRecord` or include `Mongoid::Document`. If you are -using another ORM, or if you are using objects that are `ActiveModel` -compliant but do not descend from `ActiveRecord` or include -`Mongoid::Document`, you must add an include statement for -`ActiveModel::SerializerSupport` to make models serializable. If you -also want to make collections serializable, you should include -`ActiveModel::ArraySerializerSupport` into your ORM's -relation/criteria class. - -# ActiveModel::Serializer - -All new serializers descend from ActiveModel::Serializer - -# render :json - -In your controllers, when you use `render :json`, Rails will now first search -for a serializer for the object and use it if available. - -```ruby -class PostsController < ApplicationController - def show - @post = Post.find(params[:id]) - render json: @post - end -end -``` - -In this case, Rails will look for a serializer named `PostSerializer`, and if -it exists, use it to serialize the `Post`. - -This also works with `respond_with`, which uses `to_json` under the hood. Also -note that any options passed to `render :json` will be passed to your -serializer and available as `@options` inside. - -To specify a custom serializer for an object, there are 2 options: - -#### 1. Specify the serializer in your model: - -```ruby -class Post < ActiveRecord::Base - def active_model_serializer - FancyPostSerializer - end -end -``` - -#### 2. Specify the serializer when you render the object: - -```ruby -render json: @post, serializer: FancyPostSerializer -``` - -## Arrays - -In your controllers, when you use `render :json` for an array of objects, AMS will -use `ActiveModel::ArraySerializer` (included in this project) as the base serializer, -and the individual `Serializer` for the objects contained in that array. - -```ruby -class PostSerializer < ActiveModel::Serializer - attributes :title, :body -end - -class PostsController < ApplicationController - def index - @posts = Post.all - render json: @posts - end -end -``` - -Given the example above, the index action will return - -```json -{ - "posts": - [ - { "title": "Post 1", "body": "Hello!" }, - { "title": "Post 2", "body": "Goodbye!" } - ] -} -``` - -By default, the root element is the name of the controller. For example, `PostsController` -generates a root element "posts". To change it: - -```ruby -render json: @posts, root: "some_posts" -``` - -You may disable the root element for arrays at the top level, which will result in -more concise json. See the next section for ways on how to do this. Disabling the -root element of the array with any of those methods will produce - -```json -[ - { "title": "Post 1", "body": "Hello!" }, - { "title": "Post 2", "body": "Goodbye!" } -] -``` - -To specify a custom serializer for the items within an array: - -```ruby -render json: @posts, each_serializer: FancyPostSerializer -``` - -## Disabling the root element - -You have 4 options to disable the root element, each with a slightly different scope: - -#### 1. Disable root globally for all, or per class - -In an initializer: - -```ruby -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 -``` - -#### 2. Disable root per render call in your controller - -```ruby -render json: @posts, root: false -``` - -#### 3. Subclass the serializer, and specify using it - -```ruby -class CustomArraySerializer < ActiveModel::ArraySerializer - self.root = false -end - -# controller: -render json: @posts, serializer: CustomArraySerializer -``` - -#### 4. Define default_serializer_options in your controller - -If you define `default_serializer_options` method in your controller, -all serializers in actions of this controller and it's children will use them. -One of the options may be `root: false` - -```ruby -def default_serializer_options - { - root: false - } -end -``` - -## Getting the old version - -If you find that your project is already relying on the old rails to_json -change `render :json` to `render json: @your_object.to_json`. - -# Attributes and Associations - -Once you have a serializer, you can specify which attributes and associations -you would like to include in the serialized form. - -```ruby -class PostSerializer < ActiveModel::Serializer - attributes :id, :title, :body - has_many :comments -end -``` - -## Attributes - -For specified attributes, a serializer will look up the attribute on the -object you passed to `render :json`. It uses -`read_attribute_for_serialization`, which `ActiveRecord` objects implement as a -regular attribute lookup. - -Before looking up the attribute on the object, a serializer will check for the -presence of a method with the name of the attribute. This allows serializers to -include properties beyond the simple attributes of the model. For example: - -```ruby -class PersonSerializer < ActiveModel::Serializer - attributes :first_name, :last_name, :full_name - - def full_name - "#{object.first_name} #{object.last_name}" - end -end -``` - -Within a serializer's methods, you can access the object being -serialized as `object`. - -Since this shadows any attribute named `object`, you can include them through `object.object`. For example: - -```ruby -class VersionSerializer < ActiveModel::Serializer - attribute :version_object, key: :object - - def version_object - object.object - end -end -``` - -You can also access the `current_user` method, which provides an -authorization context to your serializer. By default, the context -is the current user of your application, but this -[can be customized](#customizing-scope). - -Serializers will check for the presence of a method named -`include_[ATTRIBUTE]?` to determine whether a particular attribute should be -included in the output. This is typically used to customize output -based on `current_user`. For example: - -```ruby -class PostSerializer < ActiveModel::Serializer - attributes :id, :title, :body, :author - - def include_author? - current_user.admin? - end -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} - - def full_name - "#{object.first_name} #{object.last_name}" - end -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: - -```ruby -render json: @posts, serializer: CustomArraySerializer, meta: {total: 10} -``` - -The above usage of `:meta` will produce the following: - -```json -{ - "meta": { "total": 10 }, - "posts": [ - { "title": "Post 1", "body": "Hello!" }, - { "title": "Post 2", "body": "Goodbye!" } - ] -} -``` - -If you would like to change the meta key name you can use the `:meta_key` option: - -```ruby -render json: @posts, serializer: CustomArraySerializer, meta: {total: 10}, meta_key: 'meta_object' -``` - -The above usage of `:meta_key` will produce the following: - -```json -{ - "meta_object": { "total": 10 }, - "posts": [ - { "title": "Post 1", "body": "Hello!" }, - { "title": "Post 2", "body": "Goodbye!" } - ] -} -``` - -If you would like direct, low-level control of attribute serialization, you can -completely override the `attributes` method to return the hash you need: - -```ruby -class PersonSerializer < ActiveModel::Serializer - attributes :first_name, :last_name - - def attributes - hash = super - if current_user.admin? - hash["ssn"] = object.ssn - hash["secret"] = object.mothers_maiden_name - end - hash - end -end -``` - -## Associations - -For specified associations, the serializer will look up the association and -then serialize each element of the association. For instance, a `has_many -:comments` association will create a new `CommentSerializer` for each comment -and use it to serialize the comment. - -By default, serializers simply look up the association on the original object. -You can customize this behavior by implementing a method with the name of the -association and returning a different Array. Often, you will do this to -customize the objects returned based on the current user. - -```ruby -class PostSerializer < ActiveModel::Serializer - attributes :id, :title, :body - has_many :comments - - # only let the user see comments he created. - def comments - object.comments.where(created_by: current_user) - end -end -``` - -As with attributes, you can change the JSON key that the serializer should -use for a particular association. - -```ruby -class PostSerializer < ActiveModel::Serializer - attributes :id, :title, :body - - # look up comments, but use +my_comments+ as the key in JSON - has_many :comments, key: :my_comments -end -``` - -Also, as with attributes, serializers will check for the presence -of a method named `include_[ASSOCIATION]?` to determine whether a particular association -should be included in the output. For example: - -```ruby -class PostSerializer < ActiveModel::Serializer - attributes :id, :title, :body - has_many :comments - - def include_comments? - !object.comments_disabled? - end -end -``` - -If you would like lower-level control of association serialization, you can -override `include_associations!` to specify which associations should be included: - -```ruby -class PostSerializer < ActiveModel::Serializer - attributes :id, :title, :body - has_one :author - has_many :comments - - def include_associations! - include! :author if current_user.admin? - include! :comments unless object.comments_disabled? - end -end -``` - -You may also use the `:serializer` option to specify a custom serializer class and the `:polymorphic` option to specify an association that is polymorphic (STI), e.g.: - -```ruby - has_many :comments, serializer: CommentShortSerializer - has_one :reviewer, polymorphic: true -``` - -Serializers are only concerned with multiplicity, and not ownership. `belongs_to` ActiveRecord associations can be included using `has_one` in your serializer. - -## Embedding Associations - -By default, associations will be embedded inside the serialized object. So if -you have a post, the outputted JSON will look like: - -```json -{ - "post": { - "id": 1, - "title": "New post", - "body": "A body!", - "comments": [ - { "id": 1, "body": "what a dumb post" } - ] - } -} -``` - -This is convenient for simple use-cases, but for more complex clients, it is -better to supply an Array of IDs for the association. This makes your API more -flexible from a performance standpoint and avoids wasteful duplication. - -To embed IDs instead of associations, simply use the `embed` class method: - -```ruby -class PostSerializer < ActiveModel::Serializer - embed :ids - - attributes :id, :title, :body - has_many :comments -end -``` - -Now, any associations will be supplied as an Array of IDs: - -```json -{ - "post": { - "id": 1, - "title": "New post", - "body": "A body!", - "comment_ids": [ 1, 2, 3 ] - } -} -``` - -Alternatively, you can choose to embed only the ids or the associated objects per association: - -```ruby -class PostSerializer < ActiveModel::Serializer - attributes :id, :title, :body - - has_many :comments, embed: :objects - has_many :tags, embed: :ids -end -``` - -The JSON will look like this: - -```json -{ - "post": { - "id": 1, - "title": "New post", - "body": "A body!", - "comments": [ - { "id": 1, "body": "what a dumb post" } - ], - "tag_ids": [ 1, 2, 3 ] - } -} -``` - -In addition to supplying an Array of IDs, you may want to side-load the data -alongside the main object. This makes it easier to process the entire package -of data without having to recursively scan the tree looking for embedded -information. It also ensures that associations that are shared between several -objects (like tags), are only delivered once for the entire payload. - -You can specify that the data be included like this: - -```ruby -class PostSerializer < ActiveModel::Serializer - embed :ids, include: true - - attributes :id, :title, :body - has_many :comments -end -``` - -Assuming that the comments also `has_many :tags`, you will get a JSON like -this: - -```json -{ - "post": { - "id": 1, - "title": "New post", - "body": "A body!", - "comment_ids": [ 1, 2 ] - }, - "comments": [ - { "id": 1, "body": "what a dumb post", "tag_ids": [ 1, 2 ] }, - { "id": 2, "body": "i liked it", "tag_ids": [ 1, 3 ] }, - ], - "tags": [ - { "id": 1, "name": "short" }, - { "id": 2, "name": "whiny" }, - { "id": 3, "name": "happy" } - ] -} -``` - -You can also specify a different root for the embedded objects than the key -used to reference them: - -```ruby -class PostSerializer < ActiveModel::Serializer - embed :ids, include: true - - attributes :id, :title, :body - has_many :comments, key: :comment_ids, root: :comment_objects -end -``` - -This would generate JSON that would look like this: - -```json -{ - "post": { - "id": 1, - "title": "New post", - "body": "A body!", - "comment_ids": [ 1 ] - }, - "comment_objects": [ - { "id": 1, "body": "what a dumb post" } - ] -} -``` - -You can also specify a different attribute to use rather than the ID of the -objects: - -```ruby -class PostSerializer < ActiveModel::Serializer - embed :ids, include: true - - attributes :id, :title, :body - has_many :comments, embed_key: :external_id -end -``` - -This would generate JSON that would look like this: - -```json -{ - "post": { - "id": 1, - "title": "New post", - "body": "A body!", - "comment_ids": [ "COMM001" ] - }, - "comments": [ - { "id": 1, "external_id": "COMM001", "body": "what a dumb post" } - ] -} -``` - -**NOTE**: The `embed :ids` mechanism is primary useful for clients that process -data in bulk and load it into a local store. For these clients, the ability to -easily see all of the data per type, rather than having to recursively scan the -data looking for information, is extremely useful. - -If you are mostly working with the data in simple scenarios and manually making -Ajax requests, you probably just want to use the default embedded behavior. - -## Customizing Scope - -In a serializer, `current_user` is the current authorization scope which the controller -provides to the serializer when you call `render :json`. By default, this is -`current_user`, but can be customized in your controller by calling -`serialization_scope`: - -```ruby -class ApplicationController < ActionController::Base - serialization_scope :current_admin -end -``` - -The above example will also change the scope name from `current_user` to -`current_admin`. - -Please note that, until now, `serialization_scope` doesn't accept a second -object with options for specifying which actions should or should not take a -given scope in consideration. - -To be clear, it's not possible, yet, to do something like this: - -```ruby -class SomeController < ApplicationController - serialization_scope :current_admin, except: [:index, :show] -end -``` - -So, in order to have a fine grained control of what each action should take in -consideration for its scope, you may use something like this: - -```ruby -class CitiesController < ApplicationController - serialization_scope nil - - def index - @cities = City.all - - render json: @cities, each_serializer: CitySerializer - end - - def show - @city = City.find(params[:id]) - - render json: @city, scope: current_admin, scope_name: :current_admin - end -end -``` - -Assuming that the `current_admin` method needs to make a query in the database -for the current user, the advantage of this approach is that, by setting -`serialization_scope` to `nil`, the `index` action no longer will need to make -that query, only the `show` action will. - -## Caching - -To cache a serializer, call `cached` and define a `cache_key` method: - -```ruby -class PostSerializer < ActiveModel::Serializer - cached # enables caching for this serializer - - attributes :title, :body - - def cache_key - [object, current_user] - end -end -``` - -The caching interface uses `Rails.cache` under the hood. - -# Design and Implementation - -## Keep it Simple - -ActiveModel::Serializers is capable of producing complex JSON views/large object -trees, and it may be tempting to design in this way so that your client can make -fewer requests to get data and so that related querying can be optimized. -However, keeping things simple in your serializers and controllers may -significantly reduce complexity and maintenance over the long-term development -of your application. Please consider reducing the complexity of the JSON views -you provide via the serializers as you build out your application, so that -controllers/services can be more easily reused without a lot of complexity -later. - -## Performance - -As you develop your controllers or other code that utilizes serializers, try to -avoid n+1 queries by ensuring that data loads in an optimal fashion, e.g. if you -are using ActiveRecord, you might want to use query includes or joins as needed -to make the data available that the serializer(s) need. diff --git a/Rakefile b/Rakefile deleted file mode 100644 index 8c5bd75a..00000000 --- a/Rakefile +++ /dev/null @@ -1,18 +0,0 @@ -#!/usr/bin/env rake -require "bundler/gem_tasks" -require "rake/testtask" - -desc 'Run tests' -Rake::TestTask.new(:test) do |t| - t.libs << 'lib' - t.libs << 'test' - t.pattern = 'test/**/*_test.rb' - t.verbose = true -end - -desc 'Benchmark' -task :bench do - load 'bench/perf.rb' -end - -task default: :test diff --git a/active_model_serializers.gemspec b/active_model_serializers.gemspec deleted file mode 100644 index 9b551fad..00000000 --- a/active_model_serializers.gemspec +++ /dev/null @@ -1,28 +0,0 @@ -# -*- encoding: utf-8 -*- - -$:.unshift File.expand_path("../lib", __FILE__) -require "active_model/serializer/version" - -Gem::Specification.new do |gem| - gem.authors = ["José Valim", "Yehuda Katz"] - gem.email = ["jose.valim@gmail.com", "wycats@gmail.com"] - gem.description = %q{Making it easy to serialize models for client-side use} - gem.summary = %q{Bringing consistency and object orientation to model serialization. Works great for client-side MVC frameworks!} - gem.homepage = "https://github.com/rails-api/active_model_serializers" - - gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } - gem.files = `git ls-files`.split("\n") - gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") - gem.name = "active_model_serializers" - gem.require_paths = ["lib"] - gem.version = ActiveModel::Serializer::VERSION - - gem.required_ruby_version = ">= 1.9.3" - - gem.add_dependency "activemodel", ">= 3.2" - - gem.add_development_dependency "rails", ">= 3.2" - gem.add_development_dependency "pry" - gem.add_development_dependency "simplecov" - gem.add_development_dependency "coveralls" -end diff --git a/bench/perf.rb b/bench/perf.rb deleted file mode 100644 index ea668d56..00000000 --- a/bench/perf.rb +++ /dev/null @@ -1,43 +0,0 @@ -require "rubygems" -require "bundler/setup" -require "active_model_serializers" -require "active_support/json" -require "benchmark" - -class User < Struct.new(:id,:name,:age,:about) - include ActiveModel::SerializerSupport - - def fast_hash - h = { - id: read_attribute_for_serialization(:id), - name: read_attribute_for_serialization(:name), - about: read_attribute_for_serialization(:about) - } - h[:age] = read_attribute_for_serialization(:age) if age > 18 - h - end -end - -class UserSerializer < ActiveModel::Serializer - attributes :id, :name, :age, :about - - def include_age? - object.age > 18 - end -end - - - -u = User.new(1, "sam", 10, "about") -s = UserSerializer.new(u) - -n = 100000 - -Benchmark.bmbm {|x| - x.report("init") { n.times { UserSerializer.new(u) } } - x.report("fast_hash") { n.times { u.fast_hash } } - x.report("attributes") { n.times { UserSerializer.new(u).attributes } } - x.report("serializable_hash") { n.times { UserSerializer.new(u).serializable_hash } } -} - - diff --git a/cruft.md b/cruft.md deleted file mode 100644 index 22cbf7d3..00000000 --- a/cruft.md +++ /dev/null @@ -1,19 +0,0 @@ -As of Ruby 1.9.3, it is impossible to dynamically generate a Symbol -through interpolation without generating garbage. Theoretically, Ruby -should be able to take care of this by building up the String in C and -interning the C String. - -Because of this, we avoid generating dynamic Symbols at runtime. For -example, instead of generating the instrumentation event dynamically, we -have a constant with a Hash of events: - -```ruby -INSTRUMENT = { - serialize: :"serialize.serializer", - associations: :"associations.serializer" -} -``` - -If Ruby ever fixes this issue and avoids generating garbage with dynamic -symbols, this code can be removed. - diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb deleted file mode 100644 index 6a67275c..00000000 --- a/lib/action_controller/serialization.rb +++ /dev/null @@ -1,58 +0,0 @@ -module ActionController - # Action Controller Serialization - # - # Overrides render :json to check if the given object implements +active_model_serializer+ - # as a method. If so, use the returned serializer instead of calling +to_json+ on the object. - # - # This module also provides a serialization_scope method that allows you to configure the - # +serialization_scope+ of the serializer. Most apps will likely set the +serialization_scope+ - # to the current user: - # - # class ApplicationController < ActionController::Base - # serialization_scope :current_user - # end - # - # If you need more complex scope rules, you can simply override the serialization_scope: - # - # class ApplicationController < ActionController::Base - # private - # - # def serialization_scope - # current_user - # end - # end - # - module Serialization - extend ActiveSupport::Concern - - include ActionController::Renderers - - included do - class_attribute :_serialization_scope - self._serialization_scope = :current_user - end - - def serialization_scope - send(_serialization_scope) if _serialization_scope && respond_to?(_serialization_scope, true) - end - - def default_serializer_options - end - - def _render_option_json(resource, options) - json = ActiveModel::Serializer.build_json(self, resource, options) - - if json - super(json, options) - else - super - end - end - - module ClassMethods - def serialization_scope(scope) - self._serialization_scope = scope - end - end - end -end diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb deleted file mode 100644 index e752c812..00000000 --- a/lib/active_model/array_serializer.rb +++ /dev/null @@ -1,65 +0,0 @@ -require 'active_model/serializable' -require 'active_model/serializer/caching' -require "active_support/core_ext/class/attribute" -require 'active_support/dependencies' -require 'active_support/descendants_tracker' - -module ActiveModel - # Active Model Array Serializer - # - # Serializes an Array, checking if each element implements - # the +active_model_serializer+ method. - # - # To disable serialization of root elements: - # - # ActiveModel::ArraySerializer.root = false - # - class ArraySerializer - extend ActiveSupport::DescendantsTracker - - include ActiveModel::Serializable - include ActiveModel::Serializer::Caching - - attr_reader :object, :options - - class_attribute :root - - class_attribute :cache - class_attribute :perform_caching - - class << self - # set perform caching like root - def cached(value = true) - self.perform_caching = value - end - end - - def initialize(object, options={}) - @object = object - @options = options - end - - def serialize_object - serializable_array - end - - def serializable_array - object.map do |item| - if options.has_key? :each_serializer - serializer = options[:each_serializer] - elsif item.respond_to?(:active_model_serializer) - serializer = item.active_model_serializer - end - serializer ||= DefaultSerializer - - serializable = serializer.new(item, options.merge(root: nil)) - - if serializable.respond_to?(:serializable_hash) - serializable.serializable_hash - else - serializable.as_json - end - end - end - end -end diff --git a/lib/active_model/serializable.rb b/lib/active_model/serializable.rb deleted file mode 100644 index 7122ae20..00000000 --- a/lib/active_model/serializable.rb +++ /dev/null @@ -1,49 +0,0 @@ -require 'active_support/core_ext/object/to_json' - -module ActiveModel - # Enable classes to Classes including this module to serialize themselves by implementing a serialize method and an options method. - # - # Example: - # - # require 'active_model_serializers' - # - # class MySerializer - # include ActiveModel::Serializable - # - # def initialize - # @options = {} - # end - # - # attr_reader :options - # - # def serialize - # { a: 1 } - # end - # end - # - # puts MySerializer.new.to_json - module Serializable - def as_json(args={}) - if root = args[:root] || options[:root] - options[:hash] = hash = {} - options[:unique_values] = {} - - hash.merge!(root => serialize) - include_meta hash - hash - else - serialize - end - end - - private - - def include_meta(hash) - hash[meta_key] = options[:meta] if options.has_key?(:meta) - end - - def meta_key - options[:meta_key].try(:to_sym) || :meta - end - end -end diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb deleted file mode 100644 index e2a6228b..00000000 --- a/lib/active_model/serializer.rb +++ /dev/null @@ -1,475 +0,0 @@ -require 'active_model/serializable' -require 'active_model/serializer/caching' -require "active_support/core_ext/class/attribute" -require "active_support/core_ext/module/anonymous" -require 'active_support/dependencies' -require 'active_support/descendants_tracker' - -module ActiveModel - # Active Model Serializer - # - # Provides a basic serializer implementation that allows you to easily - # control how a given object is going to be serialized. On initialization, - # it expects two objects as arguments, a resource and options. For example, - # one may do in a controller: - # - # PostSerializer.new(@post, scope: current_user).to_json - # - # The object to be serialized is the +@post+ and the current user is passed - # in for authorization purposes. - # - # We use the scope to check if a given attribute should be serialized or not. - # For example, some attributes may only be returned if +current_user+ is the - # author of the post: - # - # class PostSerializer < ActiveModel::Serializer - # attributes :title, :body - # has_many :comments - # - # private - # - # def attributes - # hash = super - # hash.merge!(email: post.email) if author? - # hash - # end - # - # def author? - # post.author == scope - # end - # end - # - class Serializer - extend ActiveSupport::DescendantsTracker - - include ActiveModel::Serializable - include ActiveModel::Serializer::Caching - - INCLUDE_METHODS = {} - INSTRUMENT = { serialize: :"serialize.serializer", associations: :"associations.serializer" } - - class IncludeError < StandardError - attr_reader :source, :association - - def initialize(source, association) - @source, @association = source, association - end - - def to_s - "Cannot serialize #{association} when #{source} does not have a root!" - end - end - - class_attribute :_attributes - self._attributes = {} - - class_attribute :_associations - self._associations = {} - - class_attribute :_root - class_attribute :_embed - self._embed = :objects - class_attribute :_root_embed - - class_attribute :cache - class_attribute :perform_caching - - class << self - def cached(value = true) - self.perform_caching = value - end - - # Define attributes to be used in the serialization. - def attributes(*attrs) - - self._attributes = _attributes.dup - - attrs.each do |attr| - if Hash === attr - attr.each {|attr_real, key| attribute(attr_real, key: key) } - else - attribute attr - end - end - end - - def attribute(attr, options={}) - self._attributes = _attributes.merge(attr.is_a?(Hash) ? attr : {attr => options[:key] || attr.to_s.gsub(/\?$/, '').to_sym}) - - attr = attr.keys[0] if attr.is_a? Hash - - unless method_defined?(attr) - define_method attr do - object.read_attribute_for_serialization(attr.to_sym) - end - end - - define_include_method attr - - # protect inheritance chains and open classes - # if a serializer inherits from another OR - # attributes are added later in a classes lifecycle - # poison the cache - define_method :_fast_attributes do - raise NameError - end - - end - - def associate(klass, attrs) #:nodoc: - options = attrs.extract_options! - self._associations = _associations.dup - - attrs.each do |attr| - unless method_defined?(attr) - define_method attr do - object.send attr - end - end - - define_include_method attr - - self._associations[attr] = [klass, options] - end - end - - def define_include_method(name) - method = "include_#{name}?".to_sym - - INCLUDE_METHODS[name] = method - - unless method_defined?(method) - define_method method do - true - end - end - end - - # Defines an association in the object should be rendered. - # - # The serializer object should implement the association name - # as a method which should return an array when invoked. If a method - # with the association name does not exist, the association name is - # dispatched to the serialized object. - def has_many(*attrs) - associate(Association::HasMany, attrs) - end - - # Defines an association in the object should be rendered. - # - # The serializer object should implement the association name - # as a method which should return an object when invoked. If a method - # with the association name does not exist, the association name is - # dispatched to the serialized object. - def has_one(*attrs) - associate(Association::HasOne, attrs) - end - - # Return a schema hash for the current serializer. This information - # can be used to generate clients for the serialized output. - # - # The schema hash has two keys: +attributes+ and +associations+. - # - # The +attributes+ hash looks like this: - # - # { name: :string, age: :integer } - # - # The +associations+ hash looks like this: - # { posts: { has_many: :posts } } - # - # If :key is used: - # - # class PostsSerializer < ActiveModel::Serializer - # has_many :posts, key: :my_posts - # end - # - # the hash looks like this: - # - # { my_posts: { has_many: :posts } - # - # This information is extracted from the serializer's model class, - # which is provided by +SerializerClass.model_class+. - # - # The schema method uses the +columns_hash+ and +reflect_on_association+ - # methods, provided by default by ActiveRecord. You can implement these - # methods on your custom models if you want the serializer's schema method - # to work. - # - # TODO: This is currently coupled to Active Record. We need to - # figure out a way to decouple those two. - def schema - klass = model_class - columns = klass.columns_hash - - attrs = {} - _attributes.each do |name, key| - if column = columns[name.to_s] - attrs[key] = column.type - 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 - end - end - - associations = {} - _associations.each do |attr, (association_class, options)| - association = association_class.new(attr, options) - - if model_association = klass.reflect_on_association(association.name) - # Real association. - associations[association.key] = { model_association.macro => model_association.name } - else - # Computed association. We could infer has_many vs. has_one from - # the association class, but that would make it different from - # real associations, which read has_one vs. belongs_to from the - # model. - associations[association.key] = nil - end - end - - { attributes: attrs, associations: associations } - end - - # The model class associated with this serializer. - def model_class - name.sub(/Serializer$/, '').constantize - end - - # Define how associations should be embedded. - # - # embed :objects # Embed associations as full objects - # embed :ids # Embed only the association ids - # embed :ids, include: true # Embed the association ids and include objects in the root - # - def embed(type, options={}) - self._embed = type - self._root_embed = true if options[:include] - end - - # Defines the root used on serialization. If false, disables the root. - def root(name) - self._root = name - end - alias_method :root=, :root - - # 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 - # not be manually defined for this method. - def build_json(controller, resource, options) - default_options = controller.send(:default_serializer_options) || {} - options = default_options.merge(options || {}) - - serializer = options.delete(:serializer) || - (resource.respond_to?(:active_model_serializer) && - resource.active_model_serializer) - - return serializer unless serializer - - if resource.respond_to?(:to_ary) - unless serializer <= ActiveModel::ArraySerializer - raise ArgumentError.new("#{serializer.name} is not an ArraySerializer. " + - "You may want to use the :each_serializer option instead.") - end - - if options[:root] != false && serializer.root != false - # the serializer for an Array is ActiveModel::ArraySerializer - options[:root] ||= serializer.root || controller.controller_name - end - end - - options[:scope] = controller.serialization_scope unless options.has_key?(:scope) - options[:scope_name] = controller._serialization_scope unless options.has_key?(:scope_name) - options[:url_options] = controller.url_options - - serializer.new(resource, options) - end - end - - attr_reader :object, :options - - def initialize(object, options={}) - @object, @options = object, options - - scope_name = @options[:scope_name] - if scope_name && !respond_to?(scope_name) - self.class.class_eval do - define_method scope_name, lambda { scope } - end - end - end - - 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 - else - self._root || class_name - end - end - - def url_options - @options[:url_options] || {} - end - - # Returns a json representation of the serializable - # object including the root. - def as_json(args={}) - super(root: args.fetch(:root, options.fetch(:root, root_name))) - end - - def serialize_object - serializable_hash - end - - # Returns a hash representation of the serializable - # object without the root. - def serializable_hash - return nil if @object.nil? - @node = attributes - include_associations! if _embed - @node - end - - def include_associations! - _associations.each_key do |name| - include!(name) if include?(name) - end - end - - def include?(name) - return false if @options.key?(:only) && !Array(@options[:only]).include?(name) - return false if @options.key?(:except) && Array(@options[:except]).include?(name) - send INCLUDE_METHODS[name] - end - - def include!(name, options={}) - hash = @options[:hash] - unique_values = @options[:unique_values] ||= {} - - node = options[:node] ||= @node - value = options[:value] - - if options[:include] == nil - if @options.key?(:include) - options[:include] = @options[:include].include?(name) - elsif @options.include?(:exclude) - options[:include] = !@options[:exclude].include?(name) - end - end - - klass, klass_options = _associations[name] - association_class = - if klass - options = klass_options.merge options - klass - elsif value.respond_to?(:to_ary) - Association::HasMany - else - Association::HasOne - end - - options = default_embed_options.merge!(options) - options[:value] ||= send(name) - association = association_class.new(name, options, self.options) - - if association.embed_ids? - node[association.key] = association.serialize_ids - - if association.embed_in_root? && hash.nil? - raise IncludeError.new(self.class, association.name) - elsif association.embed_in_root? && association.embeddable? - merge_association hash, association.root, association.serializables, unique_values - end - elsif association.embed_objects? - node[association.key] = association.serialize - end - end - - # In some cases, an Array of associations is built by merging the associated - # content for all of the children. For instance, if a Post has_many comments, - # which has_many tags, the top-level :tags key will contain the merged list - # of all tags for all comments of the post. - # - # In order to make this efficient, we store a :unique_values hash containing - # a unique list of all of the objects that are already in the Array. This - # avoids the need to scan through the Array looking for entries every time - # we want to merge a new list of values. - def merge_association(hash, key, serializables, unique_values) - already_serialized = (unique_values[key] ||= {}) - serializable_hashes = (hash[key] ||= []) - - serializables.each do |serializable| - unless already_serialized.include? serializable.object - already_serialized[serializable.object] = true - serializable_hashes << serializable.serializable_hash - end - end - end - - # Returns a hash representation of the serializable - # object attributes. - def attributes - _fast_attributes - rescue NameError - method = "def _fast_attributes\n" - - method << " h = {}\n" - - _attributes.each do |name,key| - method << " h[:\"#{key}\"] = read_attribute_for_serialization(:\"#{name}\") if include?(:\"#{name}\")\n" - end - method << " h\nend" - - self.class.class_eval method - _fast_attributes - end - - # Returns options[:scope] - def scope - @options[:scope] - end - - alias :read_attribute_for_serialization :send - - # Use ActiveSupport::Notifications to send events to external systems. - # The event name is: name.class_name.serializer - def instrument(name, payload = {}, &block) - event_name = INSTRUMENT[name] - ActiveSupport::Notifications.instrument(event_name, payload, &block) - end - - private - - def default_embed_options - { - embed: _embed, - include: _root_embed - } - end - end - - # DefaultSerializer - # - # Provides a constant interface for all items, particularly - # for ArraySerializer. - class DefaultSerializer - attr_reader :object, :options - - def initialize(object, options={}) - @object, @options = object, options - end - - def serializable_hash - @object.as_json(@options) - end - end -end diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb deleted file mode 100644 index 1f2b0b53..00000000 --- a/lib/active_model/serializer/associations.rb +++ /dev/null @@ -1,185 +0,0 @@ -module ActiveModel - class Serializer - class Association #:nodoc: - # name: The name of the association. - # - # options: A hash. These keys are accepted: - # - # value: The object we're associating with. - # - # serializer: The class used to serialize the association. - # - # embed: Define how associations should be embedded. - # - :objects # Embed associations as full objects. - # - :ids # Embed only the association ids. - # - :ids, include: true # Embed the association ids and include objects in the root. - # - # include: Used in conjunction with embed :ids. Includes the objects in the root. - # - # root: Used in conjunction with include: true. Defines the key used to embed the objects. - # - # key: Key name used to store the ids in. - # - # embed_key: Method used to fetch ids. Defaults to :id. - # - # polymorphic: Is the association is polymorphic?. Values: true or false. - def initialize(name, options={}, serializer_options={}) - @name = name - @object = options[:value] - - embed = options[:embed] - @embed_ids = embed == :id || embed == :ids - @embed_objects = embed == :object || embed == :objects - @embed_key = options[:embed_key] || :id - @embed_in_root = options[:include] - - serializer = options[:serializer] - @serializer_class = serializer.is_a?(String) ? serializer.constantize : serializer - - @options = options - @serializer_options = serializer_options - end - - attr_reader :object, :root, :name, :embed_ids, :embed_objects, :embed_in_root - alias embeddable? object - alias embed_objects? embed_objects - alias embed_ids? embed_ids - alias use_id_key? embed_ids? - alias embed_in_root? embed_in_root - - def key - if key = options[:key] - key - elsif use_id_key? - id_key - else - name - end - end - - private - - attr_reader :embed_key, :serializer_class, :options, :serializer_options - - def find_serializable(object) - if serializer_class - serializer_class.new(object, serializer_options) - elsif object.respond_to?(:active_model_serializer) && (ams = object.active_model_serializer) - ams.new(object, serializer_options) - else - object - end - end - - class HasMany < Association #:nodoc: - def root - options[:root] || name - end - - def id_key - "#{name.to_s.singularize}_ids".to_sym - end - - def serializables - object.map do |item| - find_serializable(item) - end - end - - def serialize - object.map do |item| - find_serializable(item).serializable_hash - end - end - - def serialize_ids - object.map do |item| - serializer = find_serializable(item) - if serializer.respond_to?(embed_key) - serializer.send(embed_key) - else - item.read_attribute_for_serialization(embed_key) - end - end - end - end - - class HasOne < Association #:nodoc: - def initialize(name, options={}, serializer_options={}) - super - @polymorphic = options[:polymorphic] - end - - def root - if root = options[:root] - root - elsif polymorphic? - object.class.to_s.pluralize.demodulize.underscore.to_sym - else - name.to_s.pluralize.to_sym - end - end - - def id_key - "#{name}_id".to_sym - end - - def embeddable? - super || !polymorphic? - end - - def serializables - value = object && find_serializable(object) - value ? [value] : [] - end - - def serialize - if object - if polymorphic? - { - :type => polymorphic_key, - polymorphic_key => find_serializable(object).serializable_hash - } - else - find_serializable(object).serializable_hash - end - end - end - - 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 - - if polymorphic? - { - type: polymorphic_key, - id: id - } - else - id - end - end - end - - private - - attr_reader :polymorphic - alias polymorphic? polymorphic - - def use_id_key? - embed_ids? && !polymorphic? - end - - def polymorphic_key - object.class.to_s.demodulize.underscore.to_sym - end - end - end - end -end diff --git a/lib/active_model/serializer/caching.rb b/lib/active_model/serializer/caching.rb deleted file mode 100644 index 50fcf7b5..00000000 --- a/lib/active_model/serializer/caching.rb +++ /dev/null @@ -1,37 +0,0 @@ -module ActiveModel - class Serializer - module Caching - def to_json(*args) - if caching_enabled? - key = expand_cache_key([self.class.to_s.underscore, cache_key, 'to-json']) - cache.fetch key do - super - end - else - super - end - end - - def serialize(*args) - if caching_enabled? - key = expand_cache_key([self.class.to_s.underscore, cache_key, 'serialize']) - cache.fetch key do - serialize_object - end - else - serialize_object - end - end - - private - - def caching_enabled? - perform_caching && cache && respond_to?(:cache_key) - end - - def expand_cache_key(*args) - ActiveSupport::Cache.expand_cache_key(args) - end - end - end -end diff --git a/lib/active_model/serializer/version.rb b/lib/active_model/serializer/version.rb deleted file mode 100644 index 10e62668..00000000 --- a/lib/active_model/serializer/version.rb +++ /dev/null @@ -1,5 +0,0 @@ -module ActiveModel - class Serializer - VERSION = "0.8.1" - end -end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb deleted file mode 100644 index 4ae2d743..00000000 --- a/lib/active_model_serializers.rb +++ /dev/null @@ -1,95 +0,0 @@ -require "active_support" -require "active_support/core_ext/string/inflections" -require "active_support/notifications" -require "active_model" -require "active_model/array_serializer" -require "active_model/serializer" -require "active_model/serializer/associations" -require "set" - -if defined?(Rails) - module ActiveModel - class Railtie < Rails::Railtie - generators do |app| - Rails::Generators.configure!(app.config.generators) - Rails::Generators.hidden_namespaces.uniq! - require_relative "generators/resource_override" - end - - initializer "include_routes.active_model_serializer" do |app| - ActiveSupport.on_load(:active_model_serializers) do - include AbstractController::UrlFor - extend ::AbstractController::Railties::RoutesHelpers.with(app.routes) - include app.routes.mounted_helpers - end - end - - initializer "caching.active_model_serializer" do |app| - ActiveModel::Serializer.perform_caching = app.config.action_controller.perform_caching - ActiveModel::ArraySerializer.perform_caching = app.config.action_controller.perform_caching - - ActiveModel::Serializer.cache = Rails.cache - ActiveModel::ArraySerializer.cache = Rails.cache - end - end - end -end - -module ActiveModel::SerializerSupport - extend ActiveSupport::Concern - - module ClassMethods #:nodoc: - if "".respond_to?(:safe_constantize) - def active_model_serializer - "#{self.name}Serializer".safe_constantize - end - else - def active_model_serializer - begin - "#{self.name}Serializer".constantize - rescue NameError => e - raise unless e.message =~ /uninitialized constant/ - end - end - end - end - - # Returns a model serializer for this object considering its namespace. - def active_model_serializer - self.class.active_model_serializer - end - - alias :read_attribute_for_serialization :send -end - -module ActiveModel::ArraySerializerSupport - def active_model_serializer - ActiveModel::ArraySerializer - end -end - -Array.send(:include, ActiveModel::ArraySerializerSupport) -Set.send(:include, ActiveModel::ArraySerializerSupport) - -{ - active_record: 'ActiveRecord::Relation', - mongoid: 'Mongoid::Criteria' -}.each do |orm, rel_class| - ActiveSupport.on_load(orm) do - include ActiveModel::SerializerSupport - rel_class.constantize.send(:include, ActiveModel::ArraySerializerSupport) - end -end - -begin - require 'action_controller' - require 'action_controller/serialization' - - ActiveSupport.on_load(:action_controller) do - include ::ActionController::Serialization - end -rescue LoadError => ex - # rails on installed, continuing -end - -ActiveSupport.run_load_hooks(:active_model_serializers, ActiveModel::Serializer) diff --git a/lib/active_record/serializer_override.rb b/lib/active_record/serializer_override.rb deleted file mode 100644 index b6149b83..00000000 --- a/lib/active_record/serializer_override.rb +++ /dev/null @@ -1,16 +0,0 @@ -# We do not recommend that you use AM::S in this way, but if you must, here -# is a mixin that overrides ActiveRecord::Base#to_json and #as_json. - -module ActiveRecord - module SerializerOverride - def to_json options = {} - active_model_serializer.new(self).to_json options - end - - def as_json options={} - active_model_serializer.new(self).as_json options - end - end - - Base.send(:include, SerializerOverride) -end diff --git a/lib/generators/resource_override.rb b/lib/generators/resource_override.rb deleted file mode 100644 index 1b48a12e..00000000 --- a/lib/generators/resource_override.rb +++ /dev/null @@ -1,13 +0,0 @@ -require "rails/generators" -require "rails/generators/rails/resource/resource_generator" - -module Rails - module Generators - ResourceGenerator.class_eval do - def add_serializer - invoke "serializer" - end - end - end -end - diff --git a/lib/generators/serializer/USAGE b/lib/generators/serializer/USAGE deleted file mode 100644 index a49f7ea1..00000000 --- a/lib/generators/serializer/USAGE +++ /dev/null @@ -1,9 +0,0 @@ -Description: - Generates a serializer for the given resource with tests. - -Example: - `rails generate serializer Account name created_at` - - For TestUnit it creates: - Serializer: app/serializers/account_serializer.rb - TestUnit: test/unit/account_serializer_test.rb diff --git a/lib/generators/serializer/serializer_generator.rb b/lib/generators/serializer/serializer_generator.rb deleted file mode 100644 index 8212d62c..00000000 --- a/lib/generators/serializer/serializer_generator.rb +++ /dev/null @@ -1,36 +0,0 @@ -module Rails - module Generators - class SerializerGenerator < NamedBase - source_root File.expand_path("../templates", __FILE__) - check_class_collision suffix: "Serializer" - - argument :attributes, type: :array, default: [], banner: "field:type field:type" - - class_option :parent, type: :string, desc: "The parent class for the generated serializer" - - def create_serializer_file - template 'serializer.rb', File.join('app/serializers', class_path, "#{file_name}_serializer.rb") - end - - private - - def attributes_names - [:id] + attributes.select { |attr| !attr.reference? }.map { |a| a.name.to_sym } - end - - def association_names - attributes.select { |attr| attr.reference? }.map { |a| a.name.to_sym } - end - - def parent_class_name - if options[:parent] - options[:parent] - elsif defined?(::ApplicationSerializer) - "ApplicationSerializer" - else - "ActiveModel::Serializer" - end - end - end - end -end diff --git a/lib/generators/serializer/templates/serializer.rb b/lib/generators/serializer/templates/serializer.rb deleted file mode 100644 index 4ebb004e..00000000 --- a/lib/generators/serializer/templates/serializer.rb +++ /dev/null @@ -1,8 +0,0 @@ -<% module_namespacing do -%> -class <%= class_name %>Serializer < <%= parent_class_name %> - attributes <%= attributes_names.map(&:inspect).join(", ") %> -<% association_names.each do |attribute| -%> - has_one :<%= attribute %> -<% end -%> -end -<% end -%> diff --git a/test/array_serializer_test.rb b/test/array_serializer_test.rb deleted file mode 100644 index d48e8027..00000000 --- a/test/array_serializer_test.rb +++ /dev/null @@ -1,85 +0,0 @@ -require "test_helper" -require "test_fakes" - -class ArraySerializerTest < ActiveModel::TestCase - # serialize different typed objects - def test_array_serializer - model = Model.new - user = User.new - comments = Comment.new(title: "Comment1", id: 1) - - array = [model, user, comments] - serializer = array.active_model_serializer.new(array, scope: { scope: true }) - assert_equal([ - { model: "Model" }, - { last_name: "Valim", ok: true, first_name: "Jose", scope: true }, - { title: "Comment1" } - ], serializer.as_json) - end - - def test_array_serializer_with_root - comment1 = Comment.new(title: "Comment1", id: 1) - comment2 = Comment.new(title: "Comment2", id: 2) - - array = [ comment1, comment2 ] - - serializer = array.active_model_serializer.new(array, root: :comments) - - assert_equal({ comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ]}, serializer.as_json) - end - - def test_active_model_with_root - comment1 = ModelWithActiveModelSerializer.new(title: "Comment1") - comment2 = ModelWithActiveModelSerializer.new(title: "Comment2") - - array = [ comment1, comment2 ] - - serializer = array.active_model_serializer.new(array, root: :comments) - - assert_equal({ comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ]}, serializer.as_json) - end - - def test_array_serializer_with_hash - hash = { value: "something" } - array = [hash] - serializer = array.active_model_serializer.new(array, root: :items) - assert_equal({ items: [hash.as_json] }, serializer.as_json) - end - - def test_array_serializer_with_specified_serializer - post1 = Post.new(title: "Post1", author: "Author1", id: 1) - post2 = Post.new(title: "Post2", author: "Author2", id: 2) - - array = [ post1, post2 ] - - serializer = array.active_model_serializer.new array, each_serializer: CustomPostSerializer - - assert_equal([ - { title: "Post1" }, - { title: "Post2" } - ], serializer.as_json) - end - - def test_array_serializer_using_default_serializer - hash = { "value" => "something" } - class << hash - def active_model_serializer - nil - end - end - - array = [hash] - - serializer = array.active_model_serializer.new array - - assert_equal([ - { "value" => "something" } - ], serializer.as_json) - end -end diff --git a/test/association_test.rb b/test/association_test.rb deleted file mode 100644 index 3ae225a0..00000000 --- a/test/association_test.rb +++ /dev/null @@ -1,592 +0,0 @@ -require "test_helper" - -class AssociationTest < ActiveModel::TestCase - def def_serializer(&block) - Class.new(ActiveModel::Serializer, &block) - end - - class Model - def initialize(hash={}) - @attributes = hash - end - - def read_attribute_for_serialization(name) - @attributes[name] - end - - def as_json(*) - { model: "Model" } - end - - def method_missing(meth, *args) - if meth.to_s =~ /^(.*)=$/ - @attributes[$1.to_sym] = args[0] - elsif @attributes.key?(meth) - @attributes[meth] - else - super - end - end - end - - def setup - @hash = {} - @root_hash = {} - - @post = Model.new(title: "New Post", body: "Body") - @comment = Model.new(id: 1, external_id: "COMM001", body: "ZOMG A COMMENT") - @post.comments = [ @comment ] - @post.comment = @comment - - @comment_serializer_class = def_serializer do - attributes :id, :external_id, :body - end - - @post_serializer_class = def_serializer do - attributes :title, :body - end - - @post_serializer = @post_serializer_class.new(@post, hash: @root_hash) - end - - def include!(key, options={}) - @post_serializer.include! key, { - embed: :ids, - include: true, - node: @hash, - serializer: @comment_serializer_class - }.merge(options) - end - - def include_bare!(key, options={}) - @post_serializer.include! key, { - node: @hash, - serializer: @comment_serializer_class - }.merge(options) - end - - class NoDefaults < AssociationTest - def test_include_bang_has_many_associations - include! :comments, value: @post.comments - - assert_equal({ - comment_ids: [ 1 ] - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_include_bang_with_embed_false - include! :comments, value: @post.comments, embed: false - - assert_equal({}, @hash) - assert_equal({}, @root_hash) - end - - def test_include_bang_with_embed_ids_include_false - include! :comments, value: @post.comments, embed: :ids, include: false - - assert_equal({ - comment_ids: [ 1 ] - }, @hash) - - assert_equal({}, @root_hash) - end - - def test_include_bang_has_one_associations - include! :comment, value: @post.comment - - assert_equal({ - comment_id: 1 - }, @hash) - - assert_equal({ - comments: [{ id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" }] - }, @root_hash) - end - end - - class DefaultsTest < AssociationTest - def test_with_default_has_many - @post_serializer_class.class_eval do - has_many :comments - end - - include! :comments - - assert_equal({ - comment_ids: [ 1 ] - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_with_default_has_one - @post_serializer_class.class_eval do - has_one :comment - end - - include! :comment - - assert_equal({ - comment_id: 1 - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_with_default_has_many_with_custom_key - @post_serializer_class.class_eval do - has_many :comments, key: :custom_comments - end - - include! :comments - - assert_equal({ - custom_comments: [ 1 ] - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_with_default_has_one_with_custom_key - @post_serializer_class.class_eval do - has_one :comment, key: :custom_comment_id - end - - include! :comment - - assert_equal({ - custom_comment_id: 1 - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_with_default_has_many_with_custom_embed_key - @post_serializer_class.class_eval do - has_many :comments, embed_key: :external_id - end - - include! :comments - - assert_equal({ - comment_ids: [ "COMM001" ] - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_with_default_has_one_with_custom_embed_key - @post_serializer_class.class_eval do - has_one :comment, embed_key: :external_id - end - - include! :comment - - assert_equal({ - comment_id: "COMM001" - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_with_default_has_many_with_custom_key_and_custom_embed_key - @post_serializer_class.class_eval do - has_many :comments, key: :custom_comments, embed_key: :external_id - end - - include! :comments - - assert_equal({ - custom_comments: [ "COMM001" ] - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_with_default_has_one_with_custom_key_and_custom_embed_key - @post_serializer_class.class_eval do - has_one :comment, key: :custom_comment, embed_key: :external_id - end - - include! :comment - - assert_equal({ - custom_comment: "COMM001" - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_embed_objects_for_has_many_associations - @post_serializer_class.class_eval do - has_many :comments, embed: :objects - end - - include_bare! :comments - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @hash) - - assert_equal({}, @root_hash) - end - - def test_embed_ids_for_has_many_associations - @post_serializer_class.class_eval do - has_many :comments, embed: :ids - end - - include_bare! :comments - - assert_equal({ - comment_ids: [ 1 ] - }, @hash) - - assert_equal({}, @root_hash) - end - - def test_embed_false_for_has_many_associations - @post_serializer_class.class_eval do - has_many :comments, embed: false - end - - include_bare! :comments - - assert_equal({}, @hash) - assert_equal({}, @root_hash) - end - - def test_embed_ids_include_true_for_has_many_associations - @post_serializer_class.class_eval do - has_many :comments, embed: :ids, include: true - end - - include_bare! :comments - - assert_equal({ - comment_ids: [ 1 ] - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_embed_ids_for_has_one_associations - @post_serializer_class.class_eval do - has_one :comment, embed: :ids - end - - include_bare! :comment - - assert_equal({ - comment_id: 1 - }, @hash) - - assert_equal({}, @root_hash) - end - - def test_embed_false_for_has_one_associations - @post_serializer_class.class_eval do - has_one :comment, embed: false - end - - include_bare! :comment - - assert_equal({}, @hash) - assert_equal({}, @root_hash) - end - - def test_embed_ids_include_true_for_has_one_associations - @post_serializer_class.class_eval do - has_one :comment, embed: :ids, include: true - end - - include_bare! :comment - - assert_equal({ - comment_id: 1 - }, @hash) - - assert_equal({ - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, @root_hash) - end - - def test_embed_ids_include_true_does_not_serialize_multiple_times - @post.recent_comment = @comment - - @post_serializer_class.class_eval do - has_one :comment, embed: :ids, include: true - has_one :recent_comment, embed: :ids, include: true, root: :comments - end - - # Count how often the @comment record is serialized. - serialized_times = 0 - @comment.class_eval do - define_method :read_attribute_for_serialization, lambda { |name| - serialized_times += 1 if name == :body - super(name) - } - end - - include_bare! :comment - include_bare! :recent_comment - - assert_equal 1, serialized_times - end - - def test_include_with_read_association_id_for_serialization_hook - @post_serializer_class.class_eval do - has_one :comment, embed: :ids, include: true - end - - association_name = nil - @post.class_eval do - define_method :read_attribute_for_serialization, lambda { |name| - association_name = name - send(name) - } - define_method :comment_id, lambda { - @attributes[:comment].id - } - end - - include_bare! :comment - - assert_equal({ - comment_id: 1 - }, @hash) - end - - def test_include_with_read_association_ids_for_serialization_hook - @post_serializer_class.class_eval do - has_many :comments, embed: :ids, include: false - end - - association_name = nil - @post.class_eval do - define_method :read_attribute_for_serialization, lambda { |name| - association_name = name - send(name) - } - define_method :comment_ids, lambda { - @attributes[:comments].map(&:id) - } - end - - include_bare! :comments - - assert_equal({ - comment_ids: [1] - }, @hash) - end - end - - class RecursiveTest < AssociationTest - class BarSerializer < ActiveModel::Serializer; end - - class FooSerializer < ActiveModel::Serializer - root :foos - attributes :id - has_many :bars, serializer: BarSerializer, root: :bars, embed: :ids, include: true - end - - class BarSerializer < ActiveModel::Serializer - root :bars - attributes :id - has_many :foos, serializer: FooSerializer, root: :foos, embed: :ids, include: true - end - - class Foo < Model - def active_model_serializer; FooSerializer; end - end - - class Bar < Model - def active_model_serializer; BarSerializer; end - end - - def setup - super - - foo = Foo.new(id: 1) - bar = Bar.new(id: 2) - - foo.bars = [ bar ] - bar.foos = [ foo ] - - collection = [ foo ] - - @serializer = collection.active_model_serializer.new(collection, root: :foos) - end - - def test_mutual_relation_result - assert_equal({ - foos: [{ - bar_ids: [ 2 ], - id: 1 - }], - bars: [{ - foo_ids: [ 1 ], - id: 2 - }] - }, @serializer.as_json) - end - - def test_mutual_relation_does_not_raise_error - assert_nothing_raised SystemStackError, 'stack level too deep' do - @serializer.as_json - end - end - end - - class InclusionTest < AssociationTest - def setup - super - - comment_serializer_class = @comment_serializer_class - - @post_serializer_class.class_eval do - root :post - embed :ids, include: true - has_many :comments, serializer: comment_serializer_class - end - end - - def test_when_it_is_included - post_serializer = @post_serializer_class.new( - @post, include: [:comments] - ) - - json = post_serializer.as_json - - assert_equal({ - post: { - title: "New Post", - body: "Body", - comment_ids: [ 1 ] - }, - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, json) - end - - def test_when_it_is_not_included - post_serializer = @post_serializer_class.new( - @post, include: [] - ) - - json = post_serializer.as_json - - assert_equal({ - post: { - title: "New Post", - body: "Body", - comment_ids: [ 1 ] - } - }, json) - end - - def test_when_it_is_excluded - post_serializer = @post_serializer_class.new( - @post, exclude: [:comments] - ) - - json = post_serializer.as_json - - assert_equal({ - post: { - title: "New Post", - body: "Body", - comment_ids: [ 1 ] - } - }, json) - end - - def test_when_it_is_not_excluded - post_serializer = @post_serializer_class.new( - @post, exclude: [] - ) - - json = post_serializer.as_json - - assert_equal({ - post: { - title: "New Post", - body: "Body", - comment_ids: [ 1 ] - }, - comments: [ - { id: 1, external_id: "COMM001", body: "ZOMG A COMMENT" } - ] - }, json) - end - end - - class StringSerializerOption < AssociationTest - class StringSerializer < ActiveModel::Serializer - attributes :id, :body - end - - def test_specifying_serializer_class_as_string - @post_serializer_class.class_eval do - has_many :comments, embed: :objects - end - - include_bare! :comments, serializer: "AssociationTest::StringSerializerOption::StringSerializer" - - assert_equal({ - comments: [ - { id: 1, body: "ZOMG A COMMENT" } - ] - }, @hash) - - assert_equal({}, @root_hash) - end - end -end diff --git a/test/caching_test.rb b/test/caching_test.rb deleted file mode 100644 index ee1dd263..00000000 --- a/test/caching_test.rb +++ /dev/null @@ -1,96 +0,0 @@ -require "test_helper" - -class CachingTest < ActiveModel::TestCase - class NullStore - def fetch(key) - return store[key] if store[key] - - store[key] = yield - end - - def clear - store.clear - end - - def store - @store ||= {} - end - - def read(key) - store[key] - end - end - - class Programmer - def name - 'Adam' - end - - def skills - %w(ruby) - end - - def read_attribute_for_serialization(name) - send name - end - end - - def test_serializers_have_a_cache_store - ActiveModel::Serializer.cache = NullStore.new - - assert_kind_of NullStore, ActiveModel::Serializer.cache - end - - def test_serializers_can_enable_caching - serializer = Class.new(ActiveModel::Serializer) do - cached true - end - - assert serializer.perform_caching - end - - def test_serializers_use_cache - serializer = Class.new(ActiveModel::Serializer) do - cached true - attributes :name, :skills - - def self.to_s - 'serializer' - end - - def cache_key - object.name - end - end - - serializer.cache = NullStore.new - instance = serializer.new Programmer.new - - instance.to_json - - assert_equal(instance.serializable_hash, serializer.cache.read('serializer/Adam/serialize')) - assert_equal(instance.to_json, serializer.cache.read('serializer/Adam/to-json')) - end - - def test_array_serializer_uses_cache - serializer = Class.new(ActiveModel::ArraySerializer) do - cached true - - def self.to_s - 'array_serializer' - end - - def cache_key - 'cache-key' - end - end - - serializer.cache = NullStore.new - instance = serializer.new [Programmer.new] - - instance.to_json - - assert_equal instance.serializable_array, serializer.cache.read('array_serializer/cache-key/serialize') - assert_equal instance.to_json, serializer.cache.read('array_serializer/cache-key/to-json') - end -end diff --git a/test/generators_test.rb b/test/generators_test.rb deleted file mode 100644 index b1a05b3a..00000000 --- a/test/generators_test.rb +++ /dev/null @@ -1,73 +0,0 @@ -require 'test_helper' - -class Foo < Rails::Application - if Rails.version.to_s.start_with? '4' - config.eager_load = false - config.secret_key_base = 'abc123' - end -end - -Rails.application.load_generators - -require 'generators/serializer/serializer_generator' - -class SerializerGeneratorTest < Rails::Generators::TestCase - destination File.expand_path("../tmp", __FILE__) - setup :prepare_destination - - tests Rails::Generators::SerializerGenerator - arguments %w(account name:string description:text business:references) - - def test_generates_a_serializer - run_generator - assert_file "app/serializers/account_serializer.rb", /class AccountSerializer < ActiveModel::Serializer/ - end - - def test_generates_a_namespaced_serializer - run_generator ["admin/account"] - assert_file "app/serializers/admin/account_serializer.rb", /class Admin::AccountSerializer < ActiveModel::Serializer/ - end - - def test_uses_application_serializer_if_one_exists - Object.const_set(:ApplicationSerializer, Class.new) - run_generator - assert_file "app/serializers/account_serializer.rb", /class AccountSerializer < ApplicationSerializer/ - ensure - Object.send :remove_const, :ApplicationSerializer - end - - # def test_uses_namespace_application_serializer_if_one_exists - # Object.const_set(:SerializerNamespace, Module.new) - # SerializerNamespace.const_set(:ApplicationSerializer, Class.new) - # Rails::Generators.namespace = SerializerNamespace - # run_generator - # assert_file "app/serializers/serializer_namespace/account_serializer.rb", - # /module SerializerNamespace\n class AccountSerializer < ApplicationSerializer/ - # ensure - # Object.send :remove_const, :SerializerNamespace - # Rails::Generators.namespace = nil - # end - - def test_uses_given_parent - Object.const_set(:ApplicationSerializer, Class.new) - run_generator ["Account", "--parent=MySerializer"] - assert_file "app/serializers/account_serializer.rb", /class AccountSerializer < MySerializer/ - ensure - Object.send :remove_const, :ApplicationSerializer - end - - def test_generates_attributes_and_associations - run_generator - assert_file "app/serializers/account_serializer.rb" do |serializer| - assert_match(/^ attributes :id, :name, :description$/, serializer) - assert_match(/^ has_one :business$/, serializer) - end - end - - def test_with_no_attributes_does_not_add_extra_space - run_generator ["account"] - assert_file "app/serializers/account_serializer.rb" do |content| - assert_no_match /\n\nend/, content - end - end -end diff --git a/test/no_serialization_scope_test.rb b/test/no_serialization_scope_test.rb deleted file mode 100644 index 31ba475f..00000000 --- a/test/no_serialization_scope_test.rb +++ /dev/null @@ -1,34 +0,0 @@ -require "test_helper" - -class NoSerializationScopeTest < ActionController::TestCase - class ScopeSerializer - def initialize(object, options) - @object, @options = object, options - end - - def as_json(*) - { scope: @options[:scope].as_json } - end - end - - class ScopeSerializable - def active_model_serializer - ScopeSerializer - end - end - - class NoSerializationScopeController < ActionController::Base - serialization_scope nil - - def index - render json: ScopeSerializable.new - end - end - - tests NoSerializationScopeController - - def test_disabled_serialization_scope - get :index, format: :json - assert_equal '{"scope":null}', @response.body - end -end diff --git a/test/serialization_scope_name_test.rb b/test/serialization_scope_name_test.rb deleted file mode 100644 index a5e164c4..00000000 --- a/test/serialization_scope_name_test.rb +++ /dev/null @@ -1,99 +0,0 @@ -require 'test_helper' -require 'pathname' - -class DefaultScopeNameTest < ActionController::TestCase - TestUser = Struct.new(:name, :admin) - - class UserSerializer < ActiveModel::Serializer - attributes :admin? - def admin? - current_user.admin - end - end - - class UserTestController < ActionController::Base - protect_from_forgery - - before_filter { request.format = :json } - - def current_user - TestUser.new('Pete', false) - end - - def render_new_user - render json: TestUser.new('pete', false), serializer: UserSerializer - end - end - - tests UserTestController - - def test_default_scope_name - get :render_new_user - assert_equal '{"user":{"admin":false}}', @response.body - end -end - -class SerializationScopeNameTest < ActionController::TestCase - TestUser = Struct.new(:name, :admin) - - class AdminUserSerializer < ActiveModel::Serializer - attributes :admin? - def admin? - current_admin.admin - end - end - - class AdminUserTestController < ActionController::Base - protect_from_forgery - - serialization_scope :current_admin - before_filter { request.format = :json } - - def current_admin - TestUser.new('Bob', true) - end - - def render_new_user - render json: TestUser.new('pete', false), serializer: AdminUserSerializer - end - end - - tests AdminUserTestController - - def test_override_scope_name_with_controller - get :render_new_user - assert_equal '{"admin_user":{"admin":true}}', @response.body - end -end - -class SerializationActionScopeOverrideTest < ActionController::TestCase - TestUser = Struct.new(:name, :admin) - - class AdminUserSerializer < ActiveModel::Serializer - attributes :admin? - def admin? - current_admin.admin - end - end - - class AdminUserTestController < ActionController::Base - protect_from_forgery - before_filter { request.format = :json } - - def current_admin - TestUser.new('Bob', true) - end - - def render_new_user - render json: TestUser.new('pete', false), serializer: AdminUserSerializer, scope: current_admin, scope_name: :current_admin - end - end - - tests AdminUserTestController - - def test_override_scope_name_with_controller - get :render_new_user - assert_equal '{"admin_user":{"admin":true}}', @response.body - end - -end diff --git a/test/serialization_test.rb b/test/serialization_test.rb deleted file mode 100644 index 6fe5075c..00000000 --- a/test/serialization_test.rb +++ /dev/null @@ -1,394 +0,0 @@ -require 'test_helper' -require 'pathname' - -class RenderJsonTest < ActionController::TestCase - class JsonRenderable - def as_json(options={}) - hash = { a: :b, c: :d, e: :f } - hash.except!(*options[:except]) if options[:except] - hash - end - - def to_json(options = {}) - super except: [:c, :e] - end - end - - class JsonSerializer - def initialize(object, options={}) - @object, @options = object, options - end - - def as_json(*) - hash = { object: serializable_hash, scope: @options[:scope].as_json } - hash.merge!(options: true) if @options[:options] - hash.merge!(check_defaults: true) if @options[:check_defaults] - hash - end - - def serializable_hash - @object.as_json - end - end - - class JsonSerializable - def initialize(skip=false) - @skip = skip - end - - def active_model_serializer - JsonSerializer unless @skip - end - - def as_json(*) - { serializable_object: true } - end - end - - class CustomSerializer - def initialize(*) - end - - def as_json(*) - { hello: true } - end - end - - class AnotherCustomSerializer - def initialize(*) - end - - def as_json(*) - { rails: 'rocks' } - end - end - - class DummyCustomSerializer < ActiveModel::Serializer - attributes :id - end - - class HypermediaSerializable - def active_model_serializer - HypermediaSerializer - end - end - - class HypermediaSerializer < ActiveModel::Serializer - def as_json(*) - { link: hypermedia_url } - end - end - - class CustomArraySerializer < ActiveModel::ArraySerializer - self.root = "items" - end - - class TestController < ActionController::Base - protect_from_forgery - - serialization_scope :current_user - attr_reader :current_user - - def self.controller_path - 'test' - end - - def render_json_nil - render json: nil - end - - def render_json_render_to_string - render text: render_to_string(json: '[]') - end - - def render_json_hello_world - render json: ActiveSupport::JSON.encode(hello: 'world') - end - - def render_json_hello_world_with_status - render json: ActiveSupport::JSON.encode(hello: 'world'), status: 401 - end - - def render_json_hello_world_with_callback - render json: ActiveSupport::JSON.encode(hello: 'world'), callback: 'alert' - end - - def render_json_with_custom_content_type - render json: ActiveSupport::JSON.encode(hello: 'world'), content_type: 'text/javascript' - end - - def render_symbol_json - render json: ActiveSupport::JSON.encode(hello: 'world') - end - - def render_json_nil_with_custom_serializer - render json: nil, serializer: DummyCustomSerializer - end - - - def render_json_with_extra_options - render json: JsonRenderable.new, except: [:c, :e] - end - - def render_json_without_options - render json: JsonRenderable.new - end - - def render_json_with_serializer - @current_user = Struct.new(:as_json).new(current_user: true) - render json: JsonSerializable.new - end - - def render_json_with_serializer_and_implicit_root - @current_user = Struct.new(:as_json).new(current_user: true) - render json: [JsonSerializable.new] - end - - def render_json_with_serializer_and_options - @current_user = Struct.new(:as_json).new(current_user: true) - render json: JsonSerializable.new, options: true - end - - def render_json_with_serializer_and_scope_option - @current_user = Struct.new(:as_json).new(current_user: true) - scope = Struct.new(:as_json).new(current_user: false) - render json: JsonSerializable.new, scope: scope - end - - def render_json_with_serializer_api_but_without_serializer - @current_user = Struct.new(:as_json).new(current_user: true) - render json: JsonSerializable.new(true) - end - - # To specify a custom serializer for an object, use :serializer. - def render_json_with_custom_serializer - render json: Object.new, serializer: CustomSerializer - end - - # To specify a custom serializer for each item in the Array, use :each_serializer. - def render_json_array_with_custom_serializer - render json: [Object.new], each_serializer: CustomSerializer - end - - def render_json_array_with_wrong_option - render json: [Object.new], serializer: CustomSerializer - end - - def render_json_with_links - render json: HypermediaSerializable.new - end - - def render_json_array_with_no_root - render json: [], root: false - end - - def render_json_empty_array - render json: [] - end - - def render_json_array_with_custom_array_serializer - render json: [], serializer: CustomArraySerializer - end - - - private - def default_serializer_options - defaults = {} - defaults.merge!(check_defaults: true) if params[:check_defaults] - defaults.merge!(root: :awesome) if params[:check_default_root] - defaults.merge!(scope: :current_admin) if params[:check_default_scope] - defaults.merge!(serializer: AnotherCustomSerializer) if params[:check_default_serializer] - defaults.merge!(each_serializer: AnotherCustomSerializer) if params[:check_default_each_serializer] - defaults - end - end - - tests TestController - - def setup - # enable a logger so that (e.g.) the benchmarking stuff runs, so we can get - # a more accurate simulation of what happens in "real life". - super - @controller.logger = Logger.new(nil) - - @request.host = "www.nextangle.com" - end - - def test_render_json_nil - get :render_json_nil - assert_equal 'null', @response.body - assert_equal 'application/json', @response.content_type - end - - def test_render_json_render_to_string - get :render_json_render_to_string - assert_equal '[]', @response.body - end - - def test_render_json_nil_with_custom_serializer - get :render_json_nil_with_custom_serializer - assert_equal "{\"dummy_custom\":null}", @response.body - end - - def test_render_json - get :render_json_hello_world - assert_equal '{"hello":"world"}', @response.body - assert_equal 'application/json', @response.content_type - end - - def test_render_json_with_status - get :render_json_hello_world_with_status - assert_equal '{"hello":"world"}', @response.body - assert_equal 401, @response.status - end - - def test_render_json_with_callback - get :render_json_hello_world_with_callback - assert_equal 'alert({"hello":"world"})', @response.body - # For JSONP, Rails 3 uses application/json, but Rails 4 uses text/javascript - assert_match %r(application/json|text/javascript), @response.content_type.to_s - end - - def test_render_json_with_custom_content_type - get :render_json_with_custom_content_type - assert_equal '{"hello":"world"}', @response.body - assert_equal 'text/javascript', @response.content_type - end - - def test_render_symbol_json - get :render_symbol_json - assert_equal '{"hello":"world"}', @response.body - assert_equal 'application/json', @response.content_type - end - - def test_render_json_forwards_extra_options - get :render_json_with_extra_options - assert_equal '{"a":"b"}', @response.body - assert_equal 'application/json', @response.content_type - end - - def test_render_json_calls_to_json_from_object - get :render_json_without_options - assert_equal '{"a":"b"}', @response.body - end - - def test_render_json_with_serializer - get :render_json_with_serializer - assert_match '"scope":{"current_user":true}', @response.body - assert_match '"object":{"serializable_object":true}', @response.body - end - - def test_render_json_with_serializer_checking_defaults - get :render_json_with_serializer, check_defaults: true - assert_match '"scope":{"current_user":true}', @response.body - assert_match '"object":{"serializable_object":true}', @response.body - assert_match '"check_defaults":true', @response.body - end - - def test_render_json_with_serializer_checking_default_serailizer - get :render_json_with_serializer, check_default_serializer: true - assert_match '{"rails":"rocks"}', @response.body - end - - def test_render_json_with_serializer_checking_default_scope - get :render_json_with_serializer, check_default_scope: true - assert_match '"scope":"current_admin"', @response.body - end - - def test_render_json_with_serializer_and_implicit_root - get :render_json_with_serializer_and_implicit_root - assert_match '"test":[{"serializable_object":true}]', @response.body - end - - def test_render_json_with_serializer_and_implicit_root_checking_default_each_serailizer - get :render_json_with_serializer_and_implicit_root, check_default_each_serializer: true - assert_match '"test":[{"rails":"rocks"}]', @response.body - end - - def test_render_json_with_serializer_and_options - get :render_json_with_serializer_and_options - assert_match '"scope":{"current_user":true}', @response.body - assert_match '"object":{"serializable_object":true}', @response.body - assert_match '"options":true', @response.body - end - - def test_render_json_with_serializer_and_scope_option - get :render_json_with_serializer_and_scope_option - assert_match '"scope":{"current_user":false}', @response.body - end - - def test_render_json_with_serializer_and_scope_option_checking_default_scope - get :render_json_with_serializer_and_scope_option, check_default_scope: true - assert_match '"scope":{"current_user":false}', @response.body - end - - def test_render_json_with_serializer_api_but_without_serializer - get :render_json_with_serializer_api_but_without_serializer - assert_match '{"serializable_object":true}', @response.body - end - - def test_render_json_with_custom_serializer - get :render_json_with_custom_serializer - assert_match '{"hello":true}', @response.body - end - - def test_render_json_with_custom_serializer_checking_default_serailizer - get :render_json_with_custom_serializer, check_default_serializer: true - assert_match '{"hello":true}', @response.body - end - - def test_render_json_array_with_custom_serializer - get :render_json_array_with_custom_serializer - assert_match '{"test":[{"hello":true}]}', @response.body - end - - def test_render_json_array_with_wrong_option - assert_raise ArgumentError do - get :render_json_array_with_wrong_option - end - end - - def test_render_json_array_with_custom_serializer_checking_default_each_serailizer - get :render_json_array_with_custom_serializer, check_default_each_serializer: true - assert_match '{"test":[{"hello":true}]}', @response.body - end - - def test_render_json_with_links - get :render_json_with_links - assert_match '{"link":"http://www.nextangle.com/hypermedia"}', @response.body - end - - def test_render_json_array_with_no_root - get :render_json_array_with_no_root - assert_equal '[]', @response.body - end - - def test_render_json_array_with_no_root_checking_default_root - get :render_json_array_with_no_root, check_default_root: true - assert_equal '[]', @response.body - end - - def test_render_json_empty_array - get :render_json_empty_array - assert_equal '{"test":[]}', @response.body - end - - def test_render_json_empty_array_checking_default_root - get :render_json_empty_array, check_default_root: true - assert_equal '{"awesome":[]}', @response.body - end - - def test_render_json_empty_array_with_array_serializer_root_false - ActiveModel::ArraySerializer.root = false - get :render_json_empty_array - assert_equal '[]', @response.body - ensure # teardown - ActiveModel::ArraySerializer.root = nil - end - - def test_render_json_array_with_custom_array_serializer - get :render_json_array_with_custom_array_serializer - assert_equal '{"items":[]}', @response.body - end - -end diff --git a/test/serializer_support_test.rb b/test/serializer_support_test.rb deleted file mode 100644 index 03bd130f..00000000 --- a/test/serializer_support_test.rb +++ /dev/null @@ -1,51 +0,0 @@ -require "test_helper" - -class RandomModel - include ActiveModel::SerializerSupport -end - -class OtherRandomModel - include ActiveModel::SerializerSupport -end - -class OtherRandomModelSerializer -end - -class RandomModelCollection - include ActiveModel::ArraySerializerSupport -end - -module ActiveRecord - class Relation - end -end - -module Mongoid - class Criteria - end -end - -class SerializerSupportTest < ActiveModel::TestCase - test "it returns nil if no serializer exists" do - assert_equal nil, RandomModel.new.active_model_serializer - end - - test "it returns a deducted serializer if it exists exists" do - assert_equal OtherRandomModelSerializer, OtherRandomModel.new.active_model_serializer - end - - test "it returns ArraySerializer for a collection" do - assert_equal ActiveModel::ArraySerializer, RandomModelCollection.new.active_model_serializer - end - - test "it automatically includes array_serializer in active_record/relation" do - ActiveSupport.run_load_hooks(:active_record) - assert_equal ActiveModel::ArraySerializer, ActiveRecord::Relation.new.active_model_serializer - end - - test "it automatically includes array_serializer in mongoid/criteria" do - ActiveSupport.run_load_hooks(:mongoid) - assert_equal ActiveModel::ArraySerializer, Mongoid::Criteria.new.active_model_serializer - end -end - diff --git a/test/serializer_test.rb b/test/serializer_test.rb deleted file mode 100644 index 5da28d8a..00000000 --- a/test/serializer_test.rb +++ /dev/null @@ -1,1521 +0,0 @@ -require "test_helper" -require "test_fakes" - -class SerializerTest < ActiveModel::TestCase - def test_scope_works_correct - serializer = ActiveModel::Serializer.new :foo, scope: :bar - assert_equal serializer.scope, :bar - end - - def test_attributes - user = User.new - user_serializer = DefaultUserSerializer.new(user, {}) - - hash = user_serializer.as_json - - assert_equal({ - default_user: { first_name: "Jose", last_name: "Valim" } - }, hash) - end - - def test_attributes_method - user = User.new - user_serializer = UserSerializer.new(user, scope: {}) - - hash = user_serializer.as_json - - assert_equal({ - user: { first_name: "Jose", last_name: "Valim", ok: true } - }, hash) - end - - def test_attributes_method_specifying_keys - user = User.new - user_serializer = UserAttributesWithKeySerializer.new(user, scope: {}) - - hash = user_serializer.as_json - - assert_equal({ - user_attributes_with_key: { f_name: "Jose", l_name: "Valim", ok: true } - }, hash) - end - - def test_attributes_method_specifying_some_keys - user = User.new - user_serializer = UserAttributesWithSomeKeySerializer.new(user, scope: {}) - - hash = user_serializer.as_json - - assert_equal({ - user_attributes_with_some_key: { first_name: "Jose", l_name: "Valim", ok: true } - }, hash) - end - - def test_attributes_method_with_unsymbolizable_key - user = User.new - user_serializer = UserAttributesWithUnsymbolizableKeySerializer.new(user, scope: {}) - - hash = user_serializer.as_json - - assert_equal({ - user_attributes_with_unsymbolizable_key: { first_name: "Jose", :"last-name" => "Valim", ok: true } - }, hash) - end - - def test_attribute_method_with_name_as_serializer_prefix - object = SomeObject.new("something") - object_serializer = SomeSerializer.new(object, {}) - - hash = object_serializer.as_json - - assert_equal({ - some: { some: "something" } - }, hash) - end - - def test_serializer_receives_scope - user = User.new - user_serializer = UserSerializer.new(user, scope: { scope: true }) - - hash = user_serializer.as_json - - assert_equal({ - user: { - first_name: "Jose", - last_name: "Valim", - ok: true, - scope: true - } - }, hash) - end - - def test_serializer_receives_url_options - user = User.new - user_serializer = UserSerializer.new(user, url_options: { host: "test.local" }) - assert_equal({ host: "test.local" }, user_serializer.url_options) - end - - def test_serializer_returns_empty_hash_without_url_options - user = User.new - user_serializer = UserSerializer.new(user) - assert_equal({}, user_serializer.url_options) - end - - def test_pretty_accessors - user = User.new - user.superuser = true - user_serializer = MyUserSerializer.new(user) - - hash = user_serializer.as_json - - assert_equal({ - my_user: { - first_name: "Jose", last_name: "Valim", super_user: true - } - }, hash) - end - - def test_has_many - user = User.new - - post = Post.new(title: "New Post", body: "Body of new post", email: "tenderlove@tenderlove.com") - comments = [Comment.new(title: "Comment1"), Comment.new(title: "Comment2")] - post.comments = comments - - post_serializer = PostSerializer.new(post, scope: user) - - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ] - } - }, post_serializer.as_json) - end - - def test_conditionally_included_associations - user = User.new - - post = Post.new(title: "New Post", body: "Body of new post", email: "tenderlove@tenderlove.com") - comments = [Comment.new(title: "Comment1"), Comment.new(title: "Comment2")] - post.comments = comments - - post_serializer = PostWithConditionalCommentsSerializer.new(post, scope: user) - - # comments enabled - post.comments_disabled = false - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ] - } - }, post_serializer.as_json) - - # comments disabled - post.comments_disabled = true - assert_equal({ - post: { - title: "New Post", - body: "Body of new post" - } - }, post_serializer.as_json) - end - - def test_conditionally_included_associations_and_attributes - user = User.new - - post = Post.new(title: "New Post", body: "Body of new post", author: 'Sausage King', email: "tenderlove@tenderlove.com") - comments = [Comment.new(title: "Comment1"), Comment.new(title: "Comment2")] - post.comments = comments - - post_serializer = PostWithMultipleConditionalsSerializer.new(post, scope: user) - - # comments enabled - post.comments_disabled = false - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ] - } - }, post_serializer.as_json) - - # comments disabled - post.comments_disabled = true - assert_equal({ - post: { - title: "New Post", - body: "Body of new post" - } - }, post_serializer.as_json) - - # superuser - should see author - user.superuser = true - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - author: "Sausage King" - } - }, post_serializer.as_json) - end - - def test_has_one - user = User.new - blog = Blog.new - blog.author = user - - json = BlogSerializer.new(blog, scope: user).as_json - assert_equal({ - blog: { - author: { - first_name: "Jose", - last_name: "Valim" - } - } - }, json) - end - - def test_overridden_associations - author_serializer = Class.new(ActiveModel::Serializer) do - attributes :first_name - end - - blog_serializer = Class.new(ActiveModel::Serializer) do - def person - object.author - end - - has_one :person, serializer: author_serializer - end - - user = User.new - blog = Blog.new - blog.author = user - - json = blog_serializer.new(blog, scope: user).as_json - assert_equal({ - person: { - first_name: "Jose" - } - }, json) - end - - def post_serializer - Class.new(ActiveModel::Serializer) do - attributes :title, :body - has_many :comments, serializer: CommentSerializer - has_one :author, serializer: DefaultUserSerializer - end - end - - def test_associations_with_nil_association - user = User.new - blog = Blog.new - - json = BlogSerializer.new(blog, scope: user).as_json - assert_equal({ - blog: { author: nil } - }, json) - - serializer = Class.new(BlogSerializer) do - root :blog - end - - json = serializer.new(blog, scope: user).as_json - assert_equal({ blog: { author: nil } }, json) - end - - def test_custom_root - user = User.new - blog = Blog.new - - serializer = Class.new(BlogSerializer) do - root :my_blog - end - - assert_equal({ my_blog: { author: nil } }, serializer.new(blog, scope: user).as_json) - end - - def test_nil_root_object - user = User.new - blog = nil - - serializer = Class.new(BlogSerializer) do - root false - end - - assert_equal(nil, serializer.new(blog, scope: user).as_json) - end - - def test_custom_root_with_nil_root_object - user = User.new - blog = nil - - serializer = Class.new(BlogSerializer) do - root :my_blog - end - - assert_equal({ my_blog: nil }, serializer.new(blog, scope: user).as_json) - end - - def test_false_root - user = User.new - blog = Blog.new - - serializer = Class.new(BlogSerializer) do - root false - end - - another_serializer = Class.new(BlogSerializer) do - self.root = false - end - - assert_equal({ author: nil }, serializer.new(blog, scope: user).as_json) - assert_equal({ author: nil }, another_serializer.new(blog, scope: user).as_json) - - # test inherited false root - serializer = Class.new(serializer) - assert_equal({ author: nil }, serializer.new(blog, scope: user).as_json) - end - - def test_true_root - blog = Blog.new - - assert_equal({ - blog_with_root: { - author: nil, - } - }, BlogWithRootSerializer.new(blog).as_json) - end - - def test_root_false_on_load_active_model_serializers - begin - ActiveSupport.on_load(:active_model_serializers) do - self.root = false - end - - blog = Blog.new - serializer = BlogSerializer.new(blog) - - assert_equal({ author: nil }, serializer.as_json) - ensure - ActiveSupport.on_load(:active_model_serializers) do - self.root = nil - end - end - end - - def test_embed_ids - serializer = post_serializer - - serializer.class_eval do - root :post - embed :ids - end - - post = Post.new(title: "New Post", body: "Body of new post", email: "tenderlove@tenderlove.com") - comments = [Comment.new(title: "Comment1", id: 1), Comment.new(title: "Comment2", id: 2)] - post.comments = comments - - serializer = serializer.new(post) - - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - comment_ids: [1, 2], - author_id: nil - } - }, serializer.as_json) - end - - def test_embed_ids_include_true - serializer_class = post_serializer - - serializer_class.class_eval do - root :post - embed :ids, include: true - end - - post = Post.new(title: "New Post", body: "Body of new post", email: "tenderlove@tenderlove.com") - comments = [Comment.new(title: "Comment1", id: 1), Comment.new(title: "Comment2", id: 2)] - post.comments = comments - - serializer = serializer_class.new(post) - - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - comment_ids: [1, 2], - author_id: nil - }, - comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ], - authors: [] - }, serializer.as_json) - - post.author = User.new(id: 1) - - serializer = serializer_class.new(post) - - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - comment_ids: [1, 2], - author_id: 1 - }, - comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ], - authors: [{ first_name: "Jose", last_name: "Valim" }] - }, serializer.as_json) - end - - def test_methods_take_priority_over_associations - post_serializer = Class.new(ActiveModel::Serializer) do - attributes :title - has_many :comments - embed :ids - - def comments - object.comments[0,1] - end - end - - post = Post.new(title: "My Post") - comments = [Comment.new(title: "Comment1", id: 1), Comment.new(title: "Comment2", id: 2)] - post.comments = comments - - post.class_eval do - define_method :comment_ids, lambda { - self.comments.map { |c| c.read_attribute_for_serialization(:id) } - } - end - json = post_serializer.new(post).as_json - assert_equal({ - title: "My Post", - comment_ids: [1] - }, json) - end - - def test_methods_take_priority_over_associations_and_call_the_appropriate_id_method - comment_serializer = Class.new(ActiveModel::Serializer) do - def id - "OMG" - end - end - - post_serializer = Class.new(ActiveModel::Serializer) do - attributes :title - has_many :comments, serializer: comment_serializer - embed :ids - - def comments - object.comments[0,1] - end - end - - post = Post.new(title: "My Post") - comments = [Comment.new(title: "Comment1", id: 1), Comment.new(title: "Comment2", id: 2)] - post.comments = comments - - post.class_eval do - define_method :comment_ids, lambda { - self.comments.map { |c| c.read_attribute_for_serialization(:id) } - } - end - json = post_serializer.new(post).as_json - assert_equal({ - title: "My Post", - comment_ids: ["OMG"] - }, json) - end - - def test_embed_objects - serializer = post_serializer - - serializer.class_eval do - root :post - embed :objects - end - - post = Post.new(title: "New Post", body: "Body of new post", email: "tenderlove@tenderlove.com") - comments = [Comment.new(title: "Comment1", id: 1), Comment.new(title: "Comment2", id: 2)] - post.comments = comments - - serializer = serializer.new(post) - - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - author: nil, - comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ] - } - }, serializer.as_json) - end - - def test_sets_can_be_serialized - post1 = Post.new(title: "Post1", author: "Author1", id: 1) - post2 = Post.new(title: "Post2", author: "Author2", id: 2) - - set = Set.new - set << post1 - set << post2 - - serializer = set.active_model_serializer.new set, each_serializer: CustomPostSerializer - - as_json = serializer.as_json - assert_equal 2, as_json.size - assert as_json.include?({ title: "Post1" }) - assert as_json.include?({ title: "Post2" }) - end - - def test_associations_with_as - posts = [ - Post.new(title: 'First Post', body: 'text'), - Post.new(title: 'Second Post', body: 'text') - ] - user = User.new - - custom_blog = CustomBlog.new - custom_blog.public_posts = posts - custom_blog.public_user = user - - serializer = CustomBlogSerializer.new(custom_blog, scope: { scope: true }) - - assert_equal({ - custom_blog: { - posts: [ - {title: 'First Post', body: 'text', comments: []}, - {title: 'Second Post', body: 'text', comments: []} - ], - user: { - first_name: "Jose", - last_name: "Valim", ok: true, - scope: true - } - } - }, serializer.as_json) - end - - def test_implicity_detection_for_association_serializers - implicit_serializer = Class.new(ActiveModel::Serializer) do - root :custom_blog - const_set(:UserSerializer, UserSerializer) - const_set(:PostSerializer, PostSerializer) - - has_many :public_posts, key: :posts - has_one :public_user, key: :user - end - - posts = [ - Post.new(title: 'First Post', body: 'text', comments: []), - Post.new(title: 'Second Post', body: 'text', comments: []) - ] - user = User.new - - custom_blog = CustomBlog.new - custom_blog.public_posts = posts - custom_blog.public_user = user - - serializer = implicit_serializer.new(custom_blog, scope: { scope: true }) - - assert_equal({ - custom_blog: { - posts: [ - {title: 'First Post', body: 'text', comments: []}, - {title: 'Second Post', body: 'text', comments: []} - ], - user: { - first_name: "Jose", - last_name: "Valim", ok: true, - scope: true - } - } - }, serializer.as_json) - end - - def test_attribute_key - serializer_class = Class.new(ActiveModel::Serializer) do - root :user - - attribute :first_name, key: :firstName - attribute :last_name, key: :lastName - attribute :password - end - - serializer = serializer_class.new(User.new) - - assert_equal({ - user: { - firstName: "Jose", - lastName: "Valim", - password: "oh noes yugive my password" - } - }, serializer.as_json) - end - - def setup_model - Class.new do - class << self - def columns_hash - { "name" => Struct.new(:type).new(:string), "age" => Struct.new(:type).new(:integer) } - end - - def reflect_on_association(name) - case name - when :posts - Struct.new(:macro, :name).new(:has_many, :posts) - when :parent - Struct.new(:macro, :name).new(:belongs_to, :parent) - end - end - end - end - end - - def test_schema - model = setup_model - - serializer = Class.new(ActiveModel::Serializer) do - class << self; self; end.class_eval do - define_method(:model_class) do model end - end - - # Computed attributes (not real columns or associations). - def can_edit; end - def can_view; end - def drafts; end - - attributes :name, :age, { can_edit: :boolean }, :can_view - has_many :posts, serializer: Class.new - has_many :drafts, serializer: Class.new - has_one :parent, serializer: Class.new - end - - assert_equal serializer.schema, { - attributes: { name: :string, age: :integer, can_edit: :boolean, can_view: nil }, - associations: { - posts: { has_many: :posts }, - drafts: nil, - parent: { belongs_to: :parent } - } - } - end - - def test_schema_with_as - model = setup_model - - serializer = Class.new(ActiveModel::Serializer) do - class << self; self; end.class_eval do - define_method(:model_class) do model end - end - - attributes :name, :age - has_many :posts, key: :my_posts, serializer: Class.new - has_one :parent, key: :my_parent, serializer: Class.new - end - - assert_equal serializer.schema, { - attributes: { name: :string, age: :integer }, - associations: { - my_posts: { has_many: :posts }, - my_parent: { belongs_to: :parent } - } - } - end - - def test_embed_id_for_has_one - author_serializer = Class.new(ActiveModel::Serializer) - - serializer_class = Class.new(ActiveModel::Serializer) do - embed :ids - root :post - - attributes :title, :body - has_one :author, serializer: author_serializer - end - - post_class = Class.new(Model) do - attr_accessor :author - end - - author_class = Class.new(Model) - - post = post_class.new(title: "New Post", body: "It's a new post!") - author = author_class.new(id: 5) - post.author = author - - hash = serializer_class.new(post) - - assert_equal({ - post: { - title: "New Post", - body: "It's a new post!", - author_id: 5 - } - }, hash.as_json) - end - - def test_embed_id_for_has_one_overriding_associated_id - author_serializer = Class.new(ActiveModel::Serializer) do - def id - "OMG" - end - end - - serializer_class = Class.new(ActiveModel::Serializer) do - embed :ids - root :post - - attributes :title, :body - has_one :author, serializer: author_serializer - end - - post_class = Class.new(Model) do - attr_accessor :author - end - - author_class = Class.new(Model) - - post = post_class.new(title: "New Post", body: "It's a new post!") - author = author_class.new(id: 5) - post.author = author - - hash = serializer_class.new(post) - - assert_equal({ - post: { - title: "New Post", - body: "It's a new post!", - author_id: "OMG" - } - }, hash.as_json) - end - - def test_embed_objects_for_has_one - author_serializer = Class.new(ActiveModel::Serializer) do - attributes :id, :name - end - - serializer_class = Class.new(ActiveModel::Serializer) do - root :post - - attributes :title, :body - has_one :author, serializer: author_serializer - end - - post_class = Class.new(Model) do - attr_accessor :author - end - - author_class = Class.new(Model) - - post = post_class.new(title: "New Post", body: "It's a new post!") - author = author_class.new(id: 5, name: "Tom Dale") - post.author = author - - hash = serializer_class.new(post) - - assert_equal({ - post: { - title: "New Post", - body: "It's a new post!", - author: { id: 5, name: "Tom Dale" } - } - }, hash.as_json) - end - - def test_root_provided_in_options - author_serializer = Class.new(ActiveModel::Serializer) do - attributes :id, :name - end - - serializer_class = Class.new(ActiveModel::Serializer) do - root :post - - attributes :title, :body - has_one :author, serializer: author_serializer - end - - post_class = Class.new(Model) do - attr_accessor :author - end - - author_class = Class.new(Model) - - post = post_class.new(title: "New Post", body: "It's a new post!") - author = author_class.new(id: 5, name: "Tom Dale") - post.author = author - - assert_equal({ - blog_post: { - title: "New Post", - body: "It's a new post!", - author: { id: 5, name: "Tom Dale" } - } - }, serializer_class.new(post, root: :blog_post).as_json) - - assert_equal({ - title: "New Post", - body: "It's a new post!", - author: { id: 5, name: "Tom Dale" } - }, serializer_class.new(post, root: false).as_json) - - assert_equal({ - blog_post: { - title: "New Post", - body: "It's a new post!", - author: { id: 5, name: "Tom Dale" } - } - }, serializer_class.new(post).as_json(root: :blog_post)) - - assert_equal({ - title: "New Post", - body: "It's a new post!", - author: { id: 5, name: "Tom Dale" } - }, serializer_class.new(post).as_json(root: false)) - end - - def test_serializer_has_access_to_root_object - hash_object = nil - - author_serializer = Class.new(ActiveModel::Serializer) do - attributes :id, :name - - define_method :serializable_hash do - hash_object = @options[:hash] - super() - end - end - - serializer_class = Class.new(ActiveModel::Serializer) do - root :post - - attributes :title, :body - has_one :author, serializer: author_serializer - end - - post_class = Class.new(Model) do - attr_accessor :author - end - - author_class = Class.new(Model) - - post = post_class.new(title: "New Post", body: "It's a new post!") - author = author_class.new(id: 5, name: "Tom Dale") - post.author = author - - expected = serializer_class.new(post).as_json - assert_equal expected, hash_object - end - - def test_embed_ids_include_true_with_root - serializer_class = post_serializer - - serializer_class.class_eval do - root :post - embed :ids, include: true - has_many :comments, key: :comment_ids, root: :comments - has_one :author, serializer: DefaultUserSerializer, key: :author_id, root: :author - end - - post = Post.new(title: "New Post", body: "Body of new post", email: "tenderlove@tenderlove.com") - comments = [Comment.new(title: "Comment1", id: 1), Comment.new(title: "Comment2", id: 2)] - post.comments = comments - - serializer = serializer_class.new(post) - - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - comment_ids: [1, 2], - author_id: nil - }, - comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ], - author: [] - }, serializer.as_json) - - post.author = User.new(id: 1) - - serializer = serializer_class.new(post) - - assert_equal({ - post: { - title: "New Post", - body: "Body of new post", - comment_ids: [1, 2], - author_id: 1 - }, - comments: [ - { title: "Comment1" }, - { title: "Comment2" } - ], - author: [{ first_name: "Jose", last_name: "Valim" }] - }, serializer.as_json) - end - - # the point of this test is to illustrate that deeply nested serializers - # still side-load at the root. - def test_embed_with_include_inserts_at_root - tag_serializer = Class.new(ActiveModel::Serializer) do - attributes :id, :name - end - - comment_serializer = Class.new(ActiveModel::Serializer) do - embed :ids, include: true - attributes :id, :body - has_many :tags, serializer: tag_serializer - end - - post_serializer = Class.new(ActiveModel::Serializer) do - embed :ids, include: true - attributes :id, :title, :body - has_many :comments, serializer: comment_serializer - end - - post_class = Class.new(Model) do - attr_accessor :comments - - define_method :active_model_serializer do - post_serializer - end - end - - comment_class = Class.new(Model) do - attr_accessor :tags - end - - tag_class = Class.new(Model) - - post = post_class.new(title: "New Post", body: "NEW POST", id: 1) - comment1 = comment_class.new(body: "EWOT", id: 1) - comment2 = comment_class.new(body: "YARLY", id: 2) - tag1 = tag_class.new(name: "lolcat", id: 1) - tag2 = tag_class.new(name: "nyancat", id: 2) - tag3 = tag_class.new(name: "violetcat", id: 3) - - post.comments = [comment1, comment2] - comment1.tags = [tag1, tag3] - comment2.tags = [tag1, tag2] - - actual = ActiveModel::ArraySerializer.new([post], root: :posts).as_json - assert_equal({ - posts: [ - { title: "New Post", body: "NEW POST", id: 1, comment_ids: [1,2] } - ], - - comments: [ - { body: "EWOT", id: 1, tag_ids: [1,3] }, - { body: "YARLY", id: 2, tag_ids: [1,2] } - ], - - tags: [ - { name: "lolcat", id: 1 }, - { name: "violetcat", id: 3 }, - { name: "nyancat", id: 2 } - ] - }, actual) - end - - def test_can_customize_attributes - serializer = Class.new(ActiveModel::Serializer) do - attributes :title, :body - - def title - object.title.upcase - end - end - - klass = Class.new do - def read_attribute_for_serialization(name) - { title: "New post!", body: "First post body" }[name] - end - - def title - read_attribute_for_serialization(:title) - end - - def body - read_attribute_for_serialization(:body) - end - end - - object = klass.new - - actual = serializer.new(object, root: :post).as_json - - assert_equal({ - post: { - title: "NEW POST!", - body: "First post body" - } - }, actual) - end - - def test_can_customize_attributes_with_read_attributes - serializer = Class.new(ActiveModel::Serializer) do - attributes :title, :body - - def read_attribute_for_serialization(name) - { title: "New post!", body: "First post body" }[name] - end - end - - actual = serializer.new(Object.new, root: :post).as_json - - assert_equal({ - post: { - title: "New post!", - body: "First post body" - } - }, actual) - end - - def test_active_support_on_load_hooks_fired - loaded = nil - ActiveSupport.on_load(:active_model_serializers) do - loaded = self - end - assert_equal ActiveModel::Serializer, loaded - end - - def tests_query_attributes_strip_question_mark - todo = Class.new do - def overdue? - true - end - - def read_attribute_for_serialization(name) - send name - end - end - - serializer = Class.new(ActiveModel::Serializer) do - attribute :overdue? - end - - actual = serializer.new(todo.new).as_json - - assert_equal({ - overdue: true - }, actual) - end - - def tests_query_attributes_allow_key_option - todo = Class.new do - def overdue? - true - end - - def read_attribute_for_serialization(name) - send name - end - end - - serializer = Class.new(ActiveModel::Serializer) do - attribute :overdue?, key: :foo - end - - actual = serializer.new(todo.new).as_json - - assert_equal({ - foo: true - }, actual) - end - - def tests_can_handle_polymorphism - email_serializer = Class.new(ActiveModel::Serializer) do - attributes :subject, :body - end - - email_class = Class.new(Model) do - def self.to_s - "Email" - end - - define_method :active_model_serializer do - email_serializer - end - end - - attachment_serializer = Class.new(ActiveModel::Serializer) do - attributes :name, :url - has_one :attachable, polymorphic: true - end - - email = email_class.new subject: 'foo', body: 'bar' - - attachment = Attachment.new name: 'logo.png', url: 'http://example.com/logo.png', attachable: email - - actual = attachment_serializer.new(attachment, {}).as_json - - assert_equal({ - name: 'logo.png', - url: 'http://example.com/logo.png', - attachable: { - type: :email, - email: { subject: 'foo', body: 'bar' } - } - }, actual) - end - - def test_can_handle_polymoprhic_ids - email_serializer = Class.new(ActiveModel::Serializer) do - attributes :subject, :body - end - - email_class = Class.new(Model) do - def self.to_s - "Email" - end - - define_method :active_model_serializer do - email_serializer - end - end - - attachment_serializer = Class.new(ActiveModel::Serializer) do - embed :ids - attributes :name, :url - has_one :attachable, polymorphic: true - end - - email = email_class.new id: 1 - - attachment = Attachment.new name: 'logo.png', url: 'http://example.com/logo.png', attachable: email - - actual = attachment_serializer.new(attachment, {}).as_json - - assert_equal({ - name: 'logo.png', - url: 'http://example.com/logo.png', - attachable: { - type: :email, - id: 1 - } - }, actual) - end - - def test_polymorphic_associations_are_included_at_root - email_serializer = Class.new(ActiveModel::Serializer) do - attributes :subject, :body, :id - end - - email_class = Class.new(Model) do - def self.to_s - "Email" - end - - define_method :active_model_serializer do - email_serializer - end - end - - attachment_serializer = Class.new(ActiveModel::Serializer) do - root :attachment - embed :ids, include: true - attributes :name, :url - has_one :attachable, polymorphic: true - end - - email = email_class.new id: 1, subject: "Hello", body: "World" - - attachment = Attachment.new name: 'logo.png', url: 'http://example.com/logo.png', attachable: email - - actual = attachment_serializer.new(attachment, {}).as_json - - assert_equal({ - attachment: { - name: 'logo.png', - url: 'http://example.com/logo.png', - attachable: { - type: :email, - id: 1 - }}, - emails: [{ - id: 1, - subject: "Hello", - body: "World" - }] - }, actual) - end - - def test_multiple_polymorphic_associations - email_serializer = Class.new(ActiveModel::Serializer) do - attributes :subject, :body, :id - end - - orange_serializer = Class.new(ActiveModel::Serializer) do - embed :ids, include: true - - attributes :plu, :id - has_one :readable, polymorphic: true - end - - email_class = Class.new(Model) do - def self.to_s - "Email" - end - - define_method :active_model_serializer do - email_serializer - end - end - - orange_class = Class.new(Model) do - def self.to_s - "Orange" - end - - def readable - @attributes[:readable] - end - - define_method :active_model_serializer do - orange_serializer - end - end - - attachment_serializer = Class.new(ActiveModel::Serializer) do - root :attachment - embed :ids, include: true - - attributes :name, :url - - has_one :attachable, polymorphic: true - has_one :readable, polymorphic: true - has_one :edible, polymorphic: true - end - - email = email_class.new id: 1, subject: "Hello", body: "World" - orange = orange_class.new id: 1, plu: "3027", readable: email - - attachment = Attachment.new({ - name: 'logo.png', - url: 'http://example.com/logo.png', - attachable: email, - readable: email, - edible: orange - }) - - actual = attachment_serializer.new(attachment, {}).as_json - - assert_equal({ - emails: [{ - subject: "Hello", - body: "World", - id: 1 - }], - - oranges: [{ - plu: "3027", - id: 1, - readable: { type: :email, id: 1 } - }], - - attachment: { - name: 'logo.png', - url: 'http://example.com/logo.png', - attachable: { type: :email, id: 1 }, - readable: { type: :email, id: 1 }, - edible: { type: :orange, id: 1 } - } - }, actual) - end - - def test_raises_an_error_when_a_child_serializer_includes_associations_when_the_source_doesnt - attachment_serializer = Class.new(ActiveModel::Serializer) do - attributes :name - end - - fruit_serializer = Class.new(ActiveModel::Serializer) do - embed :ids, include: true - has_one :attachment, serializer: attachment_serializer - attribute :color - end - - banana_class = Class.new Model do - def self.to_s - 'banana' - end - - def attachment - @attributes[:attachment] - end - - define_method :active_model_serializer do - fruit_serializer - end - end - - strawberry_class = Class.new Model do - def self.to_s - 'strawberry' - end - - def attachment - @attributes[:attachment] - end - - define_method :active_model_serializer do - fruit_serializer - end - end - - smoothie = Class.new do - attr_reader :base, :flavor - - def initialize(base, flavor) - @base, @flavor = base, flavor - end - end - - smoothie_serializer = Class.new(ActiveModel::Serializer) do - root false - embed :ids, include: true - - has_one :base, polymorphic: true - has_one :flavor, polymorphic: true - end - - banana_attachment = Attachment.new({ - name: 'banana_blending.md', - id: 3, - }) - - strawberry_attachment = Attachment.new({ - name: 'strawberry_cleaning.doc', - id: 4 - }) - - banana = banana_class.new color: "yellow", id: 1, attachment: banana_attachment - strawberry = strawberry_class.new color: "red", id: 2, attachment: strawberry_attachment - - smoothie = smoothie_serializer.new(smoothie.new(banana, strawberry)) - - assert_raise ActiveModel::Serializer::IncludeError do - smoothie.as_json - end - end - - def tests_includes_does_not_include_nil_polymoprhic_associations - post_serializer = Class.new(ActiveModel::Serializer) do - root :post - embed :ids, include: true - has_one :author, polymorphic: true - attributes :title - end - - post = Post.new(title: 'Foo') - - actual = post_serializer.new(post).as_json - - assert_equal({ - post: { - title: 'Foo', - author: nil - } - }, actual) - end - - def test_meta_key_serialization - tag_serializer = Class.new(ActiveModel::Serializer) do - attributes :name - end - - tag_class = Class.new(Model) do - def name - @attributes[:name] - end - - define_method :active_model_serializer do - tag_serializer - end - end - - serializable_array = Class.new(Array) - - array = serializable_array.new - array << tag_class.new(name: 'Rails') - array << tag_class.new(name: 'Sinatra') - - actual = array.active_model_serializer.new(array, root: :tags, meta: {total: 10}).as_json - - assert_equal({ - meta: { - total: 10, - }, - tags: [ - { name: "Rails" }, - { name: "Sinatra" }, - ] - }, actual) - - actual = array.active_model_serializer.new(array, root: :tags, meta: {total: 10}, meta_key: 'meta_object').as_json - - assert_equal({ - meta_object: { - total: 10, - }, - tags: [ - { name: "Rails" }, - { name: "Sinatra" }, - ] - }, actual) - end - - def test_inheritance_does_not_used_cached_attributes - parent = Class.new(ActiveModel::Serializer) do - attributes :title - end - - child = Class.new(parent) do - attributes :body - end - - data_class = Class.new do - attr_accessor :title, :body - end - - item = data_class.new - item.title = "title" - item.body = "body" - - 2.times do - assert_equal({title: "title"}, - parent.new(item).attributes) - assert_equal({body: "body", title: "title"}, - child.new(item).attributes) - end - - end - - def test_scope_name_method - serializer = Class.new(ActiveModel::Serializer) do - def has_permission? - current_user.super_user? - end - end - - user = User.new - user.superuser = true - post = Post.new(title: 'Foo') - - a_serializer = serializer.new(post, scope: user, scope_name: :current_user) - assert a_serializer.has_permission? - end - - def test_only_option_filters_attributes_and_associations - post = Post.new(title: "New Post", body: "Body of new post") - comments = [Comment.new(title: "Comment1")] - post.comments = comments - - post_serializer = PostSerializer.new(post, only: :title) - - assert_equal({ - post: { - title: "New Post" - } - }, post_serializer.as_json) - end - - def test_except_option_filters_attributes_and_associations - post = Post.new(title: "New Post", body: "Body of new post") - comments = [Comment.new(title: "Comment1")] - post.comments = comments - - post_serializer = PostSerializer.new(post, except: [:body, :comments]) - - assert_equal({ - post: { - title: "New Post" - } - }, post_serializer.as_json) - end - - def test_only_option_takes_precedence_over_custom_defined_include_methods - user = User.new - - post = Post.new(title: "New Post", body: "Body of new post", author: "Sausage King") - comments = [Comment.new(title: "Comment")] - post.comments = comments - - post_serializer = PostWithMultipleConditionalsSerializer.new(post, scope: user, only: :title) - - # comments enabled - post.comments_disabled = false - # superuser - should see author - user.superuser = true - - assert_equal({ - post: { - title: "New Post" - } - }, post_serializer.as_json) - end -end diff --git a/test/test_fakes.rb b/test/test_fakes.rb deleted file mode 100644 index a0a244c1..00000000 --- a/test/test_fakes.rb +++ /dev/null @@ -1,202 +0,0 @@ -class Model - def initialize(hash={}) - @attributes = hash - end - - def read_attribute_for_serialization(name) - @attributes[name] - end - - def as_json(*) - { model: "Model" } - end -end - -class ModelWithActiveModelSerializer < Model - include ActiveModel::Serializers::JSON - attr_accessor :attributes - def read_attribute_for_serialization(name) - @attributes[name] - end -end - -class User - include ActiveModel::SerializerSupport - - attr_accessor :superuser - - def initialize(hash={}) - @attributes = hash.merge(first_name: "Jose", last_name: "Valim", password: "oh noes yugive my password") - end - - def read_attribute_for_serialization(name) - @attributes[name] - end - - def super_user? - @superuser - end -end - -class Post < Model - def initialize(attributes) - super(attributes) - self.comments ||= [] - self.comments_disabled = false - self.author = nil - end - - attr_accessor :comments, :comments_disabled, :author - def active_model_serializer; PostSerializer; end -end - -class Comment < Model - def active_model_serializer; CommentSerializer; end -end - -class UserSerializer < ActiveModel::Serializer - attributes :first_name, :last_name - - def serializable_hash - attributes.merge(ok: true).merge(options[:scope]) - end -end - -class UserAttributesWithKeySerializer < ActiveModel::Serializer - attributes first_name: :f_name, last_name: :l_name - - def serializable_hash - attributes.merge(ok: true).merge(options[:scope]) - end -end - -class UserAttributesWithSomeKeySerializer < ActiveModel::Serializer - attributes :first_name, last_name: :l_name - - def serializable_hash - attributes.merge(ok: true).merge(options[:scope]) - end -end - -class UserAttributesWithUnsymbolizableKeySerializer < ActiveModel::Serializer - attributes :first_name, last_name: :"last-name" - - def serializable_hash - attributes.merge(ok: true).merge(options[:scope]) - end -end - -class DefaultUserSerializer < ActiveModel::Serializer - attributes :first_name, :last_name -end - -class MyUserSerializer < ActiveModel::Serializer - attributes :first_name, :last_name - - def serializable_hash - hash = attributes - hash = hash.merge(super_user: true) if object.super_user? - hash - end -end - -class CommentSerializer - def initialize(comment, options={}) - @object = comment - end - - attr_reader :object - - def serializable_hash - { title: @object.read_attribute_for_serialization(:title) } - end - - def as_json(options=nil) - options ||= {} - if options[:root] == false - serializable_hash - else - { comment: serializable_hash } - end - end -end - -class PostSerializer < ActiveModel::Serializer - attributes :title, :body - has_many :comments, serializer: CommentSerializer -end - -class PostWithConditionalCommentsSerializer < ActiveModel::Serializer - root :post - attributes :title, :body - has_many :comments, serializer: CommentSerializer - - def include_associations! - include! :comments unless object.comments_disabled - end -end - -class PostWithMultipleConditionalsSerializer < ActiveModel::Serializer - root :post - attributes :title, :body, :author - has_many :comments, serializer: CommentSerializer - - def include_comments? - !object.comments_disabled - end - - def include_author? - scope.super_user? - end -end - -class Blog < Model - attr_accessor :author -end - -class AuthorSerializer < ActiveModel::Serializer - attributes :first_name, :last_name -end - -class BlogSerializer < ActiveModel::Serializer - has_one :author, serializer: AuthorSerializer -end - -class BlogWithRootSerializer < BlogSerializer - root true -end - -class CustomPostSerializer < ActiveModel::Serializer - attributes :title -end - -class CustomBlog < Blog - attr_accessor :public_posts, :public_user -end - -class CustomBlogSerializer < ActiveModel::Serializer - has_many :public_posts, key: :posts, serializer: PostSerializer - has_one :public_user, key: :user, serializer: UserSerializer -end - -class SomeSerializer < ActiveModel::Serializer - attributes :some -end - -class SomeObject < Struct.new(:some) -end - -# Set up some classes for polymorphic testing -class Attachment < Model - def attachable - @attributes[:attachable] - end - - def readable - @attributes[:readable] - end - - def edible - @attributes[:edible] - end -end diff --git a/test/test_helper.rb b/test/test_helper.rb deleted file mode 100644 index 889d7a60..00000000 --- a/test/test_helper.rb +++ /dev/null @@ -1,41 +0,0 @@ -require "rubygems" -require "bundler/setup" - -require 'simplecov' -SimpleCov.start do - add_group "lib", "lib" - add_group "spec", "spec" -end - -require 'coveralls' -Coveralls.wear! - -require "pry" - -require "active_model_serializers" -require "active_support/json" -require "minitest/autorun" - -require 'rails' - -module TestHelper - Routes = ActionDispatch::Routing::RouteSet.new - Routes.draw do - resource :hypermedia - get ':controller(/:action(/:id))' - get ':controller(/:action)' - end - - ActionController::Base.send :include, Routes.url_helpers - ActiveModel::Serializer.send :include, Routes.url_helpers -end - -ActiveSupport::TestCase.class_eval do - setup do - @routes = ::TestHelper::Routes - end -end - -class Object - undef_method :id if respond_to?(:id) -end From 7143eb8301216d03922314d0b6785dd5b458edc3 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 1 Jul 2013 14:04:25 -0700 Subject: [PATCH 02/73] Serialize just attributes --- lib/active_model/serializer.rb | 43 +++++++++++++++++++++++++++++ test/serializer/attributes_test.rb | 44 ++++++++++++++++++++++++++++++ test/test_helper.rb | 5 ++++ 3 files changed, 92 insertions(+) create mode 100644 lib/active_model/serializer.rb create mode 100644 test/serializer/attributes_test.rb create mode 100644 test/test_helper.rb diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb new file mode 100644 index 00000000..0c2f3860 --- /dev/null +++ b/lib/active_model/serializer.rb @@ -0,0 +1,43 @@ +module ActiveModel + class Serializer + class << self + def inherited(base) + base._attributes = {} + end + + attr_accessor :_attributes + + def attributes(*attrs) + @_attributes = attrs.map(&:to_s) + + attrs.each do |attr| + define_method attr do + object.read_attribute_for_serialization(attr) + end + end + end + end + + def initialize(object) + @object = object + end + attr_accessor :object + + alias read_attribute_for_serialization send + + def attributes + self.class._attributes.each_with_object({}) do |name, hash| + hash[name] = send(name) + end + end + + def serializable_hash(options={}) + return nil if object.nil? + attributes + end + + def as_json(options={}) + serializable_hash + end + end +end diff --git a/test/serializer/attributes_test.rb b/test/serializer/attributes_test.rb new file mode 100644 index 00000000..bff76e41 --- /dev/null +++ b/test/serializer/attributes_test.rb @@ -0,0 +1,44 @@ +require 'newbase/test_helper' +require 'newbase/active_model/serializer' + +module SerializerTest + module Attributes + class Model + def initialize(hash={}) + @attributes = hash + end + + def read_attribute_for_serialization(name) + @attributes[name] + end + end + + class ModelSerializer < ActiveModel::Serializer + attributes :attr1, :attr2 + end + + class Test < ActiveModel::TestCase + def setup + model = Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) + @model_serializer = ModelSerializer.new(model) + end + + def test_attributes_definition + assert_equal(['attr1', 'attr2'], + @model_serializer.class._attributes) + end + + def test_attributes_serialization_using_serializable_hash + assert_equal({ + 'attr1' => 'value1', 'attr2' => 'value2' + }, @model_serializer.serializable_hash) + end + + def test_attributes_serialization_using_as_json + assert_equal({ + 'attr1' => 'value1', 'attr2' => 'value2' + }, @model_serializer.as_json) + end + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb new file mode 100644 index 00000000..59932deb --- /dev/null +++ b/test/test_helper.rb @@ -0,0 +1,5 @@ +ENV['RAILS_ENV'] = 'test' + +require 'bundler/setup' +require 'rails' +require 'rails/test_help' From c3f857d1b67adc05df86b86b4e82e71477ec7b28 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 1 Jul 2013 17:20:51 -0700 Subject: [PATCH 03/73] Implement Serializer's root --- lib/active_model/serializer.rb | 23 ++++++-- test/serializer/root_test.rb | 103 +++++++++++++++++++++++++++++++++ 2 files changed, 122 insertions(+), 4 deletions(-) create mode 100644 test/serializer/root_test.rb diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 0c2f3860..615c9704 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -5,7 +5,16 @@ module ActiveModel base._attributes = {} end - attr_accessor :_attributes + attr_accessor :_root, :_attributes + + def root(root) + @_root = root + end + alias root= root + + def root_name + name.demodulize.underscore.sub(/_serializer$/, '') if name + end def attributes(*attrs) @_attributes = attrs.map(&:to_s) @@ -18,10 +27,12 @@ module ActiveModel end end - def initialize(object) + def initialize(object, options={}) @object = object + @root = options[:root] || self.class._root + @root = self.class.root_name if @root == true end - attr_accessor :object + attr_accessor :object, :root alias read_attribute_for_serialization send @@ -37,7 +48,11 @@ module ActiveModel end def as_json(options={}) - serializable_hash + if root = options[:root] || self.root + { root.to_s => serializable_hash } + else + serializable_hash + end end end end diff --git a/test/serializer/root_test.rb b/test/serializer/root_test.rb new file mode 100644 index 00000000..74a3cc37 --- /dev/null +++ b/test/serializer/root_test.rb @@ -0,0 +1,103 @@ +require 'newbase/test_helper' +require 'newbase/active_model/serializer' + +module SerializerTest + module Root + class Model + def initialize(hash={}) + @attributes = hash + end + + def read_attribute_for_serialization(name) + @attributes[name] + end + end + + class RootAsOptionTest < ActiveModel::TestCase + class ModelSerializer < ActiveModel::Serializer + attributes :attr1, :attr2 + end + ModelSerializer.root = true + + def setup + @model = Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) + @serializer = ModelSerializer.new(@model, root: 'initialize') + end + + def test_root_is_not_displayed_using_serializable_hash + assert_equal({ + 'attr1' => 'value1', 'attr2' => 'value2' + }, @serializer.serializable_hash) + end + + def test_root_using_as_json + assert_equal({ + 'initialize' => { + 'attr1' => 'value1', 'attr2' => 'value2' + } + }, @serializer.as_json) + end + + def test_root_from_serializer_name + @serializer = ModelSerializer.new(@model) + + assert_equal({ + 'model' => { + 'attr1' => 'value1', 'attr2' => 'value2' + } + }, @serializer.as_json) + end + + def test_root_as_argument_takes_presedence + assert_equal({ + 'argument' => { + 'attr1' => 'value1', 'attr2' => 'value2' + } + }, @serializer.as_json(root: 'argument')) + end + end + + class RootInSerializerTest < ActiveModel::TestCase + class ModelSerializer < ActiveModel::Serializer + root :in_serializer + attributes :attr1, :attr2 + end + + def setup + model = Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) + @serializer = ModelSerializer.new(model) + @rooted_serializer = ModelSerializer.new(model, root: :initialize) + end + + def test_root_is_not_displayed_using_serializable_hash + assert_equal({ + 'attr1' => 'value1', 'attr2' => 'value2' + }, @serializer.serializable_hash) + end + + def test_root_using_as_json + assert_equal({ + 'in_serializer' => { + 'attr1' => 'value1', 'attr2' => 'value2' + } + }, @serializer.as_json) + end + + def test_root_in_initializer_takes_precedence + assert_equal({ + 'initialize' => { + 'attr1' => 'value1', 'attr2' => 'value2' + } + }, @rooted_serializer.as_json) + end + + def test_root_as_argument_takes_precedence + assert_equal({ + 'argument' => { + 'attr1' => 'value1', 'attr2' => 'value2' + } + }, @rooted_serializer.as_json(root: :argument)) + end + end + end +end From b6f9c5ee4331654fba4d8fb1254052e251eca7eb Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 2 Jul 2013 00:13:26 -0700 Subject: [PATCH 04/73] Implement Serializer's scope --- lib/active_model/serializer.rb | 3 ++- test/serializer/scope_test.rb | 35 ++++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 1 deletion(-) create mode 100644 test/serializer/scope_test.rb diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 615c9704..d148860d 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -31,8 +31,9 @@ module ActiveModel @object = object @root = options[:root] || self.class._root @root = self.class.root_name if @root == true + @scope = options[:scope] end - attr_accessor :object, :root + attr_accessor :object, :root, :scope alias read_attribute_for_serialization send diff --git a/test/serializer/scope_test.rb b/test/serializer/scope_test.rb new file mode 100644 index 00000000..95e16ff8 --- /dev/null +++ b/test/serializer/scope_test.rb @@ -0,0 +1,35 @@ +require 'newbase/test_helper' +require 'newbase/active_model/serializer' + +module SerializerTest + module Scope + class Model + def initialize(hash={}) + @attributes = hash + end + + def read_attribute_for_serialization(name) + @attributes[name] + end + end + + class ModelSerializer < ActiveModel::Serializer + end + + class Test < ActiveModel::TestCase + def setup + @serializer = ModelSerializer.new(nil, scope: current_user) + end + + def test_scope + assert_equal('user', @serializer.scope) + end + + private + + def current_user + 'user' + end + end + end +end From 45453f638b7233f2c14ec6a023aabc7421be4b81 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 2 Jul 2013 14:30:42 -0700 Subject: [PATCH 05/73] Tests refactor --- .../serializer/attributes_test.rb | 26 +++++++------- .../active_model}/serializer/root_test.rb | 34 ++++++++++++------- .../active_model}/serializer/scope_test.rb | 24 ++++++------- 3 files changed, 47 insertions(+), 37 deletions(-) rename test/{ => unit/active_model}/serializer/attributes_test.rb (69%) rename test/{ => unit/active_model}/serializer/root_test.rb (86%) rename test/{ => unit/active_model}/serializer/scope_test.rb (52%) diff --git a/test/serializer/attributes_test.rb b/test/unit/active_model/serializer/attributes_test.rb similarity index 69% rename from test/serializer/attributes_test.rb rename to test/unit/active_model/serializer/attributes_test.rb index bff76e41..0d4469aa 100644 --- a/test/serializer/attributes_test.rb +++ b/test/unit/active_model/serializer/attributes_test.rb @@ -1,23 +1,23 @@ require 'newbase/test_helper' require 'newbase/active_model/serializer' -module SerializerTest - module Attributes - class Model - def initialize(hash={}) - @attributes = hash +module ActiveModel + class Serializer + class AttributesTest < ActiveModel::TestCase + class Model + def initialize(hash={}) + @attributes = hash + end + + def read_attribute_for_serialization(name) + @attributes[name] + end end - def read_attribute_for_serialization(name) - @attributes[name] + class ModelSerializer < ActiveModel::Serializer + attributes :attr1, :attr2 end - end - class ModelSerializer < ActiveModel::Serializer - attributes :attr1, :attr2 - end - - class Test < ActiveModel::TestCase def setup model = Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) @model_serializer = ModelSerializer.new(model) diff --git a/test/serializer/root_test.rb b/test/unit/active_model/serializer/root_test.rb similarity index 86% rename from test/serializer/root_test.rb rename to test/unit/active_model/serializer/root_test.rb index 74a3cc37..36944aab 100644 --- a/test/serializer/root_test.rb +++ b/test/unit/active_model/serializer/root_test.rb @@ -1,19 +1,19 @@ require 'newbase/test_helper' require 'newbase/active_model/serializer' -module SerializerTest - module Root - class Model - def initialize(hash={}) - @attributes = hash - end - - def read_attribute_for_serialization(name) - @attributes[name] - end - end - +module ActiveModel + class Serializer class RootAsOptionTest < ActiveModel::TestCase + class Model + def initialize(hash={}) + @attributes = hash + end + + def read_attribute_for_serialization(name) + @attributes[name] + end + end + class ModelSerializer < ActiveModel::Serializer attributes :attr1, :attr2 end @@ -58,6 +58,16 @@ module SerializerTest end class RootInSerializerTest < ActiveModel::TestCase + class Model + def initialize(hash={}) + @attributes = hash + end + + def read_attribute_for_serialization(name) + @attributes[name] + end + end + class ModelSerializer < ActiveModel::Serializer root :in_serializer attributes :attr1, :attr2 diff --git a/test/serializer/scope_test.rb b/test/unit/active_model/serializer/scope_test.rb similarity index 52% rename from test/serializer/scope_test.rb rename to test/unit/active_model/serializer/scope_test.rb index 95e16ff8..52c1655e 100644 --- a/test/serializer/scope_test.rb +++ b/test/unit/active_model/serializer/scope_test.rb @@ -1,22 +1,22 @@ require 'newbase/test_helper' require 'newbase/active_model/serializer' -module SerializerTest - module Scope - class Model - def initialize(hash={}) - @attributes = hash +module ActiveModel + class Serializer + class ScopeTest < ActiveModel::TestCase + class Model + def initialize(hash={}) + @attributes = hash + end + + def read_attribute_for_serialization(name) + @attributes[name] + end end - def read_attribute_for_serialization(name) - @attributes[name] + class ModelSerializer < ActiveModel::Serializer end - end - class ModelSerializer < ActiveModel::Serializer - end - - class Test < ActiveModel::TestCase def setup @serializer = ModelSerializer.new(nil, scope: current_user) end From d41e5ccef71475a74d9a844f29258eaaa4669820 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 2 Jul 2013 14:49:31 -0700 Subject: [PATCH 06/73] Implement SerializerSupport class --- lib/active_model/serializer_support.rb | 11 +++++++++++ .../active_model/serializer_support_test.rb | 19 +++++++++++++++++++ 2 files changed, 30 insertions(+) create mode 100644 lib/active_model/serializer_support.rb create mode 100644 test/unit/active_model/serializer_support_test.rb diff --git a/lib/active_model/serializer_support.rb b/lib/active_model/serializer_support.rb new file mode 100644 index 00000000..ed53c89a --- /dev/null +++ b/lib/active_model/serializer_support.rb @@ -0,0 +1,11 @@ +require 'active_support/core_ext/string/inflections' + +module ActiveModel + module SerializerSupport + def active_model_serializer + "#{self.class.name}Serializer".safe_constantize + end + + alias read_attribute_for_serialization send + end +end diff --git a/test/unit/active_model/serializer_support_test.rb b/test/unit/active_model/serializer_support_test.rb new file mode 100644 index 00000000..2ea2e8ac --- /dev/null +++ b/test/unit/active_model/serializer_support_test.rb @@ -0,0 +1,19 @@ +require 'newbase/test_helper' +require 'newbase/active_model/serializer_support' + +module ActiveModel + module SerializerSupport + class Test < ActiveModel::TestCase + class Model + include ActiveModel::SerializerSupport + end + + class ModelSerializer < ActiveModel::Serializer + end + + def test_active_model_returns_its_serializer + assert_equal ModelSerializer, Model.new.active_model_serializer + end + end + end +end From 0d3b56e9cf40966301ac0cf6c8dcf4263e61c2ea Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 3 Jul 2013 14:21:16 -0700 Subject: [PATCH 07/73] Implement AC integration --- lib/action_controller/serialization.rb | 71 ++++++++ lib/active_model_serializers.rb | 13 ++ .../action_controller/serialization_test.rb | 172 ++++++++++++++++++ test/test_helper.rb | 22 ++- 4 files changed, 274 insertions(+), 4 deletions(-) create mode 100644 lib/action_controller/serialization.rb create mode 100644 lib/active_model_serializers.rb create mode 100644 test/integration/action_controller/serialization_test.rb diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb new file mode 100644 index 00000000..25decc32 --- /dev/null +++ b/lib/action_controller/serialization.rb @@ -0,0 +1,71 @@ +require 'active_support/core_ext/class/attribute' + +module ActionController + # Action Controller Serialization + # + # Overrides render :json to check if the given object implements +active_model_serializer+ + # as a method. If so, use the returned serializer instead of calling +to_json+ on the object. + # + # This module also provides a serialization_scope method that allows you to configure the + # +serialization_scope+ of the serializer. Most apps will likely set the +serialization_scope+ + # to the current user: + # + # class ApplicationController < ActionController::Base + # serialization_scope :current_user + # end + # + # If you need more complex scope rules, you can simply override the serialization_scope: + # + # class ApplicationController < ActionController::Base + # private + # + # def serialization_scope + # current_user + # end + # end + # + module Serialization + extend ActiveSupport::Concern + + include ActionController::Renderers + + included do + class_attribute :_serialization_scope + self._serialization_scope = :current_user + end + + def _render_option_json(resource, options) + serializer = build_json_serializer(resource, options) + + if serializer + super(serializer, options) + else + super + end + end + + private + + def default_serializer_options + {} + end + + def serialization_scope + _serialization_scope = self.class._serialization_scope + send(_serialization_scope) if _serialization_scope && respond_to?(_serialization_scope, true) + end + + def build_json_serializer(resource, options) + options = default_serializer_options.merge(options || {}) + + serializer = + options.delete(:serializer) || + resource.respond_to?(:active_model_serializer) && + resource.active_model_serializer + + options[:scope] = serialization_scope unless options.has_key?(:scope) + + serializer.new(resource, options) + end + end +end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb new file mode 100644 index 00000000..3f9c8873 --- /dev/null +++ b/lib/active_model_serializers.rb @@ -0,0 +1,13 @@ +require 'newbase/active_model/serializer' +require 'newbase/active_model/serializer_support' + +begin + require 'action_controller' + require 'newbase/action_controller/serialization' + + ActiveSupport.on_load(:action_controller) do + include ::ActionController::Serialization + end +rescue LoadError + # rails not installed, continuing +end diff --git a/test/integration/action_controller/serialization_test.rb b/test/integration/action_controller/serialization_test.rb new file mode 100644 index 00000000..16477c16 --- /dev/null +++ b/test/integration/action_controller/serialization_test.rb @@ -0,0 +1,172 @@ +require 'newbase/test_helper' +require 'newbase/active_model_serializers' + +module ActionController + module Serialization + class ImplicitSerializerTest < ActionController::TestCase + class Model + include ActiveModel::SerializerSupport + + def initialize(hash={}) + @attributes = hash + end + + def read_attribute_for_serialization(name) + @attributes[name] + end + end + + class ModelSerializer < ActiveModel::Serializer + attributes :attr1, :attr2 + end + + class MyController < ActionController::Base + def render_using_implicit_serializer + render :json => Model.new(attr1: 'value1', attr2: 'value2', attr3: 'value3') + end + end + + tests MyController + + def test_render_using_implicit_serializer + get :render_using_implicit_serializer + assert_equal 'application/json', @response.content_type + assert_equal '{"attr1":"value1","attr2":"value2"}', @response.body + end + end + + class ImplicitSerializerScopeTest < ActionController::TestCase + class Model + include ActiveModel::SerializerSupport + + def initialize(hash={}) + @attributes = hash + end + + def read_attribute_for_serialization(name) + @attributes[name] + end + end + + class ModelSerializer < ActiveModel::Serializer + attributes :attr1, :attr2 + + def attr2 + object.read_attribute_for_serialization(:attr2) + '-' + scope + end + end + + class MyController < ActionController::Base + def render_using_implicit_serializer_and_scope + render :json => Model.new(attr1: 'value1', attr2: 'value2', attr3: 'value3') + end + + protected + + def current_user + 'current_user' + end + end + + tests MyController + + def test_render_using_implicit_serializer_and_scope + get :render_using_implicit_serializer_and_scope + assert_equal 'application/json', @response.content_type + assert_equal '{"attr1":"value1","attr2":"value2-current_user"}', @response.body + end + end + + class ExplicitSerializerScopeTest < ActionController::TestCase + class Model + include ActiveModel::SerializerSupport + + def initialize(hash={}) + @attributes = hash + end + + def read_attribute_for_serialization(name) + @attributes[name] + end + end + + class ModelSerializer < ActiveModel::Serializer + attributes :attr1, :attr2 + + def attr2 + object.read_attribute_for_serialization(:attr2) + '-' + scope + end + end + + class MyController < ActionController::Base + def render_using_implicit_serializer_and_explicit_scope + render json: Model.new(attr1: 'value1', attr2: 'value2', attr3: 'value3'), scope: current_admin + end + + private + + def current_user + 'current_user' + end + + def current_admin + 'current_admin' + end + end + + tests MyController + + def test_render_using_implicit_serializer_and_explicit_scope + get :render_using_implicit_serializer_and_explicit_scope + assert_equal 'application/json', @response.content_type + assert_equal '{"attr1":"value1","attr2":"value2-current_admin"}', @response.body + end + end + + class OverridingSerializationScopeTest < ActionController::TestCase + class Model + include ActiveModel::SerializerSupport + + def initialize(hash={}) + @attributes = hash + end + + def read_attribute_for_serialization(name) + @attributes[name] + end + end + + class ModelSerializer < ActiveModel::Serializer + attributes :attr1, :attr2 + + def attr2 + object.read_attribute_for_serialization(:attr2) + '-' + scope + end + end + + class MyController < ActionController::Base + def render_overriding_serialization_scope + render json: Model.new(attr1: 'value1', attr2: 'value2', attr3: 'value3') + end + + private + + def current_user + 'current_user' + end + + def serialization_scope + 'current_admin' + end + end + + tests MyController + + def test_render_overriding_serialization_scope + get :render_overriding_serialization_scope + assert_equal 'application/json', @response.content_type + assert_equal '{"attr1":"value1","attr2":"value2-current_admin"}', @response.body + end + end + end +end diff --git a/test/test_helper.rb b/test/test_helper.rb index 59932deb..f5b1f10e 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,5 +1,19 @@ -ENV['RAILS_ENV'] = 'test' - require 'bundler/setup' -require 'rails' -require 'rails/test_help' +require 'newbase/active_model_serializers' +require 'test/unit' + +module TestHelper + Routes = ActionDispatch::Routing::RouteSet.new + Routes.draw do + get ':controller(/:action(/:id))' + get ':controller(/:action)' + end + + ActionController::Base.send :include, Routes.url_helpers +end + +ActionController::TestCase.class_eval do + def setup + @routes = TestHelper::Routes + end +end From a25c35252580bdd9f710f1ed01beaa3805316040 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 3 Jul 2013 15:35:56 -0700 Subject: [PATCH 08/73] Move models and serializers in test to fixtures.rb file --- test/fixtures.rb | 24 ++++++ .../action_controller/serialization_test.rb | 78 +------------------ test/test_helper.rb | 1 + .../serializer/attributes_test.rb | 14 ---- .../unit/active_model/serializer/root_test.rb | 20 ++--- .../active_model/serializer/scope_test.rb | 13 ---- .../active_model/serializer_support_test.rb | 7 -- 7 files changed, 32 insertions(+), 125 deletions(-) create mode 100644 test/fixtures.rb diff --git a/test/fixtures.rb b/test/fixtures.rb new file mode 100644 index 00000000..4a143013 --- /dev/null +++ b/test/fixtures.rb @@ -0,0 +1,24 @@ +class Model + include ActiveModel::SerializerSupport + + def initialize(hash={}) + @attributes = hash + end + + def read_attribute_for_serialization(name) + @attributes[name] + end +end + +class ModelSerializer < ActiveModel::Serializer + attributes :attr1, :attr2 + + def attr2 + attr2 = object.read_attribute_for_serialization(:attr2) + if scope + attr2 + '-' + scope + else + attr2 + end + end +end diff --git a/test/integration/action_controller/serialization_test.rb b/test/integration/action_controller/serialization_test.rb index 16477c16..037fd19d 100644 --- a/test/integration/action_controller/serialization_test.rb +++ b/test/integration/action_controller/serialization_test.rb @@ -4,22 +4,6 @@ require 'newbase/active_model_serializers' module ActionController module Serialization class ImplicitSerializerTest < ActionController::TestCase - class Model - include ActiveModel::SerializerSupport - - def initialize(hash={}) - @attributes = hash - end - - def read_attribute_for_serialization(name) - @attributes[name] - end - end - - class ModelSerializer < ActiveModel::Serializer - attributes :attr1, :attr2 - end - class MyController < ActionController::Base def render_using_implicit_serializer render :json => Model.new(attr1: 'value1', attr2: 'value2', attr3: 'value3') @@ -36,32 +20,12 @@ module ActionController end class ImplicitSerializerScopeTest < ActionController::TestCase - class Model - include ActiveModel::SerializerSupport - - def initialize(hash={}) - @attributes = hash - end - - def read_attribute_for_serialization(name) - @attributes[name] - end - end - - class ModelSerializer < ActiveModel::Serializer - attributes :attr1, :attr2 - - def attr2 - object.read_attribute_for_serialization(:attr2) + '-' + scope - end - end - class MyController < ActionController::Base def render_using_implicit_serializer_and_scope render :json => Model.new(attr1: 'value1', attr2: 'value2', attr3: 'value3') end - protected + private def current_user 'current_user' @@ -78,26 +42,6 @@ module ActionController end class ExplicitSerializerScopeTest < ActionController::TestCase - class Model - include ActiveModel::SerializerSupport - - def initialize(hash={}) - @attributes = hash - end - - def read_attribute_for_serialization(name) - @attributes[name] - end - end - - class ModelSerializer < ActiveModel::Serializer - attributes :attr1, :attr2 - - def attr2 - object.read_attribute_for_serialization(:attr2) + '-' + scope - end - end - class MyController < ActionController::Base def render_using_implicit_serializer_and_explicit_scope render json: Model.new(attr1: 'value1', attr2: 'value2', attr3: 'value3'), scope: current_admin @@ -124,26 +68,6 @@ module ActionController end class OverridingSerializationScopeTest < ActionController::TestCase - class Model - include ActiveModel::SerializerSupport - - def initialize(hash={}) - @attributes = hash - end - - def read_attribute_for_serialization(name) - @attributes[name] - end - end - - class ModelSerializer < ActiveModel::Serializer - attributes :attr1, :attr2 - - def attr2 - object.read_attribute_for_serialization(:attr2) + '-' + scope - end - end - class MyController < ActionController::Base def render_overriding_serialization_scope render json: Model.new(attr1: 'value1', attr2: 'value2', attr3: 'value3') diff --git a/test/test_helper.rb b/test/test_helper.rb index f5b1f10e..28fd7d53 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,6 +1,7 @@ require 'bundler/setup' require 'newbase/active_model_serializers' require 'test/unit' +require 'newbase/fixtures' module TestHelper Routes = ActionDispatch::Routing::RouteSet.new diff --git a/test/unit/active_model/serializer/attributes_test.rb b/test/unit/active_model/serializer/attributes_test.rb index 0d4469aa..eba7182f 100644 --- a/test/unit/active_model/serializer/attributes_test.rb +++ b/test/unit/active_model/serializer/attributes_test.rb @@ -4,20 +4,6 @@ require 'newbase/active_model/serializer' module ActiveModel class Serializer class AttributesTest < ActiveModel::TestCase - class Model - def initialize(hash={}) - @attributes = hash - end - - def read_attribute_for_serialization(name) - @attributes[name] - end - end - - class ModelSerializer < ActiveModel::Serializer - attributes :attr1, :attr2 - end - def setup model = Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) @model_serializer = ModelSerializer.new(model) diff --git a/test/unit/active_model/serializer/root_test.rb b/test/unit/active_model/serializer/root_test.rb index 36944aab..bf513553 100644 --- a/test/unit/active_model/serializer/root_test.rb +++ b/test/unit/active_model/serializer/root_test.rb @@ -4,24 +4,16 @@ require 'newbase/active_model/serializer' module ActiveModel class Serializer class RootAsOptionTest < ActiveModel::TestCase - class Model - def initialize(hash={}) - @attributes = hash - end - - def read_attribute_for_serialization(name) - @attributes[name] - end - end - - class ModelSerializer < ActiveModel::Serializer - attributes :attr1, :attr2 - end - ModelSerializer.root = true def setup + @old_root = ModelSerializer._root @model = Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) @serializer = ModelSerializer.new(@model, root: 'initialize') + ModelSerializer._root = true + end + + def teardown + ModelSerializer._root = @old_root end def test_root_is_not_displayed_using_serializable_hash diff --git a/test/unit/active_model/serializer/scope_test.rb b/test/unit/active_model/serializer/scope_test.rb index 52c1655e..9ec106b6 100644 --- a/test/unit/active_model/serializer/scope_test.rb +++ b/test/unit/active_model/serializer/scope_test.rb @@ -4,19 +4,6 @@ require 'newbase/active_model/serializer' module ActiveModel class Serializer class ScopeTest < ActiveModel::TestCase - class Model - def initialize(hash={}) - @attributes = hash - end - - def read_attribute_for_serialization(name) - @attributes[name] - end - end - - class ModelSerializer < ActiveModel::Serializer - end - def setup @serializer = ModelSerializer.new(nil, scope: current_user) end diff --git a/test/unit/active_model/serializer_support_test.rb b/test/unit/active_model/serializer_support_test.rb index 2ea2e8ac..3db8bdfe 100644 --- a/test/unit/active_model/serializer_support_test.rb +++ b/test/unit/active_model/serializer_support_test.rb @@ -4,13 +4,6 @@ require 'newbase/active_model/serializer_support' module ActiveModel module SerializerSupport class Test < ActiveModel::TestCase - class Model - include ActiveModel::SerializerSupport - end - - class ModelSerializer < ActiveModel::Serializer - end - def test_active_model_returns_its_serializer assert_equal ModelSerializer, Model.new.active_model_serializer end From af357619c1b9e61fde6c6200335f2f566399d42b Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 4 Jul 2013 11:17:53 -0700 Subject: [PATCH 09/73] Add AMS support to AR objects --- Gemfile | 6 ++++++ lib/active_model_serializers.rb | 6 ++++++ test/fixtures/active_record.rb | 20 +++++++++++++++++++ test/{fixtures.rb => fixtures/poro.rb} | 0 test/test_helper.rb | 2 +- .../serializer_support/active_record_test.rb | 13 ++++++++++++ .../poro_test.rb} | 2 +- 7 files changed, 47 insertions(+), 2 deletions(-) create mode 100644 Gemfile create mode 100644 test/fixtures/active_record.rb rename test/{fixtures.rb => fixtures/poro.rb} (100%) create mode 100644 test/unit/active_model/serializer_support/active_record_test.rb rename test/unit/active_model/{serializer_support_test.rb => serializer_support/poro_test.rb} (81%) diff --git a/Gemfile b/Gemfile new file mode 100644 index 00000000..9e639683 --- /dev/null +++ b/Gemfile @@ -0,0 +1,6 @@ +source 'https://rubygems.org' + +# Specify gem dependencies in active_model_serializers.gemspec +gemspec + +gem "sqlite3" diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 3f9c8873..0e715c3c 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -11,3 +11,9 @@ begin rescue LoadError # rails not installed, continuing end + +[:active_record, :mongoid].each do |orm| + ActiveSupport.on_load(orm) do + include ActiveModel::SerializerSupport + end +end diff --git a/test/fixtures/active_record.rb b/test/fixtures/active_record.rb new file mode 100644 index 00000000..05ae73a4 --- /dev/null +++ b/test/fixtures/active_record.rb @@ -0,0 +1,20 @@ +require 'active_record' + +ActiveRecord::Base.establish_connection( + :adapter => 'sqlite3', + :database => ':memory:' +) + +ActiveRecord::Schema.define do + create_table :ar_models, :force => true do |t| + t.string :attr1 + t.string :attr2 + end +end + +class ARModel < ActiveRecord::Base +end + +class ARModelSerializer < ActiveModel::Serializer + attributes :attr1, :attr2 +end diff --git a/test/fixtures.rb b/test/fixtures/poro.rb similarity index 100% rename from test/fixtures.rb rename to test/fixtures/poro.rb diff --git a/test/test_helper.rb b/test/test_helper.rb index 28fd7d53..e9ee164a 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,7 +1,7 @@ require 'bundler/setup' require 'newbase/active_model_serializers' require 'test/unit' -require 'newbase/fixtures' +require 'newbase/fixtures/poro' module TestHelper Routes = ActionDispatch::Routing::RouteSet.new diff --git a/test/unit/active_model/serializer_support/active_record_test.rb b/test/unit/active_model/serializer_support/active_record_test.rb new file mode 100644 index 00000000..f58be6e1 --- /dev/null +++ b/test/unit/active_model/serializer_support/active_record_test.rb @@ -0,0 +1,13 @@ +require 'newbase/test_helper' +require 'newbase/fixtures/active_record' +require 'newbase/active_model/serializer_support' + +module ActiveModel + module SerializerSupport + class Test < ActiveModel::TestCase + def test_active_model_returns_its_serializer + assert_equal ARModelSerializer, ARModel.new.active_model_serializer + end + end + end +end diff --git a/test/unit/active_model/serializer_support_test.rb b/test/unit/active_model/serializer_support/poro_test.rb similarity index 81% rename from test/unit/active_model/serializer_support_test.rb rename to test/unit/active_model/serializer_support/poro_test.rb index 3db8bdfe..fb212007 100644 --- a/test/unit/active_model/serializer_support_test.rb +++ b/test/unit/active_model/serializer_support/poro_test.rb @@ -4,7 +4,7 @@ require 'newbase/active_model/serializer_support' module ActiveModel module SerializerSupport class Test < ActiveModel::TestCase - def test_active_model_returns_its_serializer + def test_active_model_on_poro_returns_its_serializer assert_equal ModelSerializer, Model.new.active_model_serializer end end From 93baaa96b19e69098579b51db2f36809cfa28522 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 5 Jul 2013 14:03:02 -0700 Subject: [PATCH 10/73] Implement meta and meta_key for AM::Serializer --- lib/active_model/serializer.rb | 16 +++++--- .../unit/active_model/serializer/meta_test.rb | 41 +++++++++++++++++++ 2 files changed, 51 insertions(+), 6 deletions(-) create mode 100644 test/unit/active_model/serializer/meta_test.rb diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index d148860d..1e9835e5 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -28,12 +28,14 @@ module ActiveModel end def initialize(object, options={}) - @object = object - @root = options[:root] || self.class._root - @root = self.class.root_name if @root == true - @scope = options[:scope] + @object = object + @root = options[:root] || self.class._root + @root = self.class.root_name if @root == true + @scope = options[:scope] + @meta_key = options[:meta_key] || :meta + @meta = options[@meta_key] end - attr_accessor :object, :root, :scope + attr_accessor :object, :root, :scope, :meta_key, :meta alias read_attribute_for_serialization send @@ -50,7 +52,9 @@ module ActiveModel def as_json(options={}) if root = options[:root] || self.root - { root.to_s => serializable_hash } + hash = { root.to_s => serializable_hash } + hash[meta_key.to_s] = meta if meta + hash else serializable_hash end diff --git a/test/unit/active_model/serializer/meta_test.rb b/test/unit/active_model/serializer/meta_test.rb new file mode 100644 index 00000000..fdfbf584 --- /dev/null +++ b/test/unit/active_model/serializer/meta_test.rb @@ -0,0 +1,41 @@ +require 'newbase/test_helper' +require 'newbase/active_model/serializer' + + +module ActiveModel + class Serializer + class MetaTest < ActiveModel::TestCase + def setup + @model = Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) + end + + def test_meta + model_serializer = ModelSerializer.new(@model, root: 'model', meta: { 'total' => 10 }) + + assert_equal({ + 'model' => { + 'attr1' => 'value1', + 'attr2' => 'value2' + }, + 'meta' => { + 'total' => 10 + } + }, model_serializer.as_json) + end + + def test_meta_using_meta_key + model_serializer = ModelSerializer.new(@model, root: 'model', meta_key: :my_meta, my_meta: { 'total' => 10 }) + + assert_equal({ + 'model' => { + 'attr1' => 'value1', + 'attr2' => 'value2' + }, + 'my_meta' => { + 'total' => 10 + } + }, model_serializer.as_json) + end + end + end +end From 9e75625b1fb47e7e9125f20f43a714734ff9533c Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 5 Jul 2013 14:15:12 -0700 Subject: [PATCH 11/73] Re-order properties --- lib/active_model/serializer.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 1e9835e5..20c58e4a 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -29,13 +29,13 @@ module ActiveModel def initialize(object, options={}) @object = object + @scope = options[:scope] @root = options[:root] || self.class._root @root = self.class.root_name if @root == true - @scope = options[:scope] @meta_key = options[:meta_key] || :meta @meta = options[@meta_key] end - attr_accessor :object, :root, :scope, :meta_key, :meta + attr_accessor :object, :scope, :root, :meta_key, :meta alias read_attribute_for_serialization send From 0c91564101ab61fb17d7365ec6e8ef847c48850a Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 5 Jul 2013 22:12:25 -0700 Subject: [PATCH 12/73] Remove method redefined warning --- lib/active_model/serializer.rb | 6 ++++-- test/fixtures/poro.rb | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 20c58e4a..7ce3f061 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -20,8 +20,10 @@ module ActiveModel @_attributes = attrs.map(&:to_s) attrs.each do |attr| - define_method attr do - object.read_attribute_for_serialization(attr) + unless method_defined?(attr) + define_method attr do + object.read_attribute_for_serialization(attr) + end end end end diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb index 4a143013..1b70ee4a 100644 --- a/test/fixtures/poro.rb +++ b/test/fixtures/poro.rb @@ -11,8 +11,6 @@ class Model end class ModelSerializer < ActiveModel::Serializer - attributes :attr1, :attr2 - def attr2 attr2 = object.read_attribute_for_serialization(:attr2) if scope @@ -21,4 +19,6 @@ class ModelSerializer < ActiveModel::Serializer attr2 end end + + attributes :attr1, :attr2 end From 0d4dfb69a931ad65a98edb32952aa059ed5ce2f2 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 25 Jul 2013 18:03:04 -0300 Subject: [PATCH 13/73] Fix directories the project is on top level dir --- CHANGELOG.md | 131 ++++++++++++++++++ MIT-LICENSE.txt | 21 +++ Rakefile | 13 ++ active_model_serializers.gemspec | 25 ++++ lib/active_model/serializer/version.rb | 5 + lib/active_model_serializers.rb | 7 +- .../action_controller/serialization_test.rb | 4 +- test/test_helper.rb | 4 +- .../serializer/attributes_test.rb | 6 +- .../unit/active_model/serializer/meta_test.rb | 6 +- .../unit/active_model/serializer/root_test.rb | 8 +- .../active_model/serializer/scope_test.rb | 4 +- .../serializer_support/active_record_test.rb | 6 +- .../serializer_support/poro_test.rb | 6 +- 14 files changed, 221 insertions(+), 25 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 MIT-LICENSE.txt create mode 100644 Rakefile create mode 100644 active_model_serializers.gemspec create mode 100644 lib/active_model/serializer/version.rb diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 00000000..87cabff9 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,131 @@ +# UNRELEASED + +* 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 + of the serialization and a serialize method that returns an object to + be converted to json by the module. This also removes duplicate code. + https://github.com/rails-api/active_model_serializers/commit/6c6bc8872d3b0f040a200854fa5530a775824dbf + +* ActiveModel::Serializer::Caching module was created it enables + Serializers to be able to cache to\_json and serialize calls. This + also helps removing duplicate code. + https://github.com/rails-api/active_model_serializers/commit/3e27110df78696ac48cafd1568f72216f348a188 + +* We got rid of the Association.refine method which generated + subclasses. + https://github.com/rails-api/active_model_serializers/commit/24923722d4f215c7cfcdf553fd16582e28e3801b + +* Associations doesn't know anymore about the source serializer. + That didn't make any sense. + https://github.com/rails-api/active_model_serializers/commit/2252e8fe6dbf45660c6a35f35e2423792f2c3abf + https://github.com/rails-api/active_model_serializers/commit/87eadd09b9a988bc1d9b30d9a501ef7e3fc6bb87 + https://github.com/rails-api/active_model_serializers/commit/79a6e13e8f7fae2eb4f48e83a9633e74beb6739e + +* Passing options[:hash] is not public API of include!. That was + removed. + https://github.com/rails-api/active_model_serializers/commit/5cbf9317051002a32c90c3f995b8b2f126f70d0c + +* ActiveModel::Serializer::Associations::Config is now + ActiveModel::Serializer::Association but it's an internal + thing so shouldn't bother. + ActiveModel::Serializer::Associations::Has\* are now + ActiveModel::Serializer::Association::Has\* and inherit from + ActiveModel::Serializer::Association + https://github.com/rails-api/active_model_serializers/commit/f5de334ddf1f3b9764d914a717311532021785d2 + https://github.com/rails-api/active_model_serializers/commit/3dd422d99e8c57f113880da34f6abe583c4dadf9 + +* serialize\_ids call methods on the corresponding serializer if they + are defined, instead of talking directly with the serialized object. + Serializers are decorators so we shouldn't talk directly with + serialized objects. + +* Array items are not wrapped anymore in root element. + +* embed and include were removed from AM::Serializer and there's a global config for them in AM::Serializers + + So code like ... + + class PostSerializer < ActiveModel::Serializer + embed :ids, :include => true + has_many :comments + end + + should be changed to ... + + class PostSerializer < ActiveModel::Serializer + has_many :comments, :embed => :ids, :include => true + end + + or you could change the global defaults by adding ... + + config.active\_model\_serializers.embed = :ids + config.active\_model\_serializers.include = true + + to the config/application.rb file + +# VERSION 0.8.1 + +* Fix bug whereby a serializer using 'options' would blow up. + +# VERSION 0.8.0 + +* Attributes can now have optional types. + +* A new DefaultSerializer ensures that POROs behave the same way as ActiveModels. + +* If you wish to override ActiveRecord::Base#to\_Json, you can now require + 'active\_record/serializer\_override'. We don't recommend you do this, but + many users do, so we've left it optional. + +* Fixed a bug where ActionController wouldn't always have MimeResponds. + +* An optinal caching feature allows you to cache JSON & hashes that AMS uses. + Adding 'cached true' to your Serializers will turn on this cache. + +* URL helpers used inside of Engines now work properly. + +* Serializers now can filter attributes with `only` and `except`: + + ``` + UserSerializer.new(user, only: [:first_name, :last_name]) + UserSerializer.new(user, except: :first_name) + ``` + +* Basic Mongoid support. We now include our mixins in the right place. + +* On Ruby 1.8, we now generate an `id` method that properly serializes `id` + columns. See issue #127 for more. + +* Add an alias for `scope` method to be the name of the context. By default + this is `current_user`. The name is automatically set when using + `serialization_scope` in the controller. + +* Pass through serialization options (such as `:include`) when a model + has no serializer defined. + +# VERSION 0.7.0 + +* ```embed_key``` option to allow embedding by attributes other than IDs +* Fix rendering nil with custom serializer +* Fix global ```self.root = false``` +* Add support for specifying the serializer for an association as a String +* Able to specify keys on the attributes method +* Serializer Reloading via ActiveSupport::DescendantsTracker +* Reduce double map to once; Fixes datamapper eager loading. + +# VERSION 0.6.0 + +* Serialize sets properly +* Add root option to ArraySerializer +* Support polymorphic associations +* Support :each_serializer in ArraySerializer +* Add `scope` method to easily access the scope in the serializer +* Fix regression with Rails 3.2.6; add Rails 4 support +* Allow serialization_scope to be disabled with serialization_scope nil +* Array serializer should support pure ruby objects besides serializers + +# VERSION 0.5.0 (May 16, 2012) + +* First tagged version +* Changes generators to always generate an ApplicationSerializer diff --git a/MIT-LICENSE.txt b/MIT-LICENSE.txt new file mode 100644 index 00000000..75e6db1a --- /dev/null +++ b/MIT-LICENSE.txt @@ -0,0 +1,21 @@ +Copyright (c) 2011-2012 José Valim & Yehuda Katz + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/Rakefile b/Rakefile new file mode 100644 index 00000000..0a1d1f00 --- /dev/null +++ b/Rakefile @@ -0,0 +1,13 @@ +#!/usr/bin/env rake +require "bundler/gem_tasks" +require "rake/testtask" + +desc 'Run tests' +Rake::TestTask.new(:test) do |t| + t.libs << 'lib' + t.libs << 'test' + t.pattern = 'test/**/*_test.rb' + t.verbose = true +end + +task :default => :test diff --git a/active_model_serializers.gemspec b/active_model_serializers.gemspec new file mode 100644 index 00000000..98105777 --- /dev/null +++ b/active_model_serializers.gemspec @@ -0,0 +1,25 @@ +# -*- encoding: utf-8 -*- + +$:.unshift File.expand_path("../lib", __FILE__) +require "active_model/serializer/version" + +Gem::Specification.new do |gem| + gem.authors = ["José Valim", "Yehuda Katz", "Santiago Pastorino"] + gem.email = ["jose.valim@gmail.com", "wycats@gmail.com", "santiago@wyeworks.com"] + gem.description = %q{Making it easy to serialize models for client-side use} + gem.summary = %q{Bringing consistency and object orientation to model serialization. Works great for client-side MVC frameworks!} + gem.homepage = "https://github.com/rails-api/active_model_serializers" + + gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } + gem.files = `git ls-files`.split("\n") + gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + gem.name = "active_model_serializers" + gem.require_paths = ["lib"] + gem.version = ActiveModel::Serializer::VERSION + + gem.add_dependency 'activemodel', '>= 3.0' + gem.add_development_dependency "rails", ">= 3.0" + gem.add_development_dependency "pry" + gem.add_development_dependency "simplecov" + gem.add_development_dependency "coveralls" +end diff --git a/lib/active_model/serializer/version.rb b/lib/active_model/serializer/version.rb new file mode 100644 index 00000000..10e62668 --- /dev/null +++ b/lib/active_model/serializer/version.rb @@ -0,0 +1,5 @@ +module ActiveModel + class Serializer + VERSION = "0.8.1" + end +end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 0e715c3c..1ffdce05 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -1,9 +1,10 @@ -require 'newbase/active_model/serializer' -require 'newbase/active_model/serializer_support' +require 'active_model' +require 'active_model/serializer' +require 'active_model/serializer_support' begin require 'action_controller' - require 'newbase/action_controller/serialization' + require 'action_controller/serialization' ActiveSupport.on_load(:action_controller) do include ::ActionController::Serialization diff --git a/test/integration/action_controller/serialization_test.rb b/test/integration/action_controller/serialization_test.rb index 037fd19d..553e4bb9 100644 --- a/test/integration/action_controller/serialization_test.rb +++ b/test/integration/action_controller/serialization_test.rb @@ -1,5 +1,5 @@ -require 'newbase/test_helper' -require 'newbase/active_model_serializers' +require 'test_helper' +require 'active_model_serializers' module ActionController module Serialization diff --git a/test/test_helper.rb b/test/test_helper.rb index e9ee164a..bb2eb5dc 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,7 +1,7 @@ require 'bundler/setup' -require 'newbase/active_model_serializers' +require 'active_model_serializers' require 'test/unit' -require 'newbase/fixtures/poro' +require 'fixtures/poro' module TestHelper Routes = ActionDispatch::Routing::RouteSet.new diff --git a/test/unit/active_model/serializer/attributes_test.rb b/test/unit/active_model/serializer/attributes_test.rb index eba7182f..7db8364d 100644 --- a/test/unit/active_model/serializer/attributes_test.rb +++ b/test/unit/active_model/serializer/attributes_test.rb @@ -1,11 +1,11 @@ -require 'newbase/test_helper' -require 'newbase/active_model/serializer' +require 'test_helper' +require 'active_model/serializer' module ActiveModel class Serializer class AttributesTest < ActiveModel::TestCase def setup - model = Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) + model = ::Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) @model_serializer = ModelSerializer.new(model) end diff --git a/test/unit/active_model/serializer/meta_test.rb b/test/unit/active_model/serializer/meta_test.rb index fdfbf584..85d592a6 100644 --- a/test/unit/active_model/serializer/meta_test.rb +++ b/test/unit/active_model/serializer/meta_test.rb @@ -1,12 +1,12 @@ -require 'newbase/test_helper' -require 'newbase/active_model/serializer' +require 'test_helper' +require 'active_model/serializer' module ActiveModel class Serializer class MetaTest < ActiveModel::TestCase def setup - @model = Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) + @model = ::Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) end def test_meta diff --git a/test/unit/active_model/serializer/root_test.rb b/test/unit/active_model/serializer/root_test.rb index bf513553..e9e700d7 100644 --- a/test/unit/active_model/serializer/root_test.rb +++ b/test/unit/active_model/serializer/root_test.rb @@ -1,5 +1,5 @@ -require 'newbase/test_helper' -require 'newbase/active_model/serializer' +require 'test_helper' +require 'active_model/serializer' module ActiveModel class Serializer @@ -7,7 +7,7 @@ module ActiveModel def setup @old_root = ModelSerializer._root - @model = Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) + @model = ::Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) @serializer = ModelSerializer.new(@model, root: 'initialize') ModelSerializer._root = true end @@ -40,7 +40,7 @@ module ActiveModel }, @serializer.as_json) end - def test_root_as_argument_takes_presedence + def test_root_as_argument_takes_precedence assert_equal({ 'argument' => { 'attr1' => 'value1', 'attr2' => 'value2' diff --git a/test/unit/active_model/serializer/scope_test.rb b/test/unit/active_model/serializer/scope_test.rb index 9ec106b6..30073cb1 100644 --- a/test/unit/active_model/serializer/scope_test.rb +++ b/test/unit/active_model/serializer/scope_test.rb @@ -1,5 +1,5 @@ -require 'newbase/test_helper' -require 'newbase/active_model/serializer' +require 'test_helper' +require 'active_model/serializer' module ActiveModel class Serializer diff --git a/test/unit/active_model/serializer_support/active_record_test.rb b/test/unit/active_model/serializer_support/active_record_test.rb index f58be6e1..af9d5ed4 100644 --- a/test/unit/active_model/serializer_support/active_record_test.rb +++ b/test/unit/active_model/serializer_support/active_record_test.rb @@ -1,6 +1,6 @@ -require 'newbase/test_helper' -require 'newbase/fixtures/active_record' -require 'newbase/active_model/serializer_support' +require 'test_helper' +require 'fixtures/active_record' +require 'active_model/serializer_support' module ActiveModel module SerializerSupport diff --git a/test/unit/active_model/serializer_support/poro_test.rb b/test/unit/active_model/serializer_support/poro_test.rb index fb212007..e16a1e78 100644 --- a/test/unit/active_model/serializer_support/poro_test.rb +++ b/test/unit/active_model/serializer_support/poro_test.rb @@ -1,11 +1,11 @@ -require 'newbase/test_helper' -require 'newbase/active_model/serializer_support' +require 'test_helper' +require 'active_model/serializer_support' module ActiveModel module SerializerSupport class Test < ActiveModel::TestCase def test_active_model_on_poro_returns_its_serializer - assert_equal ModelSerializer, Model.new.active_model_serializer + assert_equal ModelSerializer, ::Model.new.active_model_serializer end end end From 01bc5349767b456f6731bb3a5a374de157657f87 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 8 Aug 2013 17:58:01 -0300 Subject: [PATCH 14/73] Remove SerializerSupport --- lib/action_controller/serialization.rb | 3 +-- lib/active_model/serializer.rb | 4 ++++ lib/active_model/serializer_support.rb | 11 ----------- lib/active_model_serializers.rb | 7 ------- test/fixtures/poro.rb | 2 -- .../serializer_support/active_record_test.rb | 13 ------------- .../active_model/serializer_support/poro_test.rb | 12 ------------ 7 files changed, 5 insertions(+), 47 deletions(-) delete mode 100644 lib/active_model/serializer_support.rb delete mode 100644 test/unit/active_model/serializer_support/active_record_test.rb delete mode 100644 test/unit/active_model/serializer_support/poro_test.rb diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index 25decc32..a4faf972 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -60,8 +60,7 @@ module ActionController serializer = options.delete(:serializer) || - resource.respond_to?(:active_model_serializer) && - resource.active_model_serializer + ActiveModel::Serializer.serializer_for(resource) options[:scope] = serialization_scope unless options.has_key?(:scope) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 7ce3f061..aece7a47 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -5,6 +5,10 @@ module ActiveModel base._attributes = {} end + def serializer_for(resource) + "#{resource.class.name}Serializer".safe_constantize + end + attr_accessor :_root, :_attributes def root(root) diff --git a/lib/active_model/serializer_support.rb b/lib/active_model/serializer_support.rb deleted file mode 100644 index ed53c89a..00000000 --- a/lib/active_model/serializer_support.rb +++ /dev/null @@ -1,11 +0,0 @@ -require 'active_support/core_ext/string/inflections' - -module ActiveModel - module SerializerSupport - def active_model_serializer - "#{self.class.name}Serializer".safe_constantize - end - - alias read_attribute_for_serialization send - end -end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 1ffdce05..26d08553 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -1,6 +1,5 @@ require 'active_model' require 'active_model/serializer' -require 'active_model/serializer_support' begin require 'action_controller' @@ -12,9 +11,3 @@ begin rescue LoadError # rails not installed, continuing end - -[:active_record, :mongoid].each do |orm| - ActiveSupport.on_load(orm) do - include ActiveModel::SerializerSupport - end -end diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb index 1b70ee4a..4d5850be 100644 --- a/test/fixtures/poro.rb +++ b/test/fixtures/poro.rb @@ -1,6 +1,4 @@ class Model - include ActiveModel::SerializerSupport - def initialize(hash={}) @attributes = hash end diff --git a/test/unit/active_model/serializer_support/active_record_test.rb b/test/unit/active_model/serializer_support/active_record_test.rb deleted file mode 100644 index af9d5ed4..00000000 --- a/test/unit/active_model/serializer_support/active_record_test.rb +++ /dev/null @@ -1,13 +0,0 @@ -require 'test_helper' -require 'fixtures/active_record' -require 'active_model/serializer_support' - -module ActiveModel - module SerializerSupport - class Test < ActiveModel::TestCase - def test_active_model_returns_its_serializer - assert_equal ARModelSerializer, ARModel.new.active_model_serializer - end - end - end -end diff --git a/test/unit/active_model/serializer_support/poro_test.rb b/test/unit/active_model/serializer_support/poro_test.rb deleted file mode 100644 index e16a1e78..00000000 --- a/test/unit/active_model/serializer_support/poro_test.rb +++ /dev/null @@ -1,12 +0,0 @@ -require 'test_helper' -require 'active_model/serializer_support' - -module ActiveModel - module SerializerSupport - class Test < ActiveModel::TestCase - def test_active_model_on_poro_returns_its_serializer - assert_equal ModelSerializer, ::Model.new.active_model_serializer - end - end - end -end From d756ae4a70bda7f65b73105faf375326ef760960 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 9 Aug 2013 21:19:14 -0300 Subject: [PATCH 15/73] Implement has_one's and serialize_ids --- lib/active_model/serializer.rb | 48 +++++++++++++++++-- lib/active_model/serializer/associations.rb | 43 +++++++++++++++++ test/fixtures/poro.rb | 14 ++++++ .../active_model/serializer/has_one_test.rb | 35 ++++++++++++++ 4 files changed, 136 insertions(+), 4 deletions(-) create mode 100644 lib/active_model/serializer/associations.rb create mode 100644 test/unit/active_model/serializer/has_one_test.rb diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index aece7a47..ea3b4b6c 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -1,15 +1,18 @@ +require 'active_model/serializer/associations' + module ActiveModel class Serializer class << self def inherited(base) - base._attributes = {} + base._attributes = [] + base._associations = [] end def serializer_for(resource) "#{resource.class.name}Serializer".safe_constantize end - attr_accessor :_root, :_attributes + attr_accessor :_root, :_attributes, :_associations def root(root) @_root = root @@ -21,7 +24,7 @@ module ActiveModel end def attributes(*attrs) - @_attributes = attrs.map(&:to_s) + @_attributes.concat attrs.map(&:to_s) attrs.each do |attr| unless method_defined?(attr) @@ -31,6 +34,22 @@ module ActiveModel end end end + + def has_one(*attrs) + options = attrs.extract_options! + + attrs.each do |attr| + attr = attr.to_s + + unless method_defined?(attr) + define_method attr do + object.send attr + end + end + + @_associations << Association::HasOne.new(attr, options) + end + end end def initialize(object, options={}) @@ -51,9 +70,30 @@ module ActiveModel end end + def associations + self.class._associations.each_with_object({}) do |association, hash| + if association.embed_ids? + hash[association.key] = serialize_ids association + elsif association.embed_objects? + # TODO + hash + end + end + end + + def serialize_ids(association) + associated_data = send(association.name) + if associated_data.respond_to?(:to_ary) + associated_data.map { |elem| elem.send(association.embed_key) } + else + associated_data.send(association.embed_key) + end + end + def serializable_hash(options={}) return nil if object.nil? - attributes + hash = attributes + hash.merge! associations end def as_json(options={}) diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb new file mode 100644 index 00000000..a8bb4167 --- /dev/null +++ b/lib/active_model/serializer/associations.rb @@ -0,0 +1,43 @@ +module ActiveModel + class Serializer + class Association + def initialize(name, options={}) + @name = name + @options = options + + self.embed = options[:embed] + @embed_key = options[:embed_key] || :id + @include = options[:include] + + serializer = @options[:serializer] + @serializer_class = serializer.is_a?(String) ? serializer.constantize : serializer + end + + attr_reader :name, :embed_ids, :embed_objects, :embed_key, :include + alias embed_ids? embed_ids + alias embed_objects? embed_objects + alias include? include + + def embed=(embed) + @embed_ids = embed == :id || embed == :ids + @embed_objects = embed == :object || embed == :objects + end + + def build_serializer(object) + @serializer_class ||= Serializer.serializer_for(object) + + if @serializer_class + @serializer_class.new(object, @options) + else + object + end + end + + class HasOne < Association + def key + "#{name}_id" + end + end + end + end +end diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb index 4d5850be..10586934 100644 --- a/test/fixtures/poro.rb +++ b/test/fixtures/poro.rb @@ -6,6 +6,14 @@ class Model def read_attribute_for_serialization(name) @attributes[name] end + + def model + @model ||= Model.new(attr1: 'v1', attr2: 'v2') + end + + def id + object_id + end end class ModelSerializer < ActiveModel::Serializer @@ -20,3 +28,9 @@ class ModelSerializer < ActiveModel::Serializer attributes :attr1, :attr2 end + +class AnotherSerializer < ActiveModel::Serializer + attributes :attr2, :attr3 + + has_one :model +end diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb new file mode 100644 index 00000000..652ed3e4 --- /dev/null +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -0,0 +1,35 @@ +require 'test_helper' +require 'active_model/serializer' + +module ActiveModel + class Serializer + class HasOneTest < ActiveModel::TestCase + def setup + @model = ::Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) + @model_serializer = AnotherSerializer.new(@model) + end + + def test_associations_definition + associations = @model_serializer.class._associations + + assert_equal 1, associations.length + assert_kind_of Association::HasOne, associations[0] + assert_equal 'model', associations[0].name + end + + def test_associations_serialization_using_serializable_hash + @model_serializer.class._associations[0].embed = :ids + assert_equal({ + 'attr2' => 'value2', 'attr3' => 'value3', 'model_id' => @model.model.object_id + }, @model_serializer.serializable_hash) + end + + def test_associations_serialization_using_as_json + @model_serializer.class._associations[0].embed = :ids + assert_equal({ + 'attr2' => 'value2', 'attr3' => 'value3', 'model_id' => @model.model.object_id + }, @model_serializer.as_json) + end + end + end +end From 52bb3f692934884e30ea02f074c2b460fb51c42f Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 13 Aug 2013 18:12:00 -0300 Subject: [PATCH 16/73] Implement has_one's embed objects --- lib/active_model/serializer.rb | 4 ++-- lib/active_model/serializer/associations.rb | 4 ++++ .../active_model/serializer/has_one_test.rb | 18 ++++++++++++++++-- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index ea3b4b6c..ed8c9a65 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -75,8 +75,8 @@ module ActiveModel if association.embed_ids? hash[association.key] = serialize_ids association elsif association.embed_objects? - # TODO - hash + associated_data = send(association.name) + hash[association.embedded_key] = association.build_serializer(associated_data).serializable_hash end end end diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index a8bb4167..acded95f 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -37,6 +37,10 @@ module ActiveModel def key "#{name}_id" end + + def embedded_key + name + end end end end diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index 652ed3e4..43889b2c 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -17,19 +17,33 @@ module ActiveModel assert_equal 'model', associations[0].name end - def test_associations_serialization_using_serializable_hash + def test_associations_embedding_ids_serialization_using_serializable_hash @model_serializer.class._associations[0].embed = :ids assert_equal({ 'attr2' => 'value2', 'attr3' => 'value3', 'model_id' => @model.model.object_id }, @model_serializer.serializable_hash) end - def test_associations_serialization_using_as_json + def test_associations_embedding_ids_serialization_using_as_json @model_serializer.class._associations[0].embed = :ids assert_equal({ 'attr2' => 'value2', 'attr3' => 'value3', 'model_id' => @model.model.object_id }, @model_serializer.as_json) end + + def test_associations_embedding_objects_serialization_using_serializable_hash + @model_serializer.class._associations[0].embed = :objects + assert_equal({ + 'attr2' => 'value2', 'attr3' => 'value3', 'model' => { 'attr1' => 'v1', 'attr2' => 'v2' } + }, @model_serializer.serializable_hash) + end + + def test_associations_embedding_objects_serialization_using_as_json + @model_serializer.class._associations[0].embed = :objects + assert_equal({ + 'attr2' => 'value2', 'attr3' => 'value3', 'model' => { 'attr1' => 'v1', 'attr2' => 'v2' } + }, @model_serializer.as_json) + end end end end From 516f5bdcebc3d501610909bc253c8d67d319b8b0 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 14 Aug 2013 19:45:05 -0300 Subject: [PATCH 17/73] Implement has_one's embed ids include true --- lib/active_model/serializer.rb | 3 ++- lib/active_model/serializer/associations.rb | 7 ++++++- .../active_model/serializer/has_one_test.rb | 18 ++++++++++++++++-- 3 files changed, 24 insertions(+), 4 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index ed8c9a65..c7889407 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -74,7 +74,8 @@ module ActiveModel self.class._associations.each_with_object({}) do |association, hash| if association.embed_ids? hash[association.key] = serialize_ids association - elsif association.embed_objects? + end + if association.embed_objects? associated_data = send(association.name) hash[association.embedded_key] = association.build_serializer(associated_data).serializable_hash end diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index acded95f..e04ca09e 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -13,7 +13,8 @@ module ActiveModel @serializer_class = serializer.is_a?(String) ? serializer.constantize : serializer end - attr_reader :name, :embed_ids, :embed_objects, :embed_key, :include + attr_reader :name, :embed_ids, :embed_objects, :embed_key + attr_accessor :include alias embed_ids? embed_ids alias embed_objects? embed_objects alias include? include @@ -23,6 +24,10 @@ module ActiveModel @embed_objects = embed == :object || embed == :objects end + def embed_objects? + @embed_objects || @embed_ids && @include + end + def build_serializer(object) @serializer_class ||= Serializer.serializer_for(object) diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index 43889b2c..ea68d6dd 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -7,6 +7,8 @@ module ActiveModel def setup @model = ::Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) @model_serializer = AnotherSerializer.new(@model) + @model_serializer.class._associations[0].include = false + @model_serializer.class._associations[0].embed = :ids end def test_associations_definition @@ -18,14 +20,12 @@ module ActiveModel end def test_associations_embedding_ids_serialization_using_serializable_hash - @model_serializer.class._associations[0].embed = :ids assert_equal({ 'attr2' => 'value2', 'attr3' => 'value3', 'model_id' => @model.model.object_id }, @model_serializer.serializable_hash) end def test_associations_embedding_ids_serialization_using_as_json - @model_serializer.class._associations[0].embed = :ids assert_equal({ 'attr2' => 'value2', 'attr3' => 'value3', 'model_id' => @model.model.object_id }, @model_serializer.as_json) @@ -44,6 +44,20 @@ module ActiveModel 'attr2' => 'value2', 'attr3' => 'value3', 'model' => { 'attr1' => 'v1', 'attr2' => 'v2' } }, @model_serializer.as_json) end + + def test_associations_embedding_ids_including_objects_serialization_using_serializable_hash + @model_serializer.class._associations[0].include = true + assert_equal({ + 'attr2' => 'value2', 'attr3' => 'value3', 'model_id' => @model.model.object_id, 'model' => { 'attr1' => 'v1', 'attr2' => 'v2' } + }, @model_serializer.serializable_hash) + end + + def test_associations_embedding_ids_including_objects_serialization_using_as_json + @model_serializer.class._associations[0].include = true + assert_equal({ + 'attr2' => 'value2', 'attr3' => 'value3', 'model_id' => @model.model.object_id, 'model' => { 'attr1' => 'v1', 'attr2' => 'v2' } + }, @model_serializer.as_json) + end end end end From 4c7599cfffecc550cbfe812d499f705994176cee Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 16 Aug 2013 19:16:46 -0300 Subject: [PATCH 18/73] Use more meaningful model names for tests --- test/fixtures/poro.rb | 47 +++++++++------ .../action_controller/serialization_test.rb | 16 ++--- .../serializer/attributes_test.rb | 16 ++--- .../active_model/serializer/has_one_test.rb | 44 +++++++------- .../unit/active_model/serializer/meta_test.rb | 23 ++++---- .../unit/active_model/serializer/root_test.rb | 58 ++++++++----------- .../active_model/serializer/scope_test.rb | 2 +- 7 files changed, 102 insertions(+), 104 deletions(-) diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb index 10586934..7f39f38c 100644 --- a/test/fixtures/poro.rb +++ b/test/fixtures/poro.rb @@ -7,30 +7,39 @@ class Model @attributes[name] end - def model - @model ||= Model.new(attr1: 'v1', attr2: 'v2') - end - def id object_id end end -class ModelSerializer < ActiveModel::Serializer - def attr2 - attr2 = object.read_attribute_for_serialization(:attr2) - if scope - attr2 + '-' + scope - else - attr2 - end + +### +## Models +### +class User < Model + def profile + @profile ||= Profile.new(name: 'N1', description: 'D1') + end +end + +class Profile < Model +end + + +### +## Serializers +### +class UserSerializer < ActiveModel::Serializer + attributes :name, :email + + has_one :profile +end + +class ProfileSerializer < ActiveModel::Serializer + def description + description = object.read_attribute_for_serialization(:description) + scope ? "#{description} - #{scope}" : description end - attributes :attr1, :attr2 -end - -class AnotherSerializer < ActiveModel::Serializer - attributes :attr2, :attr3 - - has_one :model + attributes :name, :description end diff --git a/test/integration/action_controller/serialization_test.rb b/test/integration/action_controller/serialization_test.rb index 553e4bb9..b79330f0 100644 --- a/test/integration/action_controller/serialization_test.rb +++ b/test/integration/action_controller/serialization_test.rb @@ -6,7 +6,7 @@ module ActionController class ImplicitSerializerTest < ActionController::TestCase class MyController < ActionController::Base def render_using_implicit_serializer - render :json => Model.new(attr1: 'value1', attr2: 'value2', attr3: 'value3') + render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) end end @@ -15,14 +15,14 @@ module ActionController def test_render_using_implicit_serializer get :render_using_implicit_serializer assert_equal 'application/json', @response.content_type - assert_equal '{"attr1":"value1","attr2":"value2"}', @response.body + assert_equal '{"name":"Name 1","description":"Description 1"}', @response.body end end class ImplicitSerializerScopeTest < ActionController::TestCase class MyController < ActionController::Base def render_using_implicit_serializer_and_scope - render :json => Model.new(attr1: 'value1', attr2: 'value2', attr3: 'value3') + render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) end private @@ -37,14 +37,14 @@ module ActionController def test_render_using_implicit_serializer_and_scope get :render_using_implicit_serializer_and_scope assert_equal 'application/json', @response.content_type - assert_equal '{"attr1":"value1","attr2":"value2-current_user"}', @response.body + assert_equal '{"name":"Name 1","description":"Description 1 - current_user"}', @response.body end end class ExplicitSerializerScopeTest < ActionController::TestCase class MyController < ActionController::Base def render_using_implicit_serializer_and_explicit_scope - render json: Model.new(attr1: 'value1', attr2: 'value2', attr3: 'value3'), scope: current_admin + render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }), scope: current_admin end private @@ -63,14 +63,14 @@ module ActionController def test_render_using_implicit_serializer_and_explicit_scope get :render_using_implicit_serializer_and_explicit_scope assert_equal 'application/json', @response.content_type - assert_equal '{"attr1":"value1","attr2":"value2-current_admin"}', @response.body + assert_equal '{"name":"Name 1","description":"Description 1 - current_admin"}', @response.body end end class OverridingSerializationScopeTest < ActionController::TestCase class MyController < ActionController::Base def render_overriding_serialization_scope - render json: Model.new(attr1: 'value1', attr2: 'value2', attr3: 'value3') + render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) end private @@ -89,7 +89,7 @@ module ActionController def test_render_overriding_serialization_scope get :render_overriding_serialization_scope assert_equal 'application/json', @response.content_type - assert_equal '{"attr1":"value1","attr2":"value2-current_admin"}', @response.body + assert_equal '{"name":"Name 1","description":"Description 1 - current_admin"}', @response.body end end end diff --git a/test/unit/active_model/serializer/attributes_test.rb b/test/unit/active_model/serializer/attributes_test.rb index 7db8364d..bc02f714 100644 --- a/test/unit/active_model/serializer/attributes_test.rb +++ b/test/unit/active_model/serializer/attributes_test.rb @@ -5,25 +5,25 @@ module ActiveModel class Serializer class AttributesTest < ActiveModel::TestCase def setup - model = ::Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) - @model_serializer = ModelSerializer.new(model) + @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + @profile_serializer = ProfileSerializer.new(@profile) end def test_attributes_definition - assert_equal(['attr1', 'attr2'], - @model_serializer.class._attributes) + assert_equal(['name', 'description'], + @profile_serializer.class._attributes) end def test_attributes_serialization_using_serializable_hash assert_equal({ - 'attr1' => 'value1', 'attr2' => 'value2' - }, @model_serializer.serializable_hash) + 'name' => 'Name 1', 'description' => 'Description 1' + }, @profile_serializer.serializable_hash) end def test_attributes_serialization_using_as_json assert_equal({ - 'attr1' => 'value1', 'attr2' => 'value2' - }, @model_serializer.as_json) + 'name' => 'Name 1', 'description' => 'Description 1' + }, @profile_serializer.as_json) end end end diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index ea68d6dd..c2e770f2 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -5,58 +5,58 @@ module ActiveModel class Serializer class HasOneTest < ActiveModel::TestCase def setup - @model = ::Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) - @model_serializer = AnotherSerializer.new(@model) - @model_serializer.class._associations[0].include = false - @model_serializer.class._associations[0].embed = :ids + @user = User.new({ name: 'Name 1', email: 'mail@server.com', gender: 'M' }) + @user_serializer = UserSerializer.new(@user) + @user_serializer.class._associations[0].include = false + @user_serializer.class._associations[0].embed = :ids end def test_associations_definition - associations = @model_serializer.class._associations + associations = @user_serializer.class._associations assert_equal 1, associations.length assert_kind_of Association::HasOne, associations[0] - assert_equal 'model', associations[0].name + assert_equal 'profile', associations[0].name end def test_associations_embedding_ids_serialization_using_serializable_hash assert_equal({ - 'attr2' => 'value2', 'attr3' => 'value3', 'model_id' => @model.model.object_id - }, @model_serializer.serializable_hash) + 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id + }, @user_serializer.serializable_hash) end def test_associations_embedding_ids_serialization_using_as_json assert_equal({ - 'attr2' => 'value2', 'attr3' => 'value3', 'model_id' => @model.model.object_id - }, @model_serializer.as_json) + 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id + }, @user_serializer.as_json) end def test_associations_embedding_objects_serialization_using_serializable_hash - @model_serializer.class._associations[0].embed = :objects + @user_serializer.class._associations[0].embed = :objects assert_equal({ - 'attr2' => 'value2', 'attr3' => 'value3', 'model' => { 'attr1' => 'v1', 'attr2' => 'v2' } - }, @model_serializer.serializable_hash) + 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile' => { 'name' => 'N1', 'description' => 'D1' } + }, @user_serializer.serializable_hash) end def test_associations_embedding_objects_serialization_using_as_json - @model_serializer.class._associations[0].embed = :objects + @user_serializer.class._associations[0].embed = :objects assert_equal({ - 'attr2' => 'value2', 'attr3' => 'value3', 'model' => { 'attr1' => 'v1', 'attr2' => 'v2' } - }, @model_serializer.as_json) + 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile' => { 'name' => 'N1', 'description' => 'D1' } + }, @user_serializer.as_json) end def test_associations_embedding_ids_including_objects_serialization_using_serializable_hash - @model_serializer.class._associations[0].include = true + @user_serializer.class._associations[0].include = true assert_equal({ - 'attr2' => 'value2', 'attr3' => 'value3', 'model_id' => @model.model.object_id, 'model' => { 'attr1' => 'v1', 'attr2' => 'v2' } - }, @model_serializer.serializable_hash) + 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id, 'profile' => { 'name' => 'N1', 'description' => 'D1' } + }, @user_serializer.serializable_hash) end def test_associations_embedding_ids_including_objects_serialization_using_as_json - @model_serializer.class._associations[0].include = true + @user_serializer.class._associations[0].include = true assert_equal({ - 'attr2' => 'value2', 'attr3' => 'value3', 'model_id' => @model.model.object_id, 'model' => { 'attr1' => 'v1', 'attr2' => 'v2' } - }, @model_serializer.as_json) + 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id, 'profile' => { 'name' => 'N1', 'description' => 'D1' } + }, @user_serializer.as_json) end end end diff --git a/test/unit/active_model/serializer/meta_test.rb b/test/unit/active_model/serializer/meta_test.rb index 85d592a6..73f843de 100644 --- a/test/unit/active_model/serializer/meta_test.rb +++ b/test/unit/active_model/serializer/meta_test.rb @@ -1,40 +1,39 @@ require 'test_helper' require 'active_model/serializer' - module ActiveModel class Serializer class MetaTest < ActiveModel::TestCase def setup - @model = ::Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) + @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) end def test_meta - model_serializer = ModelSerializer.new(@model, root: 'model', meta: { 'total' => 10 }) + profile_serializer = ProfileSerializer.new(@profile, root: 'profile', meta: { 'total' => 10 }) assert_equal({ - 'model' => { - 'attr1' => 'value1', - 'attr2' => 'value2' + 'profile' => { + 'name' => 'Name 1', + 'description' => 'Description 1' }, 'meta' => { 'total' => 10 } - }, model_serializer.as_json) + }, profile_serializer.as_json) end def test_meta_using_meta_key - model_serializer = ModelSerializer.new(@model, root: 'model', meta_key: :my_meta, my_meta: { 'total' => 10 }) + profile_serializer = ProfileSerializer.new(@profile, root: 'profile', meta_key: :my_meta, my_meta: { 'total' => 10 }) assert_equal({ - 'model' => { - 'attr1' => 'value1', - 'attr2' => 'value2' + 'profile' => { + 'name' => 'Name 1', + 'description' => 'Description 1' }, 'my_meta' => { 'total' => 10 } - }, model_serializer.as_json) + }, profile_serializer.as_json) end end end diff --git a/test/unit/active_model/serializer/root_test.rb b/test/unit/active_model/serializer/root_test.rb index e9e700d7..cad83cc7 100644 --- a/test/unit/active_model/serializer/root_test.rb +++ b/test/unit/active_model/serializer/root_test.rb @@ -4,38 +4,37 @@ require 'active_model/serializer' module ActiveModel class Serializer class RootAsOptionTest < ActiveModel::TestCase - def setup - @old_root = ModelSerializer._root - @model = ::Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) - @serializer = ModelSerializer.new(@model, root: 'initialize') - ModelSerializer._root = true + @old_root = ProfileSerializer._root + @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + @serializer = ProfileSerializer.new(@profile, root: 'initialize') + ProfileSerializer._root = true end def teardown - ModelSerializer._root = @old_root + ProfileSerializer._root = @old_root end def test_root_is_not_displayed_using_serializable_hash assert_equal({ - 'attr1' => 'value1', 'attr2' => 'value2' + 'name' => 'Name 1', 'description' => 'Description 1' }, @serializer.serializable_hash) end def test_root_using_as_json assert_equal({ 'initialize' => { - 'attr1' => 'value1', 'attr2' => 'value2' + 'name' => 'Name 1', 'description' => 'Description 1' } }, @serializer.as_json) end def test_root_from_serializer_name - @serializer = ModelSerializer.new(@model) + @serializer = ProfileSerializer.new(@profile) assert_equal({ - 'model' => { - 'attr1' => 'value1', 'attr2' => 'value2' + 'profile' => { + 'name' => 'Name 1', 'description' => 'Description 1' } }, @serializer.as_json) end @@ -43,44 +42,35 @@ module ActiveModel def test_root_as_argument_takes_precedence assert_equal({ 'argument' => { - 'attr1' => 'value1', 'attr2' => 'value2' + 'name' => 'Name 1', 'description' => 'Description 1' } }, @serializer.as_json(root: 'argument')) end end class RootInSerializerTest < ActiveModel::TestCase - class Model - def initialize(hash={}) - @attributes = hash - end - - def read_attribute_for_serialization(name) - @attributes[name] - end - end - - class ModelSerializer < ActiveModel::Serializer - root :in_serializer - attributes :attr1, :attr2 - end - def setup - model = Model.new({ :attr1 => 'value1', :attr2 => 'value2', :attr3 => 'value3' }) - @serializer = ModelSerializer.new(model) - @rooted_serializer = ModelSerializer.new(model, root: :initialize) + @old_root = ProfileSerializer._root + ProfileSerializer._root = 'in_serializer' + profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + @serializer = ProfileSerializer.new(profile) + @rooted_serializer = ProfileSerializer.new(profile, root: :initialize) + end + + def teardown + ProfileSerializer._root = @old_root end def test_root_is_not_displayed_using_serializable_hash assert_equal({ - 'attr1' => 'value1', 'attr2' => 'value2' + 'name' => 'Name 1', 'description' => 'Description 1' }, @serializer.serializable_hash) end def test_root_using_as_json assert_equal({ 'in_serializer' => { - 'attr1' => 'value1', 'attr2' => 'value2' + 'name' => 'Name 1', 'description' => 'Description 1' } }, @serializer.as_json) end @@ -88,7 +78,7 @@ module ActiveModel def test_root_in_initializer_takes_precedence assert_equal({ 'initialize' => { - 'attr1' => 'value1', 'attr2' => 'value2' + 'name' => 'Name 1', 'description' => 'Description 1' } }, @rooted_serializer.as_json) end @@ -96,7 +86,7 @@ module ActiveModel def test_root_as_argument_takes_precedence assert_equal({ 'argument' => { - 'attr1' => 'value1', 'attr2' => 'value2' + 'name' => 'Name 1', 'description' => 'Description 1' } }, @rooted_serializer.as_json(root: :argument)) end diff --git a/test/unit/active_model/serializer/scope_test.rb b/test/unit/active_model/serializer/scope_test.rb index 30073cb1..4b67190c 100644 --- a/test/unit/active_model/serializer/scope_test.rb +++ b/test/unit/active_model/serializer/scope_test.rb @@ -5,7 +5,7 @@ module ActiveModel class Serializer class ScopeTest < ActiveModel::TestCase def setup - @serializer = ModelSerializer.new(nil, scope: current_user) + @serializer = ProfileSerializer.new(nil, scope: current_user) end def test_scope From fa61314d0eec249c8097f27efb17743eba4d3d78 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 16 Aug 2013 19:23:23 -0300 Subject: [PATCH 19/73] Add AR integration tests --- test/fixtures/active_record.rb | 13 ++++---- .../active_record/active_record_test.rb | 31 +++++++++++++++++++ 2 files changed, 38 insertions(+), 6 deletions(-) create mode 100644 test/integration/active_record/active_record_test.rb diff --git a/test/fixtures/active_record.rb b/test/fixtures/active_record.rb index 05ae73a4..9cdf055a 100644 --- a/test/fixtures/active_record.rb +++ b/test/fixtures/active_record.rb @@ -6,15 +6,16 @@ ActiveRecord::Base.establish_connection( ) ActiveRecord::Schema.define do - create_table :ar_models, :force => true do |t| - t.string :attr1 - t.string :attr2 + create_table :ar_profiles, :force => true do |t| + t.string :name + t.string :description + t.string :comments end end -class ARModel < ActiveRecord::Base +class ARProfile < ActiveRecord::Base end -class ARModelSerializer < ActiveModel::Serializer - attributes :attr1, :attr2 +class ARProfileSerializer < ActiveModel::Serializer + attributes :name, :description end diff --git a/test/integration/active_record/active_record_test.rb b/test/integration/active_record/active_record_test.rb new file mode 100644 index 00000000..9529da1e --- /dev/null +++ b/test/integration/active_record/active_record_test.rb @@ -0,0 +1,31 @@ +require 'test_helper' +require 'fixtures/active_record' +require 'active_model/serializer' + +module ActiveModel + class Serializer + class ActiveRecordTest < ActiveModel::TestCase + def setup + @profile = ARProfile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + @profile_serializer = ARProfileSerializer.new(@profile) + end + + def test_attributes_definition + assert_equal(['name', 'description'], + @profile_serializer.class._attributes) + end + + def test_attributes_serialization_using_serializable_hash + assert_equal({ + 'name' => 'Name 1', 'description' => 'Description 1' + }, @profile_serializer.serializable_hash) + end + + def test_attributes_serialization_using_as_json + assert_equal({ + 'name' => 'Name 1', 'description' => 'Description 1' + }, @profile_serializer.as_json) + end + end + end +end From 61a1669a86295ac850b77305484d881edcc0b25c Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 16 Aug 2013 23:14:59 -0300 Subject: [PATCH 20/73] Implement has_many --- lib/active_model/serializer.rb | 24 ++++++- lib/active_model/serializer/associations.rb | 10 +++ test/fixtures/poro.rb | 19 ++++++ .../active_model/serializer/has_many_test.rb | 63 +++++++++++++++++++ 4 files changed, 113 insertions(+), 3 deletions(-) create mode 100644 test/unit/active_model/serializer/has_many_test.rb diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index c7889407..cabead2d 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -36,6 +36,16 @@ module ActiveModel end def has_one(*attrs) + associate(Association::HasOne, *attrs) + end + + def has_many(*attrs) + associate(Association::HasMany, *attrs) + end + + private + + def associate(klass, *attrs) options = attrs.extract_options! attrs.each do |attr| @@ -47,7 +57,7 @@ module ActiveModel end end - @_associations << Association::HasOne.new(attr, options) + @_associations << klass.new(attr, options) end end end @@ -76,12 +86,20 @@ module ActiveModel hash[association.key] = serialize_ids association end if association.embed_objects? - associated_data = send(association.name) - hash[association.embedded_key] = association.build_serializer(associated_data).serializable_hash + hash[association.embedded_key] = serialize association end end end + def serialize(association) + associated_data = send(association.name) + if associated_data.respond_to?(:to_ary) + associated_data.map { |elem| association.build_serializer(elem).serializable_hash } + else + association.build_serializer(associated_data).serializable_hash + end + end + def serialize_ids(association) associated_data = send(association.name) if associated_data.respond_to?(:to_ary) diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index e04ca09e..aab18b85 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -47,6 +47,16 @@ module ActiveModel name end end + + class HasMany < Association + def key + "#{name.singularize}_ids" + end + + def embedded_key + name + end + end end end end diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb index 7f39f38c..e9d41da3 100644 --- a/test/fixtures/poro.rb +++ b/test/fixtures/poro.rb @@ -25,6 +25,15 @@ end class Profile < Model end +class Post < Model + def comments + @comments ||= [Comment.new(content: 'C1'), + Comment.new(content: 'C2')] + end +end + +class Comment < Model +end ### ## Serializers @@ -43,3 +52,13 @@ class ProfileSerializer < ActiveModel::Serializer attributes :name, :description end + +class PostSerializer < ActiveModel::Serializer + attributes :title, :body + + has_many :comments +end + +class CommentSerializer < ActiveModel::Serializer + attributes :content +end diff --git a/test/unit/active_model/serializer/has_many_test.rb b/test/unit/active_model/serializer/has_many_test.rb new file mode 100644 index 00000000..7dd05e50 --- /dev/null +++ b/test/unit/active_model/serializer/has_many_test.rb @@ -0,0 +1,63 @@ +require 'test_helper' +require 'active_model/serializer' + +module ActiveModel + class Serializer + class HasManyTest < ActiveModel::TestCase + def setup + @post = Post.new({ title: 'Title 1', body: 'Body 1', date: '1/1/2000' }) + @post_serializer = PostSerializer.new(@post) + @post_serializer.class._associations[0].include = false + @post_serializer.class._associations[0].embed = :ids + end + + def test_associations_definition + associations = @post_serializer.class._associations + + assert_equal 1, associations.length + assert_kind_of Association::HasMany, associations[0] + assert_equal 'comments', associations[0].name + end + + def test_associations_embedding_ids_serialization_using_serializable_hash + assert_equal({ + 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } + }, @post_serializer.serializable_hash) + end + + def test_associations_embedding_ids_serialization_using_as_json + assert_equal({ + 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } + }, @post_serializer.as_json) + end + + def test_associations_embedding_objects_serialization_using_serializable_hash + @post_serializer.class._associations[0].embed = :objects + assert_equal({ + 'title' => 'Title 1', 'body' => 'Body 1', 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] + }, @post_serializer.serializable_hash) + end + + def test_associations_embedding_objects_serialization_using_as_json + @post_serializer.class._associations[0].embed = :objects + assert_equal({ + 'title' => 'Title 1', 'body' => 'Body 1', 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] + }, @post_serializer.as_json) + end + + def test_associations_embedding_ids_including_objects_serialization_using_serializable_hash + @post_serializer.class._associations[0].include = true + assert_equal({ + 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id }, 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] + }, @post_serializer.serializable_hash) + end + + def test_associations_embedding_ids_including_objects_serialization_using_as_json + @post_serializer.class._associations[0].include = true + assert_equal({ + 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id }, 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] + }, @post_serializer.as_json) + end + end + end +end From 7e83f0c29dce3537c23005d6431df86b0ca7052d Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 19 Aug 2013 16:55:03 -0300 Subject: [PATCH 21/73] Implement ArraySerializer --- lib/active_model/array_serializer.rb | 19 +++++++++++ lib/active_model/serializer.rb | 7 +++- lib/active_model_serializers.rb | 1 + .../active_model/array_serializer_test.rb | 32 +++++++++++++++++++ 4 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 lib/active_model/array_serializer.rb create mode 100644 test/unit/active_model/array_serializer_test.rb diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb new file mode 100644 index 00000000..083a1def --- /dev/null +++ b/lib/active_model/array_serializer.rb @@ -0,0 +1,19 @@ +module ActiveModel + class ArraySerializer + def initialize(object, options={}) + @object = object + @options = options + end + + def serializable_array + @object.map do |item| + if serializer = Serializer.serializer_for(item) + serializer.new(item).serializable_object(@options.merge(root: nil)) + else + item.as_json + end + end + end + alias serializable_object serializable_array + end +end diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index cabead2d..940d59fa 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -9,7 +9,11 @@ module ActiveModel end def serializer_for(resource) - "#{resource.class.name}Serializer".safe_constantize + if resource.respond_to?(:to_ary) + ArraySerializer + else + "#{resource.class.name}Serializer".safe_constantize + end end attr_accessor :_root, :_attributes, :_associations @@ -114,6 +118,7 @@ module ActiveModel hash = attributes hash.merge! associations end + alias serializable_object serializable_hash def as_json(options={}) if root = options[:root] || self.root diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 26d08553..5ecc1b06 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -1,5 +1,6 @@ require 'active_model' require 'active_model/serializer' +require 'active_model/array_serializer' begin require 'action_controller' diff --git a/test/unit/active_model/array_serializer_test.rb b/test/unit/active_model/array_serializer_test.rb new file mode 100644 index 00000000..9a77f3fb --- /dev/null +++ b/test/unit/active_model/array_serializer_test.rb @@ -0,0 +1,32 @@ +require 'test_helper' +require 'active_model/serializer' + +module ActiveModel + class ArraySerializer + class Test < ActiveModel::TestCase + def setup + array = [1, 2, 3] + @serializer = ActiveModel::Serializer.serializer_for(array).new(array) + end + + def test_serializer_for_array_returns_appropriate_type + assert_kind_of ArraySerializer, @serializer + end + + def test_array_serializer_serializes_simple_objects + assert_equal [1, 2, 3], @serializer.serializable_array + end + + def test_array_serializer_serializes_models + array = [Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }), + Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' })] + serializer = ArraySerializer.new(array) + + expected = [{'name' => 'Name 1', 'description' => 'Description 1'}, + {'name' => 'Name 2', 'description' => 'Description 2'}] + + assert_equal expected, serializer.serializable_array + end + end + end +end From f647b7ae74ce898d14ebbc298c01662016380818 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 19 Aug 2013 17:26:07 -0300 Subject: [PATCH 22/73] Implement each_serializer --- lib/active_model/array_serializer.rb | 3 ++- test/unit/active_model/array_serializer_test.rb | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb index 083a1def..6a56262e 100644 --- a/lib/active_model/array_serializer.rb +++ b/lib/active_model/array_serializer.rb @@ -7,7 +7,8 @@ module ActiveModel def serializable_array @object.map do |item| - if serializer = Serializer.serializer_for(item) + serializer = @options[:each_serializer] || Serializer.serializer_for(item) + if serializer serializer.new(item).serializable_object(@options.merge(root: nil)) else item.as_json diff --git a/test/unit/active_model/array_serializer_test.rb b/test/unit/active_model/array_serializer_test.rb index 9a77f3fb..25ede76b 100644 --- a/test/unit/active_model/array_serializer_test.rb +++ b/test/unit/active_model/array_serializer_test.rb @@ -27,6 +27,17 @@ module ActiveModel assert_equal expected, serializer.serializable_array end + + def test_array_serializers_each_serializer + array = [::Model.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }), + ::Model.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' })] + serializer = ArraySerializer.new(array, each_serializer: ProfileSerializer) + + expected = [{'name' => 'Name 1', 'description' => 'Description 1'}, + {'name' => 'Name 2', 'description' => 'Description 2'}] + + assert_equal expected, serializer.serializable_array + end end end end From cd3e5e9de2ece6b7f802951f8c0bbd21339ebe73 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 20 Aug 2013 14:33:33 -0300 Subject: [PATCH 23/73] Assign the association in the setup method --- .../active_model/serializer/has_one_test.rb | 21 +++++++++---------- 1 file changed, 10 insertions(+), 11 deletions(-) diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index c2e770f2..e49fd0b0 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -7,16 +7,15 @@ module ActiveModel def setup @user = User.new({ name: 'Name 1', email: 'mail@server.com', gender: 'M' }) @user_serializer = UserSerializer.new(@user) - @user_serializer.class._associations[0].include = false - @user_serializer.class._associations[0].embed = :ids + @association = UserSerializer._associations[0] + @association.include = false + @association.embed = :ids end def test_associations_definition - associations = @user_serializer.class._associations - - assert_equal 1, associations.length - assert_kind_of Association::HasOne, associations[0] - assert_equal 'profile', associations[0].name + assert_equal 1, UserSerializer._associations.length + assert_kind_of Association::HasOne, @association + assert_equal 'profile', @association.name end def test_associations_embedding_ids_serialization_using_serializable_hash @@ -32,28 +31,28 @@ module ActiveModel end def test_associations_embedding_objects_serialization_using_serializable_hash - @user_serializer.class._associations[0].embed = :objects + @association.embed = :objects assert_equal({ 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile' => { 'name' => 'N1', 'description' => 'D1' } }, @user_serializer.serializable_hash) end def test_associations_embedding_objects_serialization_using_as_json - @user_serializer.class._associations[0].embed = :objects + @association.embed = :objects assert_equal({ 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile' => { 'name' => 'N1', 'description' => 'D1' } }, @user_serializer.as_json) end def test_associations_embedding_ids_including_objects_serialization_using_serializable_hash - @user_serializer.class._associations[0].include = true + @association.include = true assert_equal({ 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id, 'profile' => { 'name' => 'N1', 'description' => 'D1' } }, @user_serializer.serializable_hash) end def test_associations_embedding_ids_including_objects_serialization_using_as_json - @user_serializer.class._associations[0].include = true + @association.include = true assert_equal({ 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id, 'profile' => { 'name' => 'N1', 'description' => 'D1' } }, @user_serializer.as_json) From 70ea6c6bc7034b69973f65b736301640a2b65484 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 20 Aug 2013 14:56:36 -0300 Subject: [PATCH 24/73] Add has_one and has_many :serialize => tests --- lib/active_model/serializer/associations.rb | 9 +++-- .../active_model/serializer/has_many_test.rb | 39 +++++++++++++------ .../active_model/serializer/has_one_test.rb | 18 +++++++++ 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index aab18b85..f255b674 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -9,16 +9,19 @@ module ActiveModel @embed_key = options[:embed_key] || :id @include = options[:include] - serializer = @options[:serializer] - @serializer_class = serializer.is_a?(String) ? serializer.constantize : serializer + self.serializer_class = @options[:serializer] end - attr_reader :name, :embed_ids, :embed_objects, :embed_key + attr_reader :name, :embed_ids, :embed_objects, :embed_key, :serializer_class attr_accessor :include alias embed_ids? embed_ids alias embed_objects? embed_objects alias include? include + def serializer_class=(serializer) + @serializer_class = serializer.is_a?(String) ? serializer.constantize : serializer + end + def embed=(embed) @embed_ids = embed == :id || embed == :ids @embed_objects = embed == :object || embed == :objects diff --git a/test/unit/active_model/serializer/has_many_test.rb b/test/unit/active_model/serializer/has_many_test.rb index 7dd05e50..63195cce 100644 --- a/test/unit/active_model/serializer/has_many_test.rb +++ b/test/unit/active_model/serializer/has_many_test.rb @@ -7,16 +7,15 @@ module ActiveModel def setup @post = Post.new({ title: 'Title 1', body: 'Body 1', date: '1/1/2000' }) @post_serializer = PostSerializer.new(@post) - @post_serializer.class._associations[0].include = false - @post_serializer.class._associations[0].embed = :ids + @association = PostSerializer._associations[0] + @association.include = false + @association.embed = :ids end def test_associations_definition - associations = @post_serializer.class._associations - - assert_equal 1, associations.length - assert_kind_of Association::HasMany, associations[0] - assert_equal 'comments', associations[0].name + assert_equal 1, PostSerializer._associations.length + assert_kind_of Association::HasMany, @association + assert_equal 'comments', @association.name end def test_associations_embedding_ids_serialization_using_serializable_hash @@ -32,32 +31,50 @@ module ActiveModel end def test_associations_embedding_objects_serialization_using_serializable_hash - @post_serializer.class._associations[0].embed = :objects + @association.embed = :objects assert_equal({ 'title' => 'Title 1', 'body' => 'Body 1', 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] }, @post_serializer.serializable_hash) end def test_associations_embedding_objects_serialization_using_as_json - @post_serializer.class._associations[0].embed = :objects + @association.embed = :objects assert_equal({ 'title' => 'Title 1', 'body' => 'Body 1', 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] }, @post_serializer.as_json) end def test_associations_embedding_ids_including_objects_serialization_using_serializable_hash - @post_serializer.class._associations[0].include = true + @association.include = true assert_equal({ 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id }, 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] }, @post_serializer.serializable_hash) end def test_associations_embedding_ids_including_objects_serialization_using_as_json - @post_serializer.class._associations[0].include = true + @association.include = true assert_equal({ 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id }, 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] }, @post_serializer.as_json) end + + def test_associations_using_a_given_serializer + @old_serializer = @association.serializer_class + @association.include = true + @association.serializer_class = Class.new(ActiveModel::Serializer) do + def content + 'fake' + end + + attributes :content + end + + assert_equal({ + 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id }, 'comments' => [{ 'content' => 'fake' }, { 'content' => 'fake' }] + }, @post_serializer.as_json) + ensure + @association.serializer_class = @old_serializer + end end end end diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index e49fd0b0..05ba59f3 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -57,6 +57,24 @@ module ActiveModel 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id, 'profile' => { 'name' => 'N1', 'description' => 'D1' } }, @user_serializer.as_json) end + + def test_associations_using_a_given_serializer + @old_serializer = @association.serializer_class + @association.include = true + @association.serializer_class = Class.new(ActiveModel::Serializer) do + def name + 'fake' + end + + attributes :name + end + + assert_equal({ + 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id, 'profile' => { 'name' => 'fake' } + }, @user_serializer.as_json) + ensure + @association.serializer_class = @old_serializer + end end end end From a820e9774fe327c9756a145506b3e9fbe2e87637 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 23 Aug 2013 19:17:17 -0300 Subject: [PATCH 25/73] Add ArraySerializer's root and meta features --- lib/active_model/array_serializer.rb | 29 +++++- .../array_serializer/meta_test.rb | 53 +++++++++++ .../array_serializer/root_test.rb | 93 +++++++++++++++++++ .../serialize_test.rb} | 5 +- 4 files changed, 176 insertions(+), 4 deletions(-) create mode 100644 test/unit/active_model/array_serializer/meta_test.rb create mode 100644 test/unit/active_model/array_serializer/root_test.rb rename test/unit/active_model/{array_serializer_test.rb => array_serializer/serialize_test.rb} (88%) diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb index 6a56262e..a078d0c6 100644 --- a/lib/active_model/array_serializer.rb +++ b/lib/active_model/array_serializer.rb @@ -1,10 +1,23 @@ module ActiveModel class ArraySerializer - def initialize(object, options={}) - @object = object - @options = options + class << self + attr_accessor :_root + + def root(root) + @_root = root + end + alias root= root end + def initialize(object, options={}) + @object = object + @options = options + @root = options[:root] || self.class._root + @meta_key = options[:meta_key] || :meta + @meta = options[@meta_key] + end + attr_accessor :object, :root, :meta_key, :meta + def serializable_array @object.map do |item| serializer = @options[:each_serializer] || Serializer.serializer_for(item) @@ -16,5 +29,15 @@ module ActiveModel end end alias serializable_object serializable_array + + def as_json(options={}) + if root = options[:root] || self.root + hash = { root.to_s => serializable_array } + hash[meta_key.to_s] = meta if meta + hash + else + serializable_array + end + end end end diff --git a/test/unit/active_model/array_serializer/meta_test.rb b/test/unit/active_model/array_serializer/meta_test.rb new file mode 100644 index 00000000..1f3e4ce8 --- /dev/null +++ b/test/unit/active_model/array_serializer/meta_test.rb @@ -0,0 +1,53 @@ +require 'test_helper' +require 'active_model/serializer' + +module ActiveModel + class ArraySerializer + class MetaTest < ActiveModel::TestCase + def setup + @profile1 = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + @profile2 = Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' }) + @serializer = ArraySerializer.new([@profile1, @profile2], root: 'profiles') + end + + def test_meta + @serializer.meta = { 'total' => 10 } + + assert_equal({ + 'profiles' => [ + { + 'name' => 'Name 1', + 'description' => 'Description 1' + }, { + 'name' => 'Name 2', + 'description' => 'Description 2' + } + ], + 'meta' => { + 'total' => 10 + } + }, @serializer.as_json) + end + + def test_meta_using_meta_key + @serializer.meta_key = :my_meta + @serializer.meta = { 'total' => 10 } + + assert_equal({ + 'profiles' => [ + { + 'name' => 'Name 1', + 'description' => 'Description 1' + }, { + 'name' => 'Name 2', + 'description' => 'Description 2' + } + ], + 'my_meta' => { + 'total' => 10 + } + }, @serializer.as_json) + end + end + end +end diff --git a/test/unit/active_model/array_serializer/root_test.rb b/test/unit/active_model/array_serializer/root_test.rb new file mode 100644 index 00000000..372d1833 --- /dev/null +++ b/test/unit/active_model/array_serializer/root_test.rb @@ -0,0 +1,93 @@ +require 'test_helper' +require 'active_model/serializer' + +module ActiveModel + class ArraySerializer + class RootAsOptionTest < ActiveModel::TestCase + def setup + @old_root = ArraySerializer._root + @profile1 = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + @profile2 = Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' }) + @serializer = ArraySerializer.new([@profile1, @profile2], root: 'initialize') + end + + def teardown + ArraySerializer._root = @old_root + end + + def test_root_is_not_displayed_using_serializable_array + assert_equal([ + { 'name' => 'Name 1', 'description' => 'Description 1' }, + { 'name' => 'Name 2', 'description' => 'Description 2' } + ], @serializer.serializable_array) + end + + def test_root_using_as_json + assert_equal({ + 'initialize' => [ + { 'name' => 'Name 1', 'description' => 'Description 1' }, + { 'name' => 'Name 2', 'description' => 'Description 2' } + ] + }, @serializer.as_json) + end + + def test_root_as_argument_takes_precedence + assert_equal({ + 'argument' => [ + { 'name' => 'Name 1', 'description' => 'Description 1' }, + { 'name' => 'Name 2', 'description' => 'Description 2' } + ] + }, @serializer.as_json(root: 'argument')) + end + end + + class RootInSerializerTest < ActiveModel::TestCase + def setup + @old_root = ArraySerializer._root + ArraySerializer._root = 'in_serializer' + @profile1 = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + @profile2 = Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' }) + @serializer = ArraySerializer.new([@profile1, @profile2]) + @rooted_serializer = ArraySerializer.new([@profile1, @profile2], root: :initialize) + end + + def teardown + ArraySerializer._root = @old_root + end + + def test_root_is_not_displayed_using_serializable_hash + assert_equal([ + { 'name' => 'Name 1', 'description' => 'Description 1' }, + { 'name' => 'Name 2', 'description' => 'Description 2' } + ], @serializer.serializable_array) + end + + def test_root_using_as_json + assert_equal({ + 'in_serializer' => [ + { 'name' => 'Name 1', 'description' => 'Description 1' }, + { 'name' => 'Name 2', 'description' => 'Description 2' } + ] + }, @serializer.as_json) + end + + def test_root_in_initializer_takes_precedence + assert_equal({ + 'initialize' => [ + { 'name' => 'Name 1', 'description' => 'Description 1' }, + { 'name' => 'Name 2', 'description' => 'Description 2' } + ] + }, @rooted_serializer.as_json) + end + + def test_root_as_argument_takes_precedence + assert_equal({ + 'argument' => [ + { 'name' => 'Name 1', 'description' => 'Description 1' }, + { 'name' => 'Name 2', 'description' => 'Description 2' } + ] + }, @rooted_serializer.as_json(root: :argument)) + end + end + end +end diff --git a/test/unit/active_model/array_serializer_test.rb b/test/unit/active_model/array_serializer/serialize_test.rb similarity index 88% rename from test/unit/active_model/array_serializer_test.rb rename to test/unit/active_model/array_serializer/serialize_test.rb index 25ede76b..ad7a935a 100644 --- a/test/unit/active_model/array_serializer_test.rb +++ b/test/unit/active_model/array_serializer/serialize_test.rb @@ -3,7 +3,7 @@ require 'active_model/serializer' module ActiveModel class ArraySerializer - class Test < ActiveModel::TestCase + class SerializeTest < ActiveModel::TestCase def setup array = [1, 2, 3] @serializer = ActiveModel::Serializer.serializer_for(array).new(array) @@ -15,6 +15,7 @@ module ActiveModel def test_array_serializer_serializes_simple_objects assert_equal [1, 2, 3], @serializer.serializable_array + assert_equal [1, 2, 3], @serializer.as_json end def test_array_serializer_serializes_models @@ -26,6 +27,7 @@ module ActiveModel {'name' => 'Name 2', 'description' => 'Description 2'}] assert_equal expected, serializer.serializable_array + assert_equal expected, serializer.as_json end def test_array_serializers_each_serializer @@ -37,6 +39,7 @@ module ActiveModel {'name' => 'Name 2', 'description' => 'Description 2'}] assert_equal expected, serializer.serializable_array + assert_equal expected, serializer.as_json end end end From e6993c677b448b89680181962bd4999ec9c7a2d1 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 23 Aug 2013 21:22:00 -0300 Subject: [PATCH 26/73] Add missing requires --- lib/active_model/array_serializer.rb | 2 ++ lib/active_model/serializer.rb | 1 + lib/active_model/serializer/associations.rb | 2 ++ lib/active_model_serializers.rb | 2 +- test/integration/action_controller/serialization_test.rb | 1 - test/integration/active_record/active_record_test.rb | 1 - test/test_helper.rb | 2 +- test/unit/active_model/array_serializer/root_test.rb | 1 - test/unit/active_model/array_serializer/serialize_test.rb | 1 - test/unit/active_model/serializer/attributes_test.rb | 1 - test/unit/active_model/serializer/has_many_test.rb | 1 - test/unit/active_model/serializer/has_one_test.rb | 1 - test/unit/active_model/serializer/meta_test.rb | 1 - test/unit/active_model/serializer/root_test.rb | 1 - test/unit/active_model/serializer/scope_test.rb | 1 - 15 files changed, 7 insertions(+), 12 deletions(-) diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb index a078d0c6..2ff339b5 100644 --- a/lib/active_model/array_serializer.rb +++ b/lib/active_model/array_serializer.rb @@ -1,3 +1,5 @@ +require 'active_model/serializer' + module ActiveModel class ArraySerializer class << self diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 940d59fa..b2b852cc 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -1,3 +1,4 @@ +require 'active_model/array_serializer' require 'active_model/serializer/associations' module ActiveModel diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index f255b674..5b6365e2 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -1,3 +1,5 @@ +require 'active_model/serializer' + module ActiveModel class Serializer class Association diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index 5ecc1b06..c3e40c4b 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -1,6 +1,6 @@ require 'active_model' require 'active_model/serializer' -require 'active_model/array_serializer' +require 'active_model/serializer/version' begin require 'action_controller' diff --git a/test/integration/action_controller/serialization_test.rb b/test/integration/action_controller/serialization_test.rb index b79330f0..fe356cbc 100644 --- a/test/integration/action_controller/serialization_test.rb +++ b/test/integration/action_controller/serialization_test.rb @@ -1,5 +1,4 @@ require 'test_helper' -require 'active_model_serializers' module ActionController module Serialization diff --git a/test/integration/active_record/active_record_test.rb b/test/integration/active_record/active_record_test.rb index 9529da1e..cfd04410 100644 --- a/test/integration/active_record/active_record_test.rb +++ b/test/integration/active_record/active_record_test.rb @@ -1,6 +1,5 @@ require 'test_helper' require 'fixtures/active_record' -require 'active_model/serializer' module ActiveModel class Serializer diff --git a/test/test_helper.rb b/test/test_helper.rb index bb2eb5dc..f5c6d8f9 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,6 +1,6 @@ require 'bundler/setup' -require 'active_model_serializers' require 'test/unit' +require 'active_model_serializers' require 'fixtures/poro' module TestHelper diff --git a/test/unit/active_model/array_serializer/root_test.rb b/test/unit/active_model/array_serializer/root_test.rb index 372d1833..01aebf3d 100644 --- a/test/unit/active_model/array_serializer/root_test.rb +++ b/test/unit/active_model/array_serializer/root_test.rb @@ -1,5 +1,4 @@ require 'test_helper' -require 'active_model/serializer' module ActiveModel class ArraySerializer diff --git a/test/unit/active_model/array_serializer/serialize_test.rb b/test/unit/active_model/array_serializer/serialize_test.rb index ad7a935a..33d3c0f8 100644 --- a/test/unit/active_model/array_serializer/serialize_test.rb +++ b/test/unit/active_model/array_serializer/serialize_test.rb @@ -1,5 +1,4 @@ require 'test_helper' -require 'active_model/serializer' module ActiveModel class ArraySerializer diff --git a/test/unit/active_model/serializer/attributes_test.rb b/test/unit/active_model/serializer/attributes_test.rb index bc02f714..10bad2b7 100644 --- a/test/unit/active_model/serializer/attributes_test.rb +++ b/test/unit/active_model/serializer/attributes_test.rb @@ -1,5 +1,4 @@ require 'test_helper' -require 'active_model/serializer' module ActiveModel class Serializer diff --git a/test/unit/active_model/serializer/has_many_test.rb b/test/unit/active_model/serializer/has_many_test.rb index 63195cce..cc70338a 100644 --- a/test/unit/active_model/serializer/has_many_test.rb +++ b/test/unit/active_model/serializer/has_many_test.rb @@ -1,5 +1,4 @@ require 'test_helper' -require 'active_model/serializer' module ActiveModel class Serializer diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index 05ba59f3..bbec3967 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -1,5 +1,4 @@ require 'test_helper' -require 'active_model/serializer' module ActiveModel class Serializer diff --git a/test/unit/active_model/serializer/meta_test.rb b/test/unit/active_model/serializer/meta_test.rb index 73f843de..a006bf1e 100644 --- a/test/unit/active_model/serializer/meta_test.rb +++ b/test/unit/active_model/serializer/meta_test.rb @@ -1,5 +1,4 @@ require 'test_helper' -require 'active_model/serializer' module ActiveModel class Serializer diff --git a/test/unit/active_model/serializer/root_test.rb b/test/unit/active_model/serializer/root_test.rb index cad83cc7..53a1658e 100644 --- a/test/unit/active_model/serializer/root_test.rb +++ b/test/unit/active_model/serializer/root_test.rb @@ -1,5 +1,4 @@ require 'test_helper' -require 'active_model/serializer' module ActiveModel class Serializer diff --git a/test/unit/active_model/serializer/scope_test.rb b/test/unit/active_model/serializer/scope_test.rb index 4b67190c..78c1b8d7 100644 --- a/test/unit/active_model/serializer/scope_test.rb +++ b/test/unit/active_model/serializer/scope_test.rb @@ -1,5 +1,4 @@ require 'test_helper' -require 'active_model/serializer' module ActiveModel class Serializer From 7ba05c02e3f62f027818945a46006ab24a799139 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 23 Aug 2013 21:35:00 -0300 Subject: [PATCH 27/73] Make Serializable module which implements as_json --- lib/active_model/array_serializer.rb | 13 +++---------- lib/active_model/serializable.rb | 13 +++++++++++++ lib/active_model/serializer.rb | 13 +++---------- 3 files changed, 19 insertions(+), 20 deletions(-) create mode 100644 lib/active_model/serializable.rb diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb index 2ff339b5..cc2fc4c9 100644 --- a/lib/active_model/array_serializer.rb +++ b/lib/active_model/array_serializer.rb @@ -1,7 +1,10 @@ +require 'active_model/serializable' require 'active_model/serializer' module ActiveModel class ArraySerializer + include Serializable + class << self attr_accessor :_root @@ -31,15 +34,5 @@ module ActiveModel end end alias serializable_object serializable_array - - def as_json(options={}) - if root = options[:root] || self.root - hash = { root.to_s => serializable_array } - hash[meta_key.to_s] = meta if meta - hash - else - serializable_array - end - end end end diff --git a/lib/active_model/serializable.rb b/lib/active_model/serializable.rb new file mode 100644 index 00000000..944cb0aa --- /dev/null +++ b/lib/active_model/serializable.rb @@ -0,0 +1,13 @@ +module ActiveModel + module Serializable + def as_json(options={}) + if root = options[:root] || self.root + hash = { root.to_s => serializable_object } + hash[meta_key.to_s] = meta if meta + hash + else + serializable_object + end + end + end +end diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index b2b852cc..82f3643a 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -1,8 +1,11 @@ require 'active_model/array_serializer' +require 'active_model/serializable' require 'active_model/serializer/associations' module ActiveModel class Serializer + include Serializable + class << self def inherited(base) base._attributes = [] @@ -120,15 +123,5 @@ module ActiveModel hash.merge! associations end alias serializable_object serializable_hash - - def as_json(options={}) - if root = options[:root] || self.root - hash = { root.to_s => serializable_hash } - hash[meta_key.to_s] = meta if meta - hash - else - serializable_hash - end - end end end From 3fcd8c5f98e4575a143e73133c81182fdb158291 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 23 Aug 2013 21:38:02 -0300 Subject: [PATCH 28/73] Define root and root= as aliases of _root= --- lib/active_model/array_serializer.rb | 7 ++----- lib/active_model/serializer.rb | 7 ++----- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb index cc2fc4c9..43e537f9 100644 --- a/lib/active_model/array_serializer.rb +++ b/lib/active_model/array_serializer.rb @@ -7,11 +7,8 @@ module ActiveModel class << self attr_accessor :_root - - def root(root) - @_root = root - end - alias root= root + alias root _root= + alias root= _root= end def initialize(object, options={}) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 82f3643a..f7906781 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -21,11 +21,8 @@ module ActiveModel end attr_accessor :_root, :_attributes, :_associations - - def root(root) - @_root = root - end - alias root= root + alias root _root= + alias root= _root= def root_name name.demodulize.underscore.sub(/_serializer$/, '') if name From 8a495b1d00a6d1d669188fffa1724bf60b30242d Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 26 Aug 2013 14:48:39 -0300 Subject: [PATCH 29/73] Embedded has_one returns an array with the key pluralized --- lib/active_model/serializer.rb | 2 +- lib/active_model/serializer/associations.rb | 2 +- test/unit/active_model/serializer/has_one_test.rb | 10 +++++----- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index f7906781..7fa1a31f 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -101,7 +101,7 @@ module ActiveModel if associated_data.respond_to?(:to_ary) associated_data.map { |elem| association.build_serializer(elem).serializable_hash } else - association.build_serializer(associated_data).serializable_hash + [association.build_serializer(associated_data).serializable_hash] end end diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index 5b6365e2..e78bce9c 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -49,7 +49,7 @@ module ActiveModel end def embedded_key - name + name.pluralize end end diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index bbec3967..b104d652 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -32,28 +32,28 @@ module ActiveModel def test_associations_embedding_objects_serialization_using_serializable_hash @association.embed = :objects assert_equal({ - 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile' => { 'name' => 'N1', 'description' => 'D1' } + 'name' => 'Name 1', 'email' => 'mail@server.com', 'profiles' => [{ 'name' => 'N1', 'description' => 'D1' }] }, @user_serializer.serializable_hash) end def test_associations_embedding_objects_serialization_using_as_json @association.embed = :objects assert_equal({ - 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile' => { 'name' => 'N1', 'description' => 'D1' } + 'name' => 'Name 1', 'email' => 'mail@server.com', 'profiles' => [{ 'name' => 'N1', 'description' => 'D1' }] }, @user_serializer.as_json) end def test_associations_embedding_ids_including_objects_serialization_using_serializable_hash @association.include = true assert_equal({ - 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id, 'profile' => { 'name' => 'N1', 'description' => 'D1' } + 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id, 'profiles' => [{ 'name' => 'N1', 'description' => 'D1' }] }, @user_serializer.serializable_hash) end def test_associations_embedding_ids_including_objects_serialization_using_as_json @association.include = true assert_equal({ - 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id, 'profile' => { 'name' => 'N1', 'description' => 'D1' } + 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id, 'profiles' => [{ 'name' => 'N1', 'description' => 'D1' }] }, @user_serializer.as_json) end @@ -69,7 +69,7 @@ module ActiveModel end assert_equal({ - 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id, 'profile' => { 'name' => 'fake' } + 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id, 'profiles' => [{ 'name' => 'fake' }] }, @user_serializer.as_json) ensure @association.serializer_class = @old_serializer From 48590a2e3793b8bf7f0395b8a29513c256aa18c9 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 26 Aug 2013 15:20:58 -0300 Subject: [PATCH 30/73] Allow to set root for associations through options --- lib/active_model/serializer/associations.rb | 6 +++--- test/unit/active_model/serializer/has_many_test.rb | 9 +++++++++ test/unit/active_model/serializer/has_one_test.rb | 9 +++++++++ 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index e78bce9c..81977316 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -14,7 +14,7 @@ module ActiveModel self.serializer_class = @options[:serializer] end - attr_reader :name, :embed_ids, :embed_objects, :embed_key, :serializer_class + attr_reader :name, :embed_ids, :embed_objects, :embed_key, :serializer_class, :options attr_accessor :include alias embed_ids? embed_ids alias embed_objects? embed_objects @@ -49,7 +49,7 @@ module ActiveModel end def embedded_key - name.pluralize + @options[:root] || name.pluralize end end @@ -59,7 +59,7 @@ module ActiveModel end def embedded_key - name + @options[:root] || name end end end diff --git a/test/unit/active_model/serializer/has_many_test.rb b/test/unit/active_model/serializer/has_many_test.rb index cc70338a..03b9df7c 100644 --- a/test/unit/active_model/serializer/has_many_test.rb +++ b/test/unit/active_model/serializer/has_many_test.rb @@ -9,6 +9,7 @@ module ActiveModel @association = PostSerializer._associations[0] @association.include = false @association.embed = :ids + @association.options[:root] = nil end def test_associations_definition @@ -43,6 +44,14 @@ module ActiveModel }, @post_serializer.as_json) end + def test_associations_embedding_objects_serialization_using_serializable_hash_and_root_from_options + @association.embed = :objects + @association.options[:root] = 'root' + assert_equal({ + 'title' => 'Title 1', 'body' => 'Body 1', 'root' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] + }, @post_serializer.serializable_hash) + end + def test_associations_embedding_ids_including_objects_serialization_using_serializable_hash @association.include = true assert_equal({ diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index b104d652..7a548a0f 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -9,6 +9,7 @@ module ActiveModel @association = UserSerializer._associations[0] @association.include = false @association.embed = :ids + @association.options[:root] = nil end def test_associations_definition @@ -43,6 +44,14 @@ module ActiveModel }, @user_serializer.as_json) end + def test_associations_embedding_objects_serialization_using_serializable_hash_and_root_from_options + @association.embed = :objects + @association.options[:root] = 'root' + assert_equal({ + 'name' => 'Name 1', 'email' => 'mail@server.com', 'root' => [{ 'name' => 'N1', 'description' => 'D1' }] + }, @user_serializer.serializable_hash) + end + def test_associations_embedding_ids_including_objects_serialization_using_serializable_hash @association.include = true assert_equal({ From 280fd65db8c0940bee27a36e6de6ab0292c8a220 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 26 Aug 2013 15:32:02 -0300 Subject: [PATCH 31/73] Allow to set key for associations through options --- lib/active_model/serializer/associations.rb | 4 ++-- test/unit/active_model/serializer/has_many_test.rb | 8 ++++++++ test/unit/active_model/serializer/has_one_test.rb | 8 ++++++++ 3 files changed, 18 insertions(+), 2 deletions(-) diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index 81977316..58289770 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -45,7 +45,7 @@ module ActiveModel class HasOne < Association def key - "#{name}_id" + @options[:key] || "#{name}_id" end def embedded_key @@ -55,7 +55,7 @@ module ActiveModel class HasMany < Association def key - "#{name.singularize}_ids" + @options[:key] || "#{name.singularize}_ids" end def embedded_key diff --git a/test/unit/active_model/serializer/has_many_test.rb b/test/unit/active_model/serializer/has_many_test.rb index 03b9df7c..32d1a166 100644 --- a/test/unit/active_model/serializer/has_many_test.rb +++ b/test/unit/active_model/serializer/has_many_test.rb @@ -10,6 +10,7 @@ module ActiveModel @association.include = false @association.embed = :ids @association.options[:root] = nil + @association.options[:key] = nil end def test_associations_definition @@ -30,6 +31,13 @@ module ActiveModel }, @post_serializer.as_json) end + def test_associations_embedding_ids_serialization_using_serializable_hash_and_key_from_options + @association.options[:key] = 'key' + assert_equal({ + 'title' => 'Title 1', 'body' => 'Body 1', 'key' => @post.comments.map { |c| c.object_id } + }, @post_serializer.serializable_hash) + end + def test_associations_embedding_objects_serialization_using_serializable_hash @association.embed = :objects assert_equal({ diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index 7a548a0f..8cf69eca 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -10,6 +10,7 @@ module ActiveModel @association.include = false @association.embed = :ids @association.options[:root] = nil + @association.options[:key] = nil end def test_associations_definition @@ -30,6 +31,13 @@ module ActiveModel }, @user_serializer.as_json) end + def test_associations_embedding_ids_serialization_using_serializable_hash_and_key_from_options + @association.options[:key] = 'key' + assert_equal({ + 'name' => 'Name 1', 'email' => 'mail@server.com', 'key' => @user.profile.object_id + }, @user_serializer.serializable_hash) + end + def test_associations_embedding_objects_serialization_using_serializable_hash @association.embed = :objects assert_equal({ From af34adc7b568fca724cfb9849cf24493e7d4afe7 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 26 Aug 2013 15:43:59 -0300 Subject: [PATCH 32/73] Move key's initialization code to initializers --- lib/active_model/serializer/associations.rb | 30 +++++++++---------- .../active_model/serializer/has_many_test.rb | 12 +++++--- .../active_model/serializer/has_one_test.rb | 12 +++++--- 3 files changed, 30 insertions(+), 24 deletions(-) diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index 58289770..369b2eff 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -7,15 +7,17 @@ module ActiveModel @name = name @options = options - self.embed = options[:embed] - @embed_key = options[:embed_key] || :id - @include = options[:include] + self.embed = options[:embed] + @embed_key = options[:embed_key] || :id + @include = options[:include] + @key = options[:key] + @embedded_key = options[:root] self.serializer_class = @options[:serializer] end attr_reader :name, :embed_ids, :embed_objects, :embed_key, :serializer_class, :options - attr_accessor :include + attr_accessor :include, :key, :embedded_key alias embed_ids? embed_ids alias embed_objects? embed_objects alias include? include @@ -44,22 +46,18 @@ module ActiveModel end class HasOne < Association - def key - @options[:key] || "#{name}_id" - end - - def embedded_key - @options[:root] || name.pluralize + def initialize(*args) + super + @key ||= "#{name}_id" + @embedded_key ||= name.pluralize end end class HasMany < Association - def key - @options[:key] || "#{name.singularize}_ids" - end - - def embedded_key - @options[:root] || name + def initialize(*args) + super + @key ||= "#{name.singularize}_ids" + @embedded_key ||= name end end end diff --git a/test/unit/active_model/serializer/has_many_test.rb b/test/unit/active_model/serializer/has_many_test.rb index 32d1a166..efab83e5 100644 --- a/test/unit/active_model/serializer/has_many_test.rb +++ b/test/unit/active_model/serializer/has_many_test.rb @@ -9,8 +9,6 @@ module ActiveModel @association = PostSerializer._associations[0] @association.include = false @association.embed = :ids - @association.options[:root] = nil - @association.options[:key] = nil end def test_associations_definition @@ -32,10 +30,13 @@ module ActiveModel end def test_associations_embedding_ids_serialization_using_serializable_hash_and_key_from_options - @association.options[:key] = 'key' + old_key = @association.key + @association.key = 'key' assert_equal({ 'title' => 'Title 1', 'body' => 'Body 1', 'key' => @post.comments.map { |c| c.object_id } }, @post_serializer.serializable_hash) + ensure + @association.key = old_key end def test_associations_embedding_objects_serialization_using_serializable_hash @@ -54,10 +55,13 @@ module ActiveModel def test_associations_embedding_objects_serialization_using_serializable_hash_and_root_from_options @association.embed = :objects - @association.options[:root] = 'root' + old_embedded_key = @association.embedded_key + @association.embedded_key = 'root' assert_equal({ 'title' => 'Title 1', 'body' => 'Body 1', 'root' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] }, @post_serializer.serializable_hash) + ensure + @association.embedded_key = old_embedded_key end def test_associations_embedding_ids_including_objects_serialization_using_serializable_hash diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index 8cf69eca..2a34426b 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -9,8 +9,6 @@ module ActiveModel @association = UserSerializer._associations[0] @association.include = false @association.embed = :ids - @association.options[:root] = nil - @association.options[:key] = nil end def test_associations_definition @@ -32,10 +30,13 @@ module ActiveModel end def test_associations_embedding_ids_serialization_using_serializable_hash_and_key_from_options - @association.options[:key] = 'key' + old_key = @association.key + @association.key = 'key' assert_equal({ 'name' => 'Name 1', 'email' => 'mail@server.com', 'key' => @user.profile.object_id }, @user_serializer.serializable_hash) + ensure + @association.key = old_key end def test_associations_embedding_objects_serialization_using_serializable_hash @@ -54,10 +55,13 @@ module ActiveModel def test_associations_embedding_objects_serialization_using_serializable_hash_and_root_from_options @association.embed = :objects - @association.options[:root] = 'root' + old_embedded_key = @association.embedded_key + @association.embedded_key = 'root' assert_equal({ 'name' => 'Name 1', 'email' => 'mail@server.com', 'root' => [{ 'name' => 'N1', 'description' => 'D1' }] }, @user_serializer.serializable_hash) + ensure + @association.embedded_key = old_embedded_key end def test_associations_embedding_ids_including_objects_serialization_using_serializable_hash From 0e0341effcdecd9c9d087dae442cc2967a53971f Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 28 Aug 2013 13:48:25 -0300 Subject: [PATCH 33/73] Implement embed in root --- lib/active_model/serializable.rb | 10 +++++- lib/active_model/serializer.rb | 30 +++++++++++++--- lib/active_model/serializer/associations.rb | 20 +++++------ .../active_model/serializer/has_many_test.rb | 36 +++++++++---------- .../active_model/serializer/has_one_test.rb | 36 +++++++++---------- 5 files changed, 79 insertions(+), 53 deletions(-) diff --git a/lib/active_model/serializable.rb b/lib/active_model/serializable.rb index 944cb0aa..897ea13f 100644 --- a/lib/active_model/serializable.rb +++ b/lib/active_model/serializable.rb @@ -3,11 +3,19 @@ module ActiveModel def as_json(options={}) if root = options[:root] || self.root hash = { root.to_s => serializable_object } - hash[meta_key.to_s] = meta if meta + hash.merge!(serializable_data) hash else serializable_object end end + + def serializable_data + if respond_to?(:meta) && meta + { meta_key.to_s => meta } + else + {} + end + end end end diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 7fa1a31f..a7af3235 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -70,15 +70,20 @@ module ActiveModel def initialize(object, options={}) @object = object @scope = options[:scope] - @root = options[:root] || self.class._root - @root = self.class.root_name if @root == true + self.root = options[:root] @meta_key = options[:meta_key] || :meta @meta = options[@meta_key] end - attr_accessor :object, :scope, :root, :meta_key, :meta + attr_accessor :object, :scope, :meta_key, :meta + attr_reader :root alias read_attribute_for_serialization send + def root=(root) + @root = root || self.class._root + @root = self.class.root_name if auto_assign_root? + end + def attributes self.class._attributes.each_with_object({}) do |name, hash| hash[name] = send(name) @@ -89,8 +94,19 @@ module ActiveModel self.class._associations.each_with_object({}) do |association, hash| if association.embed_ids? hash[association.key] = serialize_ids association + elsif association.embed_objects? + hash[association.embedded_key] = serialize association end - if association.embed_objects? + end + end + + def serializable_data + embedded_in_root_associations.merge!(super) + end + + def embedded_in_root_associations + self.class._associations.each_with_object({}) do |association, hash| + if association.embed_in_root? hash[association.embedded_key] = serialize association end end @@ -120,5 +136,11 @@ module ActiveModel hash.merge! associations end alias serializable_object serializable_hash + + private + + def auto_assign_root? + @root == true || !@root && self.class._associations.any? { |a| a.embed_in_root? } + end end end diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index 369b2eff..91d473fc 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -7,20 +7,20 @@ module ActiveModel @name = name @options = options - self.embed = options[:embed] - @embed_key = options[:embed_key] || :id - @include = options[:include] - @key = options[:key] - @embedded_key = options[:root] + self.embed = options[:embed] + @embed_in_root = @embed_ids && options[:include] + @embed_key = options[:embed_key] || :id + @key = options[:key] + @embedded_key = options[:root] self.serializer_class = @options[:serializer] end - attr_reader :name, :embed_ids, :embed_objects, :embed_key, :serializer_class, :options - attr_accessor :include, :key, :embedded_key + attr_reader :name, :embed_ids, :embed_objects, :serializer_class + attr_accessor :embed_in_root, :embed_key, :key, :embedded_key, :options alias embed_ids? embed_ids alias embed_objects? embed_objects - alias include? include + alias embed_in_root? embed_in_root def serializer_class=(serializer) @serializer_class = serializer.is_a?(String) ? serializer.constantize : serializer @@ -31,10 +31,6 @@ module ActiveModel @embed_objects = embed == :object || embed == :objects end - def embed_objects? - @embed_objects || @embed_ids && @include - end - def build_serializer(object) @serializer_class ||= Serializer.serializer_for(object) diff --git a/test/unit/active_model/serializer/has_many_test.rb b/test/unit/active_model/serializer/has_many_test.rb index efab83e5..a4837a5b 100644 --- a/test/unit/active_model/serializer/has_many_test.rb +++ b/test/unit/active_model/serializer/has_many_test.rb @@ -4,11 +4,15 @@ module ActiveModel class Serializer class HasManyTest < ActiveModel::TestCase def setup + @association = PostSerializer._associations[0] + @old_association = @association.dup + @association.embed = :ids @post = Post.new({ title: 'Title 1', body: 'Body 1', date: '1/1/2000' }) @post_serializer = PostSerializer.new(@post) - @association = PostSerializer._associations[0] - @association.include = false - @association.embed = :ids + end + + def teardown + PostSerializer._associations[0] = @old_association end def test_associations_definition @@ -30,13 +34,10 @@ module ActiveModel end def test_associations_embedding_ids_serialization_using_serializable_hash_and_key_from_options - old_key = @association.key @association.key = 'key' assert_equal({ 'title' => 'Title 1', 'body' => 'Body 1', 'key' => @post.comments.map { |c| c.object_id } }, @post_serializer.serializable_hash) - ensure - @association.key = old_key end def test_associations_embedding_objects_serialization_using_serializable_hash @@ -55,32 +56,32 @@ module ActiveModel def test_associations_embedding_objects_serialization_using_serializable_hash_and_root_from_options @association.embed = :objects - old_embedded_key = @association.embedded_key @association.embedded_key = 'root' assert_equal({ 'title' => 'Title 1', 'body' => 'Body 1', 'root' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] }, @post_serializer.serializable_hash) - ensure - @association.embedded_key = old_embedded_key end def test_associations_embedding_ids_including_objects_serialization_using_serializable_hash - @association.include = true + @association.embed_in_root = true + @post_serializer.root = nil assert_equal({ - 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id }, 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] + 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } }, @post_serializer.serializable_hash) end def test_associations_embedding_ids_including_objects_serialization_using_as_json - @association.include = true + @association.embed_in_root = true + @post_serializer.root = nil assert_equal({ - 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id }, 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] + 'post' => { 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } }, + 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] }, @post_serializer.as_json) end def test_associations_using_a_given_serializer - @old_serializer = @association.serializer_class - @association.include = true + @association.embed_in_root = true + @post_serializer.root = nil @association.serializer_class = Class.new(ActiveModel::Serializer) do def content 'fake' @@ -90,10 +91,9 @@ module ActiveModel end assert_equal({ - 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id }, 'comments' => [{ 'content' => 'fake' }, { 'content' => 'fake' }] + 'post' => { 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } }, + 'comments' => [{ 'content' => 'fake' }, { 'content' => 'fake' }] }, @post_serializer.as_json) - ensure - @association.serializer_class = @old_serializer end end end diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index 2a34426b..f7028548 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -4,11 +4,15 @@ module ActiveModel class Serializer class HasOneTest < ActiveModel::TestCase def setup + @association = UserSerializer._associations[0] + @old_association = @association.dup + @association.embed = :ids @user = User.new({ name: 'Name 1', email: 'mail@server.com', gender: 'M' }) @user_serializer = UserSerializer.new(@user) - @association = UserSerializer._associations[0] - @association.include = false - @association.embed = :ids + end + + def teardown + UserSerializer._associations[0] = @old_association end def test_associations_definition @@ -30,13 +34,10 @@ module ActiveModel end def test_associations_embedding_ids_serialization_using_serializable_hash_and_key_from_options - old_key = @association.key @association.key = 'key' assert_equal({ 'name' => 'Name 1', 'email' => 'mail@server.com', 'key' => @user.profile.object_id }, @user_serializer.serializable_hash) - ensure - @association.key = old_key end def test_associations_embedding_objects_serialization_using_serializable_hash @@ -55,32 +56,32 @@ module ActiveModel def test_associations_embedding_objects_serialization_using_serializable_hash_and_root_from_options @association.embed = :objects - old_embedded_key = @association.embedded_key @association.embedded_key = 'root' assert_equal({ 'name' => 'Name 1', 'email' => 'mail@server.com', 'root' => [{ 'name' => 'N1', 'description' => 'D1' }] }, @user_serializer.serializable_hash) - ensure - @association.embedded_key = old_embedded_key end def test_associations_embedding_ids_including_objects_serialization_using_serializable_hash - @association.include = true + @association.embed_in_root = true + @user_serializer.root = nil assert_equal({ - 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id, 'profiles' => [{ 'name' => 'N1', 'description' => 'D1' }] + 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id }, @user_serializer.serializable_hash) end def test_associations_embedding_ids_including_objects_serialization_using_as_json - @association.include = true + @association.embed_in_root = true + @user_serializer.root = nil assert_equal({ - 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id, 'profiles' => [{ 'name' => 'N1', 'description' => 'D1' }] + 'user' => { 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id }, + 'profiles' => [{ 'name' => 'N1', 'description' => 'D1' }] }, @user_serializer.as_json) end def test_associations_using_a_given_serializer - @old_serializer = @association.serializer_class - @association.include = true + @association.embed_in_root = true + @user_serializer.root = nil @association.serializer_class = Class.new(ActiveModel::Serializer) do def name 'fake' @@ -90,10 +91,9 @@ module ActiveModel end assert_equal({ - 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id, 'profiles' => [{ 'name' => 'fake' }] + 'user' => { 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id }, + 'profiles' => [{ 'name' => 'fake' }] }, @user_serializer.as_json) - ensure - @association.serializer_class = @old_serializer end end end From 29148cbe1cb178320732818aad6676c7324cca7c Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 4 Sep 2013 18:32:04 -0300 Subject: [PATCH 34/73] Add AC::Serialization#serialization_scope as a class level method --- lib/action_controller/serialization.rb | 6 +++++ .../action_controller/serialization_test.rb | 24 +++++++++++++++++++ 2 files changed, 30 insertions(+) diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index a4faf972..cc007a58 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -34,6 +34,12 @@ module ActionController self._serialization_scope = :current_user end + module ClassMethods + def serialization_scope(scope) + self._serialization_scope = scope + end + end + def _render_option_json(resource, options) serializer = build_json_serializer(resource, options) diff --git a/test/integration/action_controller/serialization_test.rb b/test/integration/action_controller/serialization_test.rb index fe356cbc..66fcea8f 100644 --- a/test/integration/action_controller/serialization_test.rb +++ b/test/integration/action_controller/serialization_test.rb @@ -91,5 +91,29 @@ module ActionController assert_equal '{"name":"Name 1","description":"Description 1 - current_admin"}', @response.body end end + + class CallingSerializationScopeTest < ActionController::TestCase + class MyController < ActionController::Base + def render_calling_serialization_scope + render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + end + + private + + def current_user + 'current_user' + end + + serialization_scope :current_user + end + + tests MyController + + def test_render_calling_serialization_scope + get :render_calling_serialization_scope + assert_equal 'application/json', @response.content_type + assert_equal '{"name":"Name 1","description":"Description 1 - current_user"}', @response.body + end + end end end From 513e7f2166b03639e9c6472bddfc0883a18952e7 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 4 Sep 2013 21:01:13 -0300 Subject: [PATCH 35/73] Add setup method and Setting object to configure AMS --- lib/active_model/serializer.rb | 5 ++ lib/active_model/serializer/settings.rb | 27 ++++++++++ .../active_model/serializer/settings_test.rb | 49 +++++++++++++++++++ 3 files changed, 81 insertions(+) create mode 100644 lib/active_model/serializer/settings.rb create mode 100644 test/unit/active_model/serializer/settings_test.rb diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index a7af3235..2b782152 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -1,6 +1,7 @@ require 'active_model/array_serializer' require 'active_model/serializable' require 'active_model/serializer/associations' +require 'active_model/serializer/settings' module ActiveModel class Serializer @@ -12,6 +13,10 @@ module ActiveModel base._associations = [] end + def setup + yield SETTINGS + end + def serializer_for(resource) if resource.respond_to?(:to_ary) ArraySerializer diff --git a/lib/active_model/serializer/settings.rb b/lib/active_model/serializer/settings.rb new file mode 100644 index 00000000..4a3e7c9d --- /dev/null +++ b/lib/active_model/serializer/settings.rb @@ -0,0 +1,27 @@ +module ActiveModel + class Serializer + class Settings + def initialize + @data = {} + end + + def [](key) + @data[key.to_s] + end + + def []=(key, value) + @data[key.to_s] = value + end + + def each(&block) + @data.each(&block) + end + + def clear + @data.clear + end + end + + SETTINGS = Settings.new + end +end diff --git a/test/unit/active_model/serializer/settings_test.rb b/test/unit/active_model/serializer/settings_test.rb new file mode 100644 index 00000000..abd5531b --- /dev/null +++ b/test/unit/active_model/serializer/settings_test.rb @@ -0,0 +1,49 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class Settings + class Test < ActiveModel::TestCase + def test_settings_const_is_an_instance_of_settings + assert_kind_of Settings, SETTINGS + end + + def test_settings_instance + settings = Settings.new + settings[:setting1] = 'value1' + + assert_equal 'value1', settings[:setting1] + end + + def test_each_settings + settings = Settings.new + settings['setting1'] = 'value1' + settings['setting2'] = 'value2' + + actual = {} + + settings.each do |k, v| + actual[k] = v + end + + assert_equal({ 'setting1' => 'value1', 'setting2' => 'value2' }, actual) + + end + end + + class SetupTest < ActiveModel::TestCase + def test_setup + ActiveModel::Serializer.setup do |settings| + settings[:a] = 'v1' + settings[:b] = 'v2' + end + + assert_equal 'v1', SETTINGS[:a] + assert_equal 'v2', SETTINGS[:b] + ensure + SETTINGS.clear + end + end + end + end +end From 7405baafd7af21b3e19f1d0a4b43dcba6e6eeaa2 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 13 Sep 2013 13:23:45 -0300 Subject: [PATCH 36/73] Serialize associations that doesn't have an associated serializer --- lib/active_model/array_serializer.rb | 9 +++------ lib/active_model/default_serializer.rb | 17 +++++++++++++++++ lib/active_model/serializer/associations.rb | 10 +++------- .../active_model/default_serializer_test.rb | 15 +++++++++++++++ .../active_model/serializer/has_many_test.rb | 13 +++++++++++++ .../active_model/serializer/has_one_test.rb | 13 +++++++++++++ 6 files changed, 64 insertions(+), 13 deletions(-) create mode 100644 lib/active_model/default_serializer.rb create mode 100644 test/unit/active_model/default_serializer_test.rb diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb index 43e537f9..5a9e5c98 100644 --- a/lib/active_model/array_serializer.rb +++ b/lib/active_model/array_serializer.rb @@ -1,3 +1,4 @@ +require 'active_model/default_serializer' require 'active_model/serializable' require 'active_model/serializer' @@ -22,12 +23,8 @@ module ActiveModel def serializable_array @object.map do |item| - serializer = @options[:each_serializer] || Serializer.serializer_for(item) - if serializer - serializer.new(item).serializable_object(@options.merge(root: nil)) - else - item.as_json - end + serializer = @options[:each_serializer] || Serializer.serializer_for(item) || DefaultSerializer + serializer.new(item).serializable_object(@options.merge(root: nil)) end end alias serializable_object serializable_array diff --git a/lib/active_model/default_serializer.rb b/lib/active_model/default_serializer.rb new file mode 100644 index 00000000..7b5e52ce --- /dev/null +++ b/lib/active_model/default_serializer.rb @@ -0,0 +1,17 @@ +module ActiveModel + # DefaultSerializer + # + # Provides a constant interface for all items + class DefaultSerializer + attr_reader :object + + def initialize(object, options=nil) + @object = object + end + + def serializable_hash(*) + @object.as_json + end + alias serializable_object serializable_hash + end +end diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index 91d473fc..4d3ec5fe 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -1,3 +1,4 @@ +require 'active_model/default_serializer' require 'active_model/serializer' module ActiveModel @@ -32,13 +33,8 @@ module ActiveModel end def build_serializer(object) - @serializer_class ||= Serializer.serializer_for(object) - - if @serializer_class - @serializer_class.new(object, @options) - else - object - end + @serializer_class ||= Serializer.serializer_for(object) || DefaultSerializer + @serializer_class.new(object, @options) end class HasOne < Association diff --git a/test/unit/active_model/default_serializer_test.rb b/test/unit/active_model/default_serializer_test.rb new file mode 100644 index 00000000..4acafb78 --- /dev/null +++ b/test/unit/active_model/default_serializer_test.rb @@ -0,0 +1,15 @@ +require 'test_helper' + +module ActiveModel + class DefaultSerializer + class Test < ActiveModel::TestCase + def test_serialize_objects + assert_equal(nil, DefaultSerializer.new(nil).serializable_hash) + assert_equal(1, DefaultSerializer.new(1).serializable_hash) + assert_equal('hi', DefaultSerializer.new('hi').serializable_hash) + obj = Struct.new(:a, :b).new(1, 2) + assert_equal({ a: 1, b: 2 }, DefaultSerializer.new(obj).serializable_hash) + end + end + end +end diff --git a/test/unit/active_model/serializer/has_many_test.rb b/test/unit/active_model/serializer/has_many_test.rb index a4837a5b..c73ce346 100644 --- a/test/unit/active_model/serializer/has_many_test.rb +++ b/test/unit/active_model/serializer/has_many_test.rb @@ -54,6 +54,19 @@ module ActiveModel }, @post_serializer.as_json) end + def test_associations_embedding_nil_objects_serialization_using_as_json + @association.embed = :objects + @post.instance_eval do + def comments + [nil] + end + end + + assert_equal({ + 'title' => 'Title 1', 'body' => 'Body 1', 'comments' => [nil] + }, @post_serializer.as_json) + end + def test_associations_embedding_objects_serialization_using_serializable_hash_and_root_from_options @association.embed = :objects @association.embedded_key = 'root' diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index f7028548..d7cddfd5 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -54,6 +54,19 @@ module ActiveModel }, @user_serializer.as_json) end + def test_associations_embedding_nil_objects_serialization_using_as_json + @association.embed = :objects + @user.instance_eval do + def profile + nil + end + end + + assert_equal({ + 'name' => 'Name 1', 'email' => 'mail@server.com', 'profiles' => [nil] + }, @user_serializer.as_json) + end + def test_associations_embedding_objects_serialization_using_serializable_hash_and_root_from_options @association.embed = :objects @association.embedded_key = 'root' From 8462a73f3afee5fbe5f7a35c0e0f6841d1b21141 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sat, 14 Sep 2013 20:31:36 -0300 Subject: [PATCH 37/73] Make render json work when not using AMS --- lib/action_controller/serialization.rb | 2 ++ .../action_controller/serialization_test.rb | 16 ++++++++++++++++ 2 files changed, 18 insertions(+) diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index cc007a58..b0630ee7 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -68,6 +68,8 @@ module ActionController options.delete(:serializer) || ActiveModel::Serializer.serializer_for(resource) + return unless serializer + options[:scope] = serialization_scope unless options.has_key?(:scope) serializer.new(resource, options) diff --git a/test/integration/action_controller/serialization_test.rb b/test/integration/action_controller/serialization_test.rb index 66fcea8f..660d1670 100644 --- a/test/integration/action_controller/serialization_test.rb +++ b/test/integration/action_controller/serialization_test.rb @@ -115,5 +115,21 @@ module ActionController assert_equal '{"name":"Name 1","description":"Description 1 - current_user"}', @response.body end end + + class RailsSerializerTest < ActionController::TestCase + class MyController < ActionController::Base + def render_using_rails_behavior + render json: JSON.dump(hello: 'world') + end + end + + tests MyController + + def test_render_using_rails_behavior + get :render_using_rails_behavior + assert_equal 'application/json', @response.content_type + assert_equal '{"hello":"world"}', @response.body + end + end end end From 626a85bc3ec010f1b057abcb675641aa682a0f5e Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sat, 14 Sep 2013 21:15:04 -0300 Subject: [PATCH 38/73] Allow using root = false in initialize --- lib/active_model/array_serializer.rb | 3 ++- lib/active_model/serializer.rb | 3 ++- test/unit/active_model/array_serializer/root_test.rb | 10 ++++++++++ test/unit/active_model/serializer/root_test.rb | 9 +++++++++ 4 files changed, 23 insertions(+), 2 deletions(-) diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb index 5a9e5c98..471d7f4a 100644 --- a/lib/active_model/array_serializer.rb +++ b/lib/active_model/array_serializer.rb @@ -15,7 +15,8 @@ module ActiveModel def initialize(object, options={}) @object = object @options = options - @root = options[:root] || self.class._root + @root = options[:root] + @root = self.class._root if @root.nil? @meta_key = options[:meta_key] || :meta @meta = options[@meta_key] end diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 2b782152..83874b4a 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -85,7 +85,8 @@ module ActiveModel alias read_attribute_for_serialization send def root=(root) - @root = root || self.class._root + @root = root + @root = self.class._root if @root.nil? @root = self.class.root_name if auto_assign_root? end diff --git a/test/unit/active_model/array_serializer/root_test.rb b/test/unit/active_model/array_serializer/root_test.rb index 01aebf3d..a6eaf8f0 100644 --- a/test/unit/active_model/array_serializer/root_test.rb +++ b/test/unit/active_model/array_serializer/root_test.rb @@ -38,6 +38,16 @@ module ActiveModel ] }, @serializer.as_json(root: 'argument')) end + + def test_using_false_root_in_initialize_takes_precedence + ArraySerializer._root = 'root' + @serializer = ArraySerializer.new([@profile1, @profile2], root: false) + + assert_equal([ + { 'name' => 'Name 1', 'description' => 'Description 1' }, + { 'name' => 'Name 2', 'description' => 'Description 2' } + ], @serializer.as_json) + end end class RootInSerializerTest < ActiveModel::TestCase diff --git a/test/unit/active_model/serializer/root_test.rb b/test/unit/active_model/serializer/root_test.rb index 53a1658e..4c76c888 100644 --- a/test/unit/active_model/serializer/root_test.rb +++ b/test/unit/active_model/serializer/root_test.rb @@ -45,6 +45,15 @@ module ActiveModel } }, @serializer.as_json(root: 'argument')) end + + def test_using_false_root_in_initializer_takes_precedence + ProfileSerializer._root = 'root' + @serializer = ProfileSerializer.new(@profile, root: false) + + assert_equal({ + 'name' => 'Name 1', 'description' => 'Description 1' + }, @serializer.as_json) + end end class RootInSerializerTest < ActiveModel::TestCase From 6f3503c9653d3492c131c58dadd319dbddaf9ad0 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sat, 14 Sep 2013 22:04:41 -0300 Subject: [PATCH 39/73] Use serializer name as root when root not set --- lib/active_model/serializer.rb | 8 +------- .../action_controller/serialization_test.rb | 10 +++++----- test/integration/active_record/active_record_test.rb | 2 +- test/unit/active_model/serializer/attributes_test.rb | 2 +- test/unit/active_model/serializer/has_many_test.rb | 6 +++--- test/unit/active_model/serializer/has_one_test.rb | 6 +++--- 6 files changed, 14 insertions(+), 20 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 83874b4a..18072fd2 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -87,7 +87,7 @@ module ActiveModel def root=(root) @root = root @root = self.class._root if @root.nil? - @root = self.class.root_name if auto_assign_root? + @root = self.class.root_name if @root == true || @root.nil? end def attributes @@ -142,11 +142,5 @@ module ActiveModel hash.merge! associations end alias serializable_object serializable_hash - - private - - def auto_assign_root? - @root == true || !@root && self.class._associations.any? { |a| a.embed_in_root? } - end end end diff --git a/test/integration/action_controller/serialization_test.rb b/test/integration/action_controller/serialization_test.rb index 660d1670..d367988e 100644 --- a/test/integration/action_controller/serialization_test.rb +++ b/test/integration/action_controller/serialization_test.rb @@ -14,7 +14,7 @@ module ActionController def test_render_using_implicit_serializer get :render_using_implicit_serializer assert_equal 'application/json', @response.content_type - assert_equal '{"name":"Name 1","description":"Description 1"}', @response.body + assert_equal '{"profile":{"name":"Name 1","description":"Description 1"}}', @response.body end end @@ -36,7 +36,7 @@ module ActionController def test_render_using_implicit_serializer_and_scope get :render_using_implicit_serializer_and_scope assert_equal 'application/json', @response.content_type - assert_equal '{"name":"Name 1","description":"Description 1 - current_user"}', @response.body + assert_equal '{"profile":{"name":"Name 1","description":"Description 1 - current_user"}}', @response.body end end @@ -62,7 +62,7 @@ module ActionController def test_render_using_implicit_serializer_and_explicit_scope get :render_using_implicit_serializer_and_explicit_scope assert_equal 'application/json', @response.content_type - assert_equal '{"name":"Name 1","description":"Description 1 - current_admin"}', @response.body + assert_equal '{"profile":{"name":"Name 1","description":"Description 1 - current_admin"}}', @response.body end end @@ -88,7 +88,7 @@ module ActionController def test_render_overriding_serialization_scope get :render_overriding_serialization_scope assert_equal 'application/json', @response.content_type - assert_equal '{"name":"Name 1","description":"Description 1 - current_admin"}', @response.body + assert_equal '{"profile":{"name":"Name 1","description":"Description 1 - current_admin"}}', @response.body end end @@ -112,7 +112,7 @@ module ActionController def test_render_calling_serialization_scope get :render_calling_serialization_scope assert_equal 'application/json', @response.content_type - assert_equal '{"name":"Name 1","description":"Description 1 - current_user"}', @response.body + assert_equal '{"profile":{"name":"Name 1","description":"Description 1 - current_user"}}', @response.body end end diff --git a/test/integration/active_record/active_record_test.rb b/test/integration/active_record/active_record_test.rb index cfd04410..e87d0571 100644 --- a/test/integration/active_record/active_record_test.rb +++ b/test/integration/active_record/active_record_test.rb @@ -22,7 +22,7 @@ module ActiveModel def test_attributes_serialization_using_as_json assert_equal({ - 'name' => 'Name 1', 'description' => 'Description 1' + 'ar_profile' => { 'name' => 'Name 1', 'description' => 'Description 1' } }, @profile_serializer.as_json) end end diff --git a/test/unit/active_model/serializer/attributes_test.rb b/test/unit/active_model/serializer/attributes_test.rb index 10bad2b7..1091ccee 100644 --- a/test/unit/active_model/serializer/attributes_test.rb +++ b/test/unit/active_model/serializer/attributes_test.rb @@ -21,7 +21,7 @@ module ActiveModel def test_attributes_serialization_using_as_json assert_equal({ - 'name' => 'Name 1', 'description' => 'Description 1' + 'profile' => { 'name' => 'Name 1', 'description' => 'Description 1' } }, @profile_serializer.as_json) end end diff --git a/test/unit/active_model/serializer/has_many_test.rb b/test/unit/active_model/serializer/has_many_test.rb index c73ce346..3c0cddaa 100644 --- a/test/unit/active_model/serializer/has_many_test.rb +++ b/test/unit/active_model/serializer/has_many_test.rb @@ -29,7 +29,7 @@ module ActiveModel def test_associations_embedding_ids_serialization_using_as_json assert_equal({ - 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } + 'post' => { 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } } }, @post_serializer.as_json) end @@ -50,7 +50,7 @@ module ActiveModel def test_associations_embedding_objects_serialization_using_as_json @association.embed = :objects assert_equal({ - 'title' => 'Title 1', 'body' => 'Body 1', 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] + 'post' => { 'title' => 'Title 1', 'body' => 'Body 1', 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] } }, @post_serializer.as_json) end @@ -63,7 +63,7 @@ module ActiveModel end assert_equal({ - 'title' => 'Title 1', 'body' => 'Body 1', 'comments' => [nil] + 'post' => { 'title' => 'Title 1', 'body' => 'Body 1', 'comments' => [nil] } }, @post_serializer.as_json) end diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index d7cddfd5..3ff24fb5 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -29,7 +29,7 @@ module ActiveModel def test_associations_embedding_ids_serialization_using_as_json assert_equal({ - 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id + 'user' => { 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id } }, @user_serializer.as_json) end @@ -50,7 +50,7 @@ module ActiveModel def test_associations_embedding_objects_serialization_using_as_json @association.embed = :objects assert_equal({ - 'name' => 'Name 1', 'email' => 'mail@server.com', 'profiles' => [{ 'name' => 'N1', 'description' => 'D1' }] + 'user' => { 'name' => 'Name 1', 'email' => 'mail@server.com', 'profiles' => [{ 'name' => 'N1', 'description' => 'D1' }] } }, @user_serializer.as_json) end @@ -63,7 +63,7 @@ module ActiveModel end assert_equal({ - 'name' => 'Name 1', 'email' => 'mail@server.com', 'profiles' => [nil] + 'user' => { 'name' => 'Name 1', 'email' => 'mail@server.com', 'profiles' => [nil] } }, @user_serializer.as_json) end From 8006529e206385676b19d5affb69d11f60ad8632 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sat, 14 Sep 2013 22:25:59 -0300 Subject: [PATCH 40/73] Allow ArraySerializer to pass the options down to item serializers --- lib/active_model/array_serializer.rb | 2 +- .../array_serializer/scope_test.rb | 24 +++++++++++++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 test/unit/active_model/array_serializer/scope_test.rb diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb index 471d7f4a..16145a05 100644 --- a/lib/active_model/array_serializer.rb +++ b/lib/active_model/array_serializer.rb @@ -25,7 +25,7 @@ module ActiveModel def serializable_array @object.map do |item| serializer = @options[:each_serializer] || Serializer.serializer_for(item) || DefaultSerializer - serializer.new(item).serializable_object(@options.merge(root: nil)) + serializer.new(item, @options.merge(root: nil)).serializable_object end end alias serializable_object serializable_array diff --git a/test/unit/active_model/array_serializer/scope_test.rb b/test/unit/active_model/array_serializer/scope_test.rb new file mode 100644 index 00000000..60efa440 --- /dev/null +++ b/test/unit/active_model/array_serializer/scope_test.rb @@ -0,0 +1,24 @@ +require 'test_helper' + +module ActiveModel + class ArraySerializer + class ScopeTest < ActiveModel::TestCase + def test_array_serializer_pass_options_to_items_serializers + array = [Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }), + Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' })] + serializer = ArraySerializer.new(array, scope: current_user) + + expected = [{'name' => 'Name 1', 'description' => 'Description 1 - user'}, + {'name' => 'Name 2', 'description' => 'Description 2 - user'}] + + assert_equal expected, serializer.serializable_array + end + + private + + def current_user + 'user' + end + end + end +end From 86b9d5a226cb0299a41dc5852719397401998ca7 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sat, 14 Sep 2013 22:31:57 -0300 Subject: [PATCH 41/73] Avoid work inside serializable_array --- lib/active_model/array_serializer.rb | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb index 16145a05..3d06af45 100644 --- a/lib/active_model/array_serializer.rb +++ b/lib/active_model/array_serializer.rb @@ -13,19 +13,20 @@ module ActiveModel end def initialize(object, options={}) - @object = object - @options = options - @root = options[:root] - @root = self.class._root if @root.nil? - @meta_key = options[:meta_key] || :meta - @meta = options[@meta_key] + @object = object + @root = options[:root] + @root = self.class._root if @root.nil? + @meta_key = options[:meta_key] || :meta + @meta = options[@meta_key] + @each_serializer = options[:each_serializer] + @options = options.merge(root: nil) end attr_accessor :object, :root, :meta_key, :meta def serializable_array @object.map do |item| - serializer = @options[:each_serializer] || Serializer.serializer_for(item) || DefaultSerializer - serializer.new(item, @options.merge(root: nil)).serializable_object + serializer = @each_serializer || Serializer.serializer_for(item) || DefaultSerializer + serializer.new(item, @options).serializable_object end end alias serializable_object serializable_array From aa23e811cc7394485d768b128cd1e4e8930619ee Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 15 Sep 2013 13:39:32 -0300 Subject: [PATCH 42/73] Use controller name as root when serializing an array and not root is defined --- lib/action_controller/serialization.rb | 1 + lib/active_model/array_serializer.rb | 1 + .../action_controller/serialization_test.rb | 16 ++++++++++++++++ 3 files changed, 18 insertions(+) diff --git a/lib/action_controller/serialization.rb b/lib/action_controller/serialization.rb index b0630ee7..b2c605f5 100644 --- a/lib/action_controller/serialization.rb +++ b/lib/action_controller/serialization.rb @@ -71,6 +71,7 @@ module ActionController return unless serializer options[:scope] = serialization_scope unless options.has_key?(:scope) + options[:resource_name] = self.controller_name if resource.respond_to?(:to_ary) serializer.new(resource, options) end diff --git a/lib/active_model/array_serializer.rb b/lib/active_model/array_serializer.rb index 3d06af45..95bd449f 100644 --- a/lib/active_model/array_serializer.rb +++ b/lib/active_model/array_serializer.rb @@ -16,6 +16,7 @@ module ActiveModel @object = object @root = options[:root] @root = self.class._root if @root.nil? + @root = options[:resource_name] if @root.nil? @meta_key = options[:meta_key] || :meta @meta = options[@meta_key] @each_serializer = options[:each_serializer] diff --git a/test/integration/action_controller/serialization_test.rb b/test/integration/action_controller/serialization_test.rb index d367988e..d89ce9b1 100644 --- a/test/integration/action_controller/serialization_test.rb +++ b/test/integration/action_controller/serialization_test.rb @@ -131,5 +131,21 @@ module ActionController assert_equal '{"hello":"world"}', @response.body end end + + class ArraySerializerTest < ActionController::TestCase + class MyController < ActionController::Base + def render_array + render json: [Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' })] + end + end + + tests MyController + + def test_render_array + get :render_array + assert_equal 'application/json', @response.content_type + assert_equal '{"my":[{"name":"Name 1","description":"Description 1"}]}', @response.body + end + end end end From 10e882a14fa9c9269f4b1deff9cf660b967bdec7 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 15 Sep 2013 20:29:43 -0300 Subject: [PATCH 43/73] Allow to set embed options from AM::Serializer --- lib/active_model/serializer.rb | 5 +++++ lib/active_model/serializer/associations.rb | 4 ++-- test/unit/active_model/serializer/has_many_test.rb | 6 +++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 18072fd2..762dfb2c 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -17,6 +17,11 @@ module ActiveModel yield SETTINGS end + def embed(type, options={}) + SETTINGS[:embed] = type + SETTINGS[:include] = true if options[:include] + end + def serializer_for(resource) if resource.respond_to?(:to_ary) ArraySerializer diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index 4d3ec5fe..dd65c51c 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -8,8 +8,8 @@ module ActiveModel @name = name @options = options - self.embed = options[:embed] - @embed_in_root = @embed_ids && options[:include] + self.embed = options[:embed] || SETTINGS[:embed] + @embed_in_root = @embed_ids && (options[:include] || SETTINGS[:include]) @embed_key = options[:embed_key] || :id @key = options[:key] @embedded_key = options[:root] diff --git a/test/unit/active_model/serializer/has_many_test.rb b/test/unit/active_model/serializer/has_many_test.rb index 3c0cddaa..b2b99ebd 100644 --- a/test/unit/active_model/serializer/has_many_test.rb +++ b/test/unit/active_model/serializer/has_many_test.rb @@ -84,12 +84,16 @@ module ActiveModel end def test_associations_embedding_ids_including_objects_serialization_using_as_json - @association.embed_in_root = true + PostSerializer.embed :ids, include: true + PostSerializer._associations[0].send :initialize, @association.name, @association.options + @post_serializer.root = nil assert_equal({ 'post' => { 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } }, 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] }, @post_serializer.as_json) + ensure + SETTINGS.clear end def test_associations_using_a_given_serializer From 75e9a2599d763050dd3a7070721731568f9a433d Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 15 Sep 2013 22:07:10 -0300 Subject: [PATCH 44/73] Store attributes as they are instead of converting them into Strings --- lib/active_model/serializer.rb | 2 +- .../active_record/active_record_test.rb | 6 ++-- .../array_serializer/meta_test.rb | 16 +++++----- .../array_serializer/root_test.rb | 32 +++++++++---------- .../array_serializer/scope_test.rb | 4 +-- .../array_serializer/serialize_test.rb | 8 ++--- .../serializer/attributes_test.rb | 6 ++-- .../active_model/serializer/has_many_test.rb | 24 +++++++------- .../active_model/serializer/has_one_test.rb | 24 +++++++------- .../unit/active_model/serializer/meta_test.rb | 8 ++--- .../unit/active_model/serializer/root_test.rb | 18 +++++------ 11 files changed, 74 insertions(+), 74 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 762dfb2c..ee63417c 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -39,7 +39,7 @@ module ActiveModel end def attributes(*attrs) - @_attributes.concat attrs.map(&:to_s) + @_attributes.concat attrs attrs.each do |attr| unless method_defined?(attr) diff --git a/test/integration/active_record/active_record_test.rb b/test/integration/active_record/active_record_test.rb index e87d0571..65524402 100644 --- a/test/integration/active_record/active_record_test.rb +++ b/test/integration/active_record/active_record_test.rb @@ -10,19 +10,19 @@ module ActiveModel end def test_attributes_definition - assert_equal(['name', 'description'], + assert_equal([:name, :description], @profile_serializer.class._attributes) end def test_attributes_serialization_using_serializable_hash assert_equal({ - 'name' => 'Name 1', 'description' => 'Description 1' + name: 'Name 1', description: 'Description 1' }, @profile_serializer.serializable_hash) end def test_attributes_serialization_using_as_json assert_equal({ - 'ar_profile' => { 'name' => 'Name 1', 'description' => 'Description 1' } + 'ar_profile' => { name: 'Name 1', description: 'Description 1' } }, @profile_serializer.as_json) end end diff --git a/test/unit/active_model/array_serializer/meta_test.rb b/test/unit/active_model/array_serializer/meta_test.rb index 1f3e4ce8..ffecea07 100644 --- a/test/unit/active_model/array_serializer/meta_test.rb +++ b/test/unit/active_model/array_serializer/meta_test.rb @@ -16,11 +16,11 @@ module ActiveModel assert_equal({ 'profiles' => [ { - 'name' => 'Name 1', - 'description' => 'Description 1' + name: 'Name 1', + description: 'Description 1' }, { - 'name' => 'Name 2', - 'description' => 'Description 2' + name: 'Name 2', + description: 'Description 2' } ], 'meta' => { @@ -36,11 +36,11 @@ module ActiveModel assert_equal({ 'profiles' => [ { - 'name' => 'Name 1', - 'description' => 'Description 1' + name: 'Name 1', + description: 'Description 1' }, { - 'name' => 'Name 2', - 'description' => 'Description 2' + name: 'Name 2', + description: 'Description 2' } ], 'my_meta' => { diff --git a/test/unit/active_model/array_serializer/root_test.rb b/test/unit/active_model/array_serializer/root_test.rb index a6eaf8f0..ec342e2a 100644 --- a/test/unit/active_model/array_serializer/root_test.rb +++ b/test/unit/active_model/array_serializer/root_test.rb @@ -16,16 +16,16 @@ module ActiveModel def test_root_is_not_displayed_using_serializable_array assert_equal([ - { 'name' => 'Name 1', 'description' => 'Description 1' }, - { 'name' => 'Name 2', 'description' => 'Description 2' } + { name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' } ], @serializer.serializable_array) end def test_root_using_as_json assert_equal({ 'initialize' => [ - { 'name' => 'Name 1', 'description' => 'Description 1' }, - { 'name' => 'Name 2', 'description' => 'Description 2' } + { name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' } ] }, @serializer.as_json) end @@ -33,8 +33,8 @@ module ActiveModel def test_root_as_argument_takes_precedence assert_equal({ 'argument' => [ - { 'name' => 'Name 1', 'description' => 'Description 1' }, - { 'name' => 'Name 2', 'description' => 'Description 2' } + { name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' } ] }, @serializer.as_json(root: 'argument')) end @@ -44,8 +44,8 @@ module ActiveModel @serializer = ArraySerializer.new([@profile1, @profile2], root: false) assert_equal([ - { 'name' => 'Name 1', 'description' => 'Description 1' }, - { 'name' => 'Name 2', 'description' => 'Description 2' } + { name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' } ], @serializer.as_json) end end @@ -66,16 +66,16 @@ module ActiveModel def test_root_is_not_displayed_using_serializable_hash assert_equal([ - { 'name' => 'Name 1', 'description' => 'Description 1' }, - { 'name' => 'Name 2', 'description' => 'Description 2' } + { name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' } ], @serializer.serializable_array) end def test_root_using_as_json assert_equal({ 'in_serializer' => [ - { 'name' => 'Name 1', 'description' => 'Description 1' }, - { 'name' => 'Name 2', 'description' => 'Description 2' } + { name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' } ] }, @serializer.as_json) end @@ -83,8 +83,8 @@ module ActiveModel def test_root_in_initializer_takes_precedence assert_equal({ 'initialize' => [ - { 'name' => 'Name 1', 'description' => 'Description 1' }, - { 'name' => 'Name 2', 'description' => 'Description 2' } + { name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' } ] }, @rooted_serializer.as_json) end @@ -92,8 +92,8 @@ module ActiveModel def test_root_as_argument_takes_precedence assert_equal({ 'argument' => [ - { 'name' => 'Name 1', 'description' => 'Description 1' }, - { 'name' => 'Name 2', 'description' => 'Description 2' } + { name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' } ] }, @rooted_serializer.as_json(root: :argument)) end diff --git a/test/unit/active_model/array_serializer/scope_test.rb b/test/unit/active_model/array_serializer/scope_test.rb index 60efa440..b9c6034f 100644 --- a/test/unit/active_model/array_serializer/scope_test.rb +++ b/test/unit/active_model/array_serializer/scope_test.rb @@ -8,8 +8,8 @@ module ActiveModel Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' })] serializer = ArraySerializer.new(array, scope: current_user) - expected = [{'name' => 'Name 1', 'description' => 'Description 1 - user'}, - {'name' => 'Name 2', 'description' => 'Description 2 - user'}] + expected = [{ name: 'Name 1', description: 'Description 1 - user' }, + { name: 'Name 2', description: 'Description 2 - user' }] assert_equal expected, serializer.serializable_array end diff --git a/test/unit/active_model/array_serializer/serialize_test.rb b/test/unit/active_model/array_serializer/serialize_test.rb index 33d3c0f8..01f2bfa7 100644 --- a/test/unit/active_model/array_serializer/serialize_test.rb +++ b/test/unit/active_model/array_serializer/serialize_test.rb @@ -22,8 +22,8 @@ module ActiveModel Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' })] serializer = ArraySerializer.new(array) - expected = [{'name' => 'Name 1', 'description' => 'Description 1'}, - {'name' => 'Name 2', 'description' => 'Description 2'}] + expected = [{ name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' }] assert_equal expected, serializer.serializable_array assert_equal expected, serializer.as_json @@ -34,8 +34,8 @@ module ActiveModel ::Model.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' })] serializer = ArraySerializer.new(array, each_serializer: ProfileSerializer) - expected = [{'name' => 'Name 1', 'description' => 'Description 1'}, - {'name' => 'Name 2', 'description' => 'Description 2'}] + expected = [{ name: 'Name 1', description: 'Description 1' }, + { name: 'Name 2', description: 'Description 2' }] assert_equal expected, serializer.serializable_array assert_equal expected, serializer.as_json diff --git a/test/unit/active_model/serializer/attributes_test.rb b/test/unit/active_model/serializer/attributes_test.rb index 1091ccee..0914747e 100644 --- a/test/unit/active_model/serializer/attributes_test.rb +++ b/test/unit/active_model/serializer/attributes_test.rb @@ -9,19 +9,19 @@ module ActiveModel end def test_attributes_definition - assert_equal(['name', 'description'], + assert_equal([:name, :description], @profile_serializer.class._attributes) end def test_attributes_serialization_using_serializable_hash assert_equal({ - 'name' => 'Name 1', 'description' => 'Description 1' + name: 'Name 1', description: 'Description 1' }, @profile_serializer.serializable_hash) end def test_attributes_serialization_using_as_json assert_equal({ - 'profile' => { 'name' => 'Name 1', 'description' => 'Description 1' } + 'profile' => { name: 'Name 1', description: 'Description 1' } }, @profile_serializer.as_json) end end diff --git a/test/unit/active_model/serializer/has_many_test.rb b/test/unit/active_model/serializer/has_many_test.rb index b2b99ebd..c8769ffb 100644 --- a/test/unit/active_model/serializer/has_many_test.rb +++ b/test/unit/active_model/serializer/has_many_test.rb @@ -23,34 +23,34 @@ module ActiveModel def test_associations_embedding_ids_serialization_using_serializable_hash assert_equal({ - 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } + title: 'Title 1', body: 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } }, @post_serializer.serializable_hash) end def test_associations_embedding_ids_serialization_using_as_json assert_equal({ - 'post' => { 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } } + 'post' => { title: 'Title 1', body: 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } } }, @post_serializer.as_json) end def test_associations_embedding_ids_serialization_using_serializable_hash_and_key_from_options @association.key = 'key' assert_equal({ - 'title' => 'Title 1', 'body' => 'Body 1', 'key' => @post.comments.map { |c| c.object_id } + title: 'Title 1', body: 'Body 1', 'key' => @post.comments.map { |c| c.object_id } }, @post_serializer.serializable_hash) end def test_associations_embedding_objects_serialization_using_serializable_hash @association.embed = :objects assert_equal({ - 'title' => 'Title 1', 'body' => 'Body 1', 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] + title: 'Title 1', body: 'Body 1', 'comments' => [{ content: 'C1' }, { content: 'C2' }] }, @post_serializer.serializable_hash) end def test_associations_embedding_objects_serialization_using_as_json @association.embed = :objects assert_equal({ - 'post' => { 'title' => 'Title 1', 'body' => 'Body 1', 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] } + 'post' => { title: 'Title 1', body: 'Body 1', 'comments' => [{ content: 'C1' }, { content: 'C2' }] } }, @post_serializer.as_json) end @@ -63,7 +63,7 @@ module ActiveModel end assert_equal({ - 'post' => { 'title' => 'Title 1', 'body' => 'Body 1', 'comments' => [nil] } + 'post' => { title: 'Title 1', body: 'Body 1', 'comments' => [nil] } }, @post_serializer.as_json) end @@ -71,7 +71,7 @@ module ActiveModel @association.embed = :objects @association.embedded_key = 'root' assert_equal({ - 'title' => 'Title 1', 'body' => 'Body 1', 'root' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] + title: 'Title 1', body: 'Body 1', 'root' => [{ content: 'C1' }, { content: 'C2' }] }, @post_serializer.serializable_hash) end @@ -79,7 +79,7 @@ module ActiveModel @association.embed_in_root = true @post_serializer.root = nil assert_equal({ - 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } + title: 'Title 1', body: 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } }, @post_serializer.serializable_hash) end @@ -89,8 +89,8 @@ module ActiveModel @post_serializer.root = nil assert_equal({ - 'post' => { 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } }, - 'comments' => [{ 'content' => 'C1' }, { 'content' => 'C2' }] + 'post' => { title: 'Title 1', body: 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } }, + 'comments' => [{ content: 'C1' }, { content: 'C2' }] }, @post_serializer.as_json) ensure SETTINGS.clear @@ -108,8 +108,8 @@ module ActiveModel end assert_equal({ - 'post' => { 'title' => 'Title 1', 'body' => 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } }, - 'comments' => [{ 'content' => 'fake' }, { 'content' => 'fake' }] + 'post' => { title: 'Title 1', body: 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } }, + 'comments' => [{ content: 'fake' }, { content: 'fake' }] }, @post_serializer.as_json) end end diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index 3ff24fb5..62ba2344 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -23,34 +23,34 @@ module ActiveModel def test_associations_embedding_ids_serialization_using_serializable_hash assert_equal({ - 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id + name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id }, @user_serializer.serializable_hash) end def test_associations_embedding_ids_serialization_using_as_json assert_equal({ - 'user' => { 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id } + 'user' => { name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id } }, @user_serializer.as_json) end def test_associations_embedding_ids_serialization_using_serializable_hash_and_key_from_options @association.key = 'key' assert_equal({ - 'name' => 'Name 1', 'email' => 'mail@server.com', 'key' => @user.profile.object_id + name: 'Name 1', email: 'mail@server.com', 'key' => @user.profile.object_id }, @user_serializer.serializable_hash) end def test_associations_embedding_objects_serialization_using_serializable_hash @association.embed = :objects assert_equal({ - 'name' => 'Name 1', 'email' => 'mail@server.com', 'profiles' => [{ 'name' => 'N1', 'description' => 'D1' }] + name: 'Name 1', email: 'mail@server.com', 'profiles' => [{ name: 'N1', description: 'D1' }] }, @user_serializer.serializable_hash) end def test_associations_embedding_objects_serialization_using_as_json @association.embed = :objects assert_equal({ - 'user' => { 'name' => 'Name 1', 'email' => 'mail@server.com', 'profiles' => [{ 'name' => 'N1', 'description' => 'D1' }] } + 'user' => { name: 'Name 1', email: 'mail@server.com', 'profiles' => [{ name: 'N1', description: 'D1' }] } }, @user_serializer.as_json) end @@ -63,7 +63,7 @@ module ActiveModel end assert_equal({ - 'user' => { 'name' => 'Name 1', 'email' => 'mail@server.com', 'profiles' => [nil] } + 'user' => { name: 'Name 1', email: 'mail@server.com', 'profiles' => [nil] } }, @user_serializer.as_json) end @@ -71,7 +71,7 @@ module ActiveModel @association.embed = :objects @association.embedded_key = 'root' assert_equal({ - 'name' => 'Name 1', 'email' => 'mail@server.com', 'root' => [{ 'name' => 'N1', 'description' => 'D1' }] + name: 'Name 1', email: 'mail@server.com', 'root' => [{ name: 'N1', description: 'D1' }] }, @user_serializer.serializable_hash) end @@ -79,7 +79,7 @@ module ActiveModel @association.embed_in_root = true @user_serializer.root = nil assert_equal({ - 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id + name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id }, @user_serializer.serializable_hash) end @@ -87,8 +87,8 @@ module ActiveModel @association.embed_in_root = true @user_serializer.root = nil assert_equal({ - 'user' => { 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id }, - 'profiles' => [{ 'name' => 'N1', 'description' => 'D1' }] + 'user' => { name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id }, + 'profiles' => [{ name: 'N1', description: 'D1' }] }, @user_serializer.as_json) end @@ -104,8 +104,8 @@ module ActiveModel end assert_equal({ - 'user' => { 'name' => 'Name 1', 'email' => 'mail@server.com', 'profile_id' => @user.profile.object_id }, - 'profiles' => [{ 'name' => 'fake' }] + 'user' => { name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id }, + 'profiles' => [{ name: 'fake' }] }, @user_serializer.as_json) end end diff --git a/test/unit/active_model/serializer/meta_test.rb b/test/unit/active_model/serializer/meta_test.rb index a006bf1e..6eb66045 100644 --- a/test/unit/active_model/serializer/meta_test.rb +++ b/test/unit/active_model/serializer/meta_test.rb @@ -12,8 +12,8 @@ module ActiveModel assert_equal({ 'profile' => { - 'name' => 'Name 1', - 'description' => 'Description 1' + name: 'Name 1', + description: 'Description 1' }, 'meta' => { 'total' => 10 @@ -26,8 +26,8 @@ module ActiveModel assert_equal({ 'profile' => { - 'name' => 'Name 1', - 'description' => 'Description 1' + name: 'Name 1', + description: 'Description 1' }, 'my_meta' => { 'total' => 10 diff --git a/test/unit/active_model/serializer/root_test.rb b/test/unit/active_model/serializer/root_test.rb index 4c76c888..12d36bc1 100644 --- a/test/unit/active_model/serializer/root_test.rb +++ b/test/unit/active_model/serializer/root_test.rb @@ -16,14 +16,14 @@ module ActiveModel def test_root_is_not_displayed_using_serializable_hash assert_equal({ - 'name' => 'Name 1', 'description' => 'Description 1' + name: 'Name 1', description: 'Description 1' }, @serializer.serializable_hash) end def test_root_using_as_json assert_equal({ 'initialize' => { - 'name' => 'Name 1', 'description' => 'Description 1' + name: 'Name 1', description: 'Description 1' } }, @serializer.as_json) end @@ -33,7 +33,7 @@ module ActiveModel assert_equal({ 'profile' => { - 'name' => 'Name 1', 'description' => 'Description 1' + name: 'Name 1', description: 'Description 1' } }, @serializer.as_json) end @@ -41,7 +41,7 @@ module ActiveModel def test_root_as_argument_takes_precedence assert_equal({ 'argument' => { - 'name' => 'Name 1', 'description' => 'Description 1' + name: 'Name 1', description: 'Description 1' } }, @serializer.as_json(root: 'argument')) end @@ -51,7 +51,7 @@ module ActiveModel @serializer = ProfileSerializer.new(@profile, root: false) assert_equal({ - 'name' => 'Name 1', 'description' => 'Description 1' + name: 'Name 1', description: 'Description 1' }, @serializer.as_json) end end @@ -71,14 +71,14 @@ module ActiveModel def test_root_is_not_displayed_using_serializable_hash assert_equal({ - 'name' => 'Name 1', 'description' => 'Description 1' + name: 'Name 1', description: 'Description 1' }, @serializer.serializable_hash) end def test_root_using_as_json assert_equal({ 'in_serializer' => { - 'name' => 'Name 1', 'description' => 'Description 1' + name: 'Name 1', description: 'Description 1' } }, @serializer.as_json) end @@ -86,7 +86,7 @@ module ActiveModel def test_root_in_initializer_takes_precedence assert_equal({ 'initialize' => { - 'name' => 'Name 1', 'description' => 'Description 1' + name: 'Name 1', description: 'Description 1' } }, @rooted_serializer.as_json) end @@ -94,7 +94,7 @@ module ActiveModel def test_root_as_argument_takes_precedence assert_equal({ 'argument' => { - 'name' => 'Name 1', 'description' => 'Description 1' + name: 'Name 1', description: 'Description 1' } }, @rooted_serializer.as_json(root: :argument)) end From daa9304398cb81524d81f0f896838e054aa9b7f3 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Sun, 15 Sep 2013 23:43:41 -0300 Subject: [PATCH 45/73] Do not convert root and meta_key to Strings --- lib/active_model/serializable.rb | 4 ++-- .../active_model/array_serializer/meta_test.rb | 12 ++++++------ .../active_model/array_serializer/root_test.rb | 16 ++++++++-------- test/unit/active_model/serializer/meta_test.rb | 12 ++++++------ test/unit/active_model/serializer/root_test.rb | 16 ++++++++-------- 5 files changed, 30 insertions(+), 30 deletions(-) diff --git a/lib/active_model/serializable.rb b/lib/active_model/serializable.rb index 897ea13f..fa4b7d83 100644 --- a/lib/active_model/serializable.rb +++ b/lib/active_model/serializable.rb @@ -2,7 +2,7 @@ module ActiveModel module Serializable def as_json(options={}) if root = options[:root] || self.root - hash = { root.to_s => serializable_object } + hash = { root => serializable_object } hash.merge!(serializable_data) hash else @@ -12,7 +12,7 @@ module ActiveModel def serializable_data if respond_to?(:meta) && meta - { meta_key.to_s => meta } + { meta_key => meta } else {} end diff --git a/test/unit/active_model/array_serializer/meta_test.rb b/test/unit/active_model/array_serializer/meta_test.rb index ffecea07..08b3db06 100644 --- a/test/unit/active_model/array_serializer/meta_test.rb +++ b/test/unit/active_model/array_serializer/meta_test.rb @@ -11,7 +11,7 @@ module ActiveModel end def test_meta - @serializer.meta = { 'total' => 10 } + @serializer.meta = { total: 10 } assert_equal({ 'profiles' => [ @@ -23,15 +23,15 @@ module ActiveModel description: 'Description 2' } ], - 'meta' => { - 'total' => 10 + meta: { + total: 10 } }, @serializer.as_json) end def test_meta_using_meta_key @serializer.meta_key = :my_meta - @serializer.meta = { 'total' => 10 } + @serializer.meta = { total: 10 } assert_equal({ 'profiles' => [ @@ -43,8 +43,8 @@ module ActiveModel description: 'Description 2' } ], - 'my_meta' => { - 'total' => 10 + my_meta: { + total: 10 } }, @serializer.as_json) end diff --git a/test/unit/active_model/array_serializer/root_test.rb b/test/unit/active_model/array_serializer/root_test.rb index ec342e2a..3453b7a2 100644 --- a/test/unit/active_model/array_serializer/root_test.rb +++ b/test/unit/active_model/array_serializer/root_test.rb @@ -7,7 +7,7 @@ module ActiveModel @old_root = ArraySerializer._root @profile1 = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) @profile2 = Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' }) - @serializer = ArraySerializer.new([@profile1, @profile2], root: 'initialize') + @serializer = ArraySerializer.new([@profile1, @profile2], root: :initialize) end def teardown @@ -23,7 +23,7 @@ module ActiveModel def test_root_using_as_json assert_equal({ - 'initialize' => [ + initialize: [ { name: 'Name 1', description: 'Description 1' }, { name: 'Name 2', description: 'Description 2' } ] @@ -32,11 +32,11 @@ module ActiveModel def test_root_as_argument_takes_precedence assert_equal({ - 'argument' => [ + argument: [ { name: 'Name 1', description: 'Description 1' }, { name: 'Name 2', description: 'Description 2' } ] - }, @serializer.as_json(root: 'argument')) + }, @serializer.as_json(root: :argument)) end def test_using_false_root_in_initialize_takes_precedence @@ -53,7 +53,7 @@ module ActiveModel class RootInSerializerTest < ActiveModel::TestCase def setup @old_root = ArraySerializer._root - ArraySerializer._root = 'in_serializer' + ArraySerializer._root = :in_serializer @profile1 = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) @profile2 = Profile.new({ name: 'Name 2', description: 'Description 2', comments: 'Comments 2' }) @serializer = ArraySerializer.new([@profile1, @profile2]) @@ -73,7 +73,7 @@ module ActiveModel def test_root_using_as_json assert_equal({ - 'in_serializer' => [ + in_serializer: [ { name: 'Name 1', description: 'Description 1' }, { name: 'Name 2', description: 'Description 2' } ] @@ -82,7 +82,7 @@ module ActiveModel def test_root_in_initializer_takes_precedence assert_equal({ - 'initialize' => [ + initialize: [ { name: 'Name 1', description: 'Description 1' }, { name: 'Name 2', description: 'Description 2' } ] @@ -91,7 +91,7 @@ module ActiveModel def test_root_as_argument_takes_precedence assert_equal({ - 'argument' => [ + argument: [ { name: 'Name 1', description: 'Description 1' }, { name: 'Name 2', description: 'Description 2' } ] diff --git a/test/unit/active_model/serializer/meta_test.rb b/test/unit/active_model/serializer/meta_test.rb index 6eb66045..c5d00bf6 100644 --- a/test/unit/active_model/serializer/meta_test.rb +++ b/test/unit/active_model/serializer/meta_test.rb @@ -8,29 +8,29 @@ module ActiveModel end def test_meta - profile_serializer = ProfileSerializer.new(@profile, root: 'profile', meta: { 'total' => 10 }) + profile_serializer = ProfileSerializer.new(@profile, root: 'profile', meta: { total: 10 }) assert_equal({ 'profile' => { name: 'Name 1', description: 'Description 1' }, - 'meta' => { - 'total' => 10 + meta: { + total: 10 } }, profile_serializer.as_json) end def test_meta_using_meta_key - profile_serializer = ProfileSerializer.new(@profile, root: 'profile', meta_key: :my_meta, my_meta: { 'total' => 10 }) + profile_serializer = ProfileSerializer.new(@profile, root: 'profile', meta_key: :my_meta, my_meta: { total: 10 }) assert_equal({ 'profile' => { name: 'Name 1', description: 'Description 1' }, - 'my_meta' => { - 'total' => 10 + my_meta: { + total: 10 } }, profile_serializer.as_json) end diff --git a/test/unit/active_model/serializer/root_test.rb b/test/unit/active_model/serializer/root_test.rb index 12d36bc1..6cf2c546 100644 --- a/test/unit/active_model/serializer/root_test.rb +++ b/test/unit/active_model/serializer/root_test.rb @@ -6,7 +6,7 @@ module ActiveModel def setup @old_root = ProfileSerializer._root @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) - @serializer = ProfileSerializer.new(@profile, root: 'initialize') + @serializer = ProfileSerializer.new(@profile, root: :initialize) ProfileSerializer._root = true end @@ -22,7 +22,7 @@ module ActiveModel def test_root_using_as_json assert_equal({ - 'initialize' => { + initialize: { name: 'Name 1', description: 'Description 1' } }, @serializer.as_json) @@ -40,10 +40,10 @@ module ActiveModel def test_root_as_argument_takes_precedence assert_equal({ - 'argument' => { + argument: { name: 'Name 1', description: 'Description 1' } - }, @serializer.as_json(root: 'argument')) + }, @serializer.as_json(root: :argument)) end def test_using_false_root_in_initializer_takes_precedence @@ -59,7 +59,7 @@ module ActiveModel class RootInSerializerTest < ActiveModel::TestCase def setup @old_root = ProfileSerializer._root - ProfileSerializer._root = 'in_serializer' + ProfileSerializer._root = :in_serializer profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) @serializer = ProfileSerializer.new(profile) @rooted_serializer = ProfileSerializer.new(profile, root: :initialize) @@ -77,7 +77,7 @@ module ActiveModel def test_root_using_as_json assert_equal({ - 'in_serializer' => { + in_serializer: { name: 'Name 1', description: 'Description 1' } }, @serializer.as_json) @@ -85,7 +85,7 @@ module ActiveModel def test_root_in_initializer_takes_precedence assert_equal({ - 'initialize' => { + initialize: { name: 'Name 1', description: 'Description 1' } }, @rooted_serializer.as_json) @@ -93,7 +93,7 @@ module ActiveModel def test_root_as_argument_takes_precedence assert_equal({ - 'argument' => { + argument: { name: 'Name 1', description: 'Description 1' } }, @rooted_serializer.as_json(root: :argument)) From f6ea07dd2261770bbee15d29fb2b3fe8d5b88a9d Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 16 Sep 2013 10:39:21 -0300 Subject: [PATCH 46/73] Do not convert attrs to String until needed --- lib/active_model/serializer.rb | 2 -- lib/active_model/serializer/associations.rb | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index ee63417c..26929181 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -64,8 +64,6 @@ module ActiveModel options = attrs.extract_options! attrs.each do |attr| - attr = attr.to_s - unless method_defined?(attr) define_method attr do object.send attr diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index dd65c51c..31cf2eda 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -5,7 +5,7 @@ module ActiveModel class Serializer class Association def initialize(name, options={}) - @name = name + @name = name.to_s @options = options self.embed = options[:embed] || SETTINGS[:embed] From 841f3b8181e909225a298bbf70f2d09a2f5652dc Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 16 Sep 2013 11:41:56 -0300 Subject: [PATCH 47/73] Add filter to allow users implement filter method to include/exclude attributes and relations --- lib/active_model/serializer.rb | 34 ++++++++----- .../active_model/serializer/filter_test.rb | 49 +++++++++++++++++++ .../active_model/serializer/has_many_test.rb | 6 +-- .../active_model/serializer/has_one_test.rb | 4 +- 4 files changed, 77 insertions(+), 16 deletions(-) create mode 100644 test/unit/active_model/serializer/filter_test.rb diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 26929181..f0a23381 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -10,7 +10,7 @@ module ActiveModel class << self def inherited(base) base._attributes = [] - base._associations = [] + base._associations = {} end def setup @@ -70,7 +70,7 @@ module ActiveModel end end - @_associations << klass.new(attr, options) + @_associations[attr] = klass.new(attr, options) end end end @@ -94,29 +94,41 @@ module ActiveModel end def attributes - self.class._attributes.each_with_object({}) do |name, hash| + filter(self.class._attributes.dup).each_with_object({}) do |name, hash| hash[name] = send(name) end end def associations - self.class._associations.each_with_object({}) do |association, hash| - if association.embed_ids? - hash[association.key] = serialize_ids association - elsif association.embed_objects? - hash[association.embedded_key] = serialize association + associations = self.class._associations + included_associations = filter(associations.keys) + associations.each_with_object({}) do |(name, association), hash| + if included_associations.include? name + if association.embed_ids? + hash[association.key] = serialize_ids association + elsif association.embed_objects? + hash[association.embedded_key] = serialize association + end end end end + def filter(keys) + keys + end + def serializable_data embedded_in_root_associations.merge!(super) end def embedded_in_root_associations - self.class._associations.each_with_object({}) do |association, hash| - if association.embed_in_root? - hash[association.embedded_key] = serialize association + associations = self.class._associations + included_associations = filter(associations.keys) + associations.each_with_object({}) do |(name, association), hash| + if included_associations.include? name + if association.embed_in_root? + hash[association.embedded_key] = serialize association + end end end end diff --git a/test/unit/active_model/serializer/filter_test.rb b/test/unit/active_model/serializer/filter_test.rb new file mode 100644 index 00000000..bf33b5e7 --- /dev/null +++ b/test/unit/active_model/serializer/filter_test.rb @@ -0,0 +1,49 @@ +require 'test_helper' + +module ActiveModel + class Serializer + class FilterAttributesTest < ActiveModel::TestCase + def setup + @profile = Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + @profile_serializer = ProfileSerializer.new(@profile) + @profile_serializer.instance_eval do + def filter(keys) + keys - [:description] + end + end + end + + def test_filtered_attributes_serialization + assert_equal({ + 'profile' => { name: 'Name 1' } + }, @profile_serializer.as_json) + end + end + + class FilterAssociationsTest < ActiveModel::TestCase + def setup + @association = PostSerializer._associations[:comments] + @old_association = @association.dup + @association.embed = :ids + @association.embed_in_root = true + @post = Post.new({ title: 'Title 1', body: 'Body 1', date: '1/1/2000' }) + @post_serializer = PostSerializer.new(@post) + @post_serializer.instance_eval do + def filter(keys) + keys - [:body, :comments] + end + end + end + + def teardown + PostSerializer._associations[:comments] = @old_association + end + + def test_filtered_associations_serialization + assert_equal({ + 'post' => { title: 'Title 1' } + }, @post_serializer.as_json) + end + end + end +end diff --git a/test/unit/active_model/serializer/has_many_test.rb b/test/unit/active_model/serializer/has_many_test.rb index c8769ffb..56d5dff3 100644 --- a/test/unit/active_model/serializer/has_many_test.rb +++ b/test/unit/active_model/serializer/has_many_test.rb @@ -4,7 +4,7 @@ module ActiveModel class Serializer class HasManyTest < ActiveModel::TestCase def setup - @association = PostSerializer._associations[0] + @association = PostSerializer._associations[:comments] @old_association = @association.dup @association.embed = :ids @post = Post.new({ title: 'Title 1', body: 'Body 1', date: '1/1/2000' }) @@ -12,7 +12,7 @@ module ActiveModel end def teardown - PostSerializer._associations[0] = @old_association + PostSerializer._associations[:comments] = @old_association end def test_associations_definition @@ -85,7 +85,7 @@ module ActiveModel def test_associations_embedding_ids_including_objects_serialization_using_as_json PostSerializer.embed :ids, include: true - PostSerializer._associations[0].send :initialize, @association.name, @association.options + PostSerializer._associations[:comments].send :initialize, @association.name, @association.options @post_serializer.root = nil assert_equal({ diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index 62ba2344..44b5ec2e 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -4,7 +4,7 @@ module ActiveModel class Serializer class HasOneTest < ActiveModel::TestCase def setup - @association = UserSerializer._associations[0] + @association = UserSerializer._associations[:profile] @old_association = @association.dup @association.embed = :ids @user = User.new({ name: 'Name 1', email: 'mail@server.com', gender: 'M' }) @@ -12,7 +12,7 @@ module ActiveModel end def teardown - UserSerializer._associations[0] = @old_association + UserSerializer._associations[:profile] = @old_association end def test_associations_definition From cad8fafa601c6a1f1449ec524c027a97fbeb84d4 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 16 Sep 2013 12:22:23 -0300 Subject: [PATCH 48/73] Optimize serializer_for for Ruby >= 2.0 --- lib/active_model/serializer.rb | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index f0a23381..d89bfb2f 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -22,11 +22,25 @@ module ActiveModel SETTINGS[:include] = true if options[:include] end - def serializer_for(resource) - if resource.respond_to?(:to_ary) - ArraySerializer - else - "#{resource.class.name}Serializer".safe_constantize + if RUBY_VERSION >= '2.0' + def serializer_for(resource) + if resource.respond_to?(:to_ary) + ArraySerializer + else + begin + Object.const_get "#{resource.class.name}Serializer" + rescue NameError + nil + end + end + end + else + def serializer_for(resource) + if resource.respond_to?(:to_ary) + ArraySerializer + else + "#{resource.class.name}Serializer".safe_constantize + end end end From dbf512c14c021aeab234e36ef7d1b8137ca8ce89 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 16 Sep 2013 12:40:49 -0300 Subject: [PATCH 49/73] Make embed nil ids work --- lib/active_model/serializer.rb | 2 +- test/unit/active_model/serializer/has_one_test.rb | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index d89bfb2f..2acbda1c 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -161,7 +161,7 @@ module ActiveModel if associated_data.respond_to?(:to_ary) associated_data.map { |elem| elem.send(association.embed_key) } else - associated_data.send(association.embed_key) + associated_data.send(association.embed_key) if associated_data end end diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index 44b5ec2e..b9279aaf 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -54,6 +54,18 @@ module ActiveModel }, @user_serializer.as_json) end + def test_associations_embedding_nil_ids_serialization_using_as_json + @user.instance_eval do + def profile + nil + end + end + + assert_equal({ + 'user' => { name: 'Name 1', email: 'mail@server.com', 'profile_id' => nil } + }, @user_serializer.as_json) + end + def test_associations_embedding_nil_objects_serialization_using_as_json @association.embed = :objects @user.instance_eval do From 23e6ed32be751f95629203acf4a9283259411698 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Wed, 9 Oct 2013 18:20:17 -0200 Subject: [PATCH 50/73] Add default_serializer_options test --- MIT-LICENSE | 21 +++++++++++++ .../action_controller/serialization_test.rb | 30 +++++++++++++++++++ 2 files changed, 51 insertions(+) create mode 100644 MIT-LICENSE diff --git a/MIT-LICENSE b/MIT-LICENSE new file mode 100644 index 00000000..75e6db1a --- /dev/null +++ b/MIT-LICENSE @@ -0,0 +1,21 @@ +Copyright (c) 2011-2012 José Valim & Yehuda Katz + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + diff --git a/test/integration/action_controller/serialization_test.rb b/test/integration/action_controller/serialization_test.rb index d89ce9b1..024dc0ab 100644 --- a/test/integration/action_controller/serialization_test.rb +++ b/test/integration/action_controller/serialization_test.rb @@ -40,6 +40,36 @@ module ActionController end end + class DefaultOptionsForSerializerScopeTest < ActionController::TestCase + class MyController < ActionController::Base + def default_serializer_options + { scope: current_admin } + end + + def render_using_scope_set_in_default_serializer_options + render json: Profile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) + end + + private + + def current_user + 'current_user' + end + + def current_admin + 'current_admin' + end + end + + tests MyController + + def test_render_using_scope_set_in_default_serializer_options + get :render_using_scope_set_in_default_serializer_options + assert_equal 'application/json', @response.content_type + assert_equal '{"profile":{"name":"Name 1","description":"Description 1 - current_admin"}}', @response.body + end + end + class ExplicitSerializerScopeTest < ActionController::TestCase class MyController < ActionController::Base def render_using_implicit_serializer_and_explicit_scope From 4f70dc20916ff1fbf70572cc7824918d7d2197a4 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 10 Oct 2013 19:37:23 -0200 Subject: [PATCH 51/73] has_one serialized objects shouldn't be wrapped in an array and it's key is singular --- lib/active_model/serializer.rb | 3 ++- lib/active_model/serializer/associations.rb | 4 +--- test/unit/active_model/serializer/has_many_test.rb | 8 ++++---- test/unit/active_model/serializer/has_one_test.rb | 12 ++++++------ 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 2acbda1c..88f96d10 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -152,7 +152,8 @@ module ActiveModel if associated_data.respond_to?(:to_ary) associated_data.map { |elem| association.build_serializer(elem).serializable_hash } else - [association.build_serializer(associated_data).serializable_hash] + result = association.build_serializer(associated_data).serializable_hash + association.is_a?(Association::HasMany) ? [result] : result end end diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index 31cf2eda..6290eb9d 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -12,7 +12,7 @@ module ActiveModel @embed_in_root = @embed_ids && (options[:include] || SETTINGS[:include]) @embed_key = options[:embed_key] || :id @key = options[:key] - @embedded_key = options[:root] + @embedded_key = options[:root] || name self.serializer_class = @options[:serializer] end @@ -41,7 +41,6 @@ module ActiveModel def initialize(*args) super @key ||= "#{name}_id" - @embedded_key ||= name.pluralize end end @@ -49,7 +48,6 @@ module ActiveModel def initialize(*args) super @key ||= "#{name.singularize}_ids" - @embedded_key ||= name end end end diff --git a/test/unit/active_model/serializer/has_many_test.rb b/test/unit/active_model/serializer/has_many_test.rb index 56d5dff3..abdc8ed2 100644 --- a/test/unit/active_model/serializer/has_many_test.rb +++ b/test/unit/active_model/serializer/has_many_test.rb @@ -43,14 +43,14 @@ module ActiveModel def test_associations_embedding_objects_serialization_using_serializable_hash @association.embed = :objects assert_equal({ - title: 'Title 1', body: 'Body 1', 'comments' => [{ content: 'C1' }, { content: 'C2' }] + title: 'Title 1', body: 'Body 1', comments: [{ content: 'C1' }, { content: 'C2' }] }, @post_serializer.serializable_hash) end def test_associations_embedding_objects_serialization_using_as_json @association.embed = :objects assert_equal({ - 'post' => { title: 'Title 1', body: 'Body 1', 'comments' => [{ content: 'C1' }, { content: 'C2' }] } + 'post' => { title: 'Title 1', body: 'Body 1', comments: [{ content: 'C1' }, { content: 'C2' }] } }, @post_serializer.as_json) end @@ -63,7 +63,7 @@ module ActiveModel end assert_equal({ - 'post' => { title: 'Title 1', body: 'Body 1', 'comments' => [nil] } + 'post' => { title: 'Title 1', body: 'Body 1', comments: [nil] } }, @post_serializer.as_json) end @@ -109,7 +109,7 @@ module ActiveModel assert_equal({ 'post' => { title: 'Title 1', body: 'Body 1', 'comment_ids' => @post.comments.map { |c| c.object_id } }, - 'comments' => [{ content: 'fake' }, { content: 'fake' }] + comments: [{ content: 'fake' }, { content: 'fake' }] }, @post_serializer.as_json) end end diff --git a/test/unit/active_model/serializer/has_one_test.rb b/test/unit/active_model/serializer/has_one_test.rb index b9279aaf..3450cbf0 100644 --- a/test/unit/active_model/serializer/has_one_test.rb +++ b/test/unit/active_model/serializer/has_one_test.rb @@ -43,14 +43,14 @@ module ActiveModel def test_associations_embedding_objects_serialization_using_serializable_hash @association.embed = :objects assert_equal({ - name: 'Name 1', email: 'mail@server.com', 'profiles' => [{ name: 'N1', description: 'D1' }] + name: 'Name 1', email: 'mail@server.com', profile: { name: 'N1', description: 'D1' } }, @user_serializer.serializable_hash) end def test_associations_embedding_objects_serialization_using_as_json @association.embed = :objects assert_equal({ - 'user' => { name: 'Name 1', email: 'mail@server.com', 'profiles' => [{ name: 'N1', description: 'D1' }] } + 'user' => { name: 'Name 1', email: 'mail@server.com', profile: { name: 'N1', description: 'D1' } } }, @user_serializer.as_json) end @@ -75,7 +75,7 @@ module ActiveModel end assert_equal({ - 'user' => { name: 'Name 1', email: 'mail@server.com', 'profiles' => [nil] } + 'user' => { name: 'Name 1', email: 'mail@server.com', profile: nil } }, @user_serializer.as_json) end @@ -83,7 +83,7 @@ module ActiveModel @association.embed = :objects @association.embedded_key = 'root' assert_equal({ - name: 'Name 1', email: 'mail@server.com', 'root' => [{ name: 'N1', description: 'D1' }] + name: 'Name 1', email: 'mail@server.com', 'root' => { name: 'N1', description: 'D1' } }, @user_serializer.serializable_hash) end @@ -100,7 +100,7 @@ module ActiveModel @user_serializer.root = nil assert_equal({ 'user' => { name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id }, - 'profiles' => [{ name: 'N1', description: 'D1' }] + profile: { name: 'N1', description: 'D1' } }, @user_serializer.as_json) end @@ -117,7 +117,7 @@ module ActiveModel assert_equal({ 'user' => { name: 'Name 1', email: 'mail@server.com', 'profile_id' => @user.profile.object_id }, - 'profiles' => [{ name: 'fake' }] + profile: { name: 'fake' } }, @user_serializer.as_json) end end From 4b91d0e5ec400bd94a365ddf842b22a3a22f9dae Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 10 Oct 2013 19:44:14 -0200 Subject: [PATCH 52/73] embed :objects is the default A commit with an integration test that covers this functionality is coming after this one --- lib/active_model/serializer/associations.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_model/serializer/associations.rb b/lib/active_model/serializer/associations.rb index 6290eb9d..863edc54 100644 --- a/lib/active_model/serializer/associations.rb +++ b/lib/active_model/serializer/associations.rb @@ -8,7 +8,7 @@ module ActiveModel @name = name.to_s @options = options - self.embed = options[:embed] || SETTINGS[:embed] + self.embed = options[:embed] || SETTINGS[:embed] || :objects @embed_in_root = @embed_ids && (options[:include] || SETTINGS[:include]) @embed_key = options[:embed_key] || :id @key = options[:key] From 94a83c1cc096481d8051aec7245e40677667ab8d Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 10 Oct 2013 19:47:11 -0200 Subject: [PATCH 53/73] Make relationship graph of AR integration tests bigger --- test/fixtures/active_record.rb | 83 +++++++++++++++++-- .../active_record/active_record_test.rb | 71 +++++++++++++--- 2 files changed, 135 insertions(+), 19 deletions(-) diff --git a/test/fixtures/active_record.rb b/test/fixtures/active_record.rb index 9cdf055a..8df19059 100644 --- a/test/fixtures/active_record.rb +++ b/test/fixtures/active_record.rb @@ -6,16 +6,87 @@ ActiveRecord::Base.establish_connection( ) ActiveRecord::Schema.define do - create_table :ar_profiles, :force => true do |t| + create_table :ar_posts, force: true do |t| + t.string :title + t.text :body + t.belongs_to :ar_section, index: true + t.timestamps + end + + create_table :ar_comments, force: true do |t| + t.text :body + t.belongs_to :ar_post, index: true + t.timestamps + end + + create_table :ar_tags, force: true do |t| t.string :name - t.string :description - t.string :comments + end + + create_table :ar_sections, force: true do |t| + t.string :name + end + + create_table :ar_posts_tags, force: true do |t| + t.references :ar_post, :ar_tag, index: true + end + + create_table :ar_comments_tags, force: true do |t| + t.references :ar_comment, :ar_tag, index: true end end -class ARProfile < ActiveRecord::Base +class ARPost < ActiveRecord::Base + has_many :ar_comments, class_name: 'ARComment' + has_and_belongs_to_many :ar_tags, class_name: 'ARTag' + belongs_to :ar_section, class_name: 'ARSection' end -class ARProfileSerializer < ActiveModel::Serializer - attributes :name, :description +class ARComment < ActiveRecord::Base + belongs_to :ar_post, class_name: 'ARPost' + has_and_belongs_to_many :ar_tags, class_name: 'ARTag' +end + +class ARTag < ActiveRecord::Base +end + +class ARSection < ActiveRecord::Base +end + +class ARPostSerializer < ActiveModel::Serializer + attributes :title, :body + + has_many :ar_comments, :ar_tags + has_one :ar_section +end + +class ARCommentSerializer < ActiveModel::Serializer + attributes :body + + has_many :ar_tags +end + +class ARTagSerializer < ActiveModel::Serializer + attributes :name +end + +class ARSectionSerializer < ActiveModel::Serializer + attributes :name +end + +ARPost.create(title: 'New post', + body: 'A body!!!', + ar_section: ARSection.create(name: 'ruby')).tap do |post| + + short_tag = post.ar_tags.create(name: 'short') + whiny_tag = post.ar_tags.create(name: 'whiny') + happy_tag = post.ar_tags.create(name: 'happy') + + post.ar_comments.create(body: 'what a dumb post').tap do |comment| + comment.ar_tags.concat short_tag, whiny_tag + end + + post.ar_comments.create(body: 'i liked it').tap do |comment| + comment.ar_tags.concat short_tag, happy_tag + end end diff --git a/test/integration/active_record/active_record_test.rb b/test/integration/active_record/active_record_test.rb index 65524402..cf914b90 100644 --- a/test/integration/active_record/active_record_test.rb +++ b/test/integration/active_record/active_record_test.rb @@ -5,25 +5,70 @@ module ActiveModel class Serializer class ActiveRecordTest < ActiveModel::TestCase def setup - @profile = ARProfile.new({ name: 'Name 1', description: 'Description 1', comments: 'Comments 1' }) - @profile_serializer = ARProfileSerializer.new(@profile) + @post = ARPost.first end - def test_attributes_definition - assert_equal([:name, :description], - @profile_serializer.class._attributes) - end + def test_serialization_embedding_objects + post_serializer = ARPostSerializer.new(@post) - def test_attributes_serialization_using_serializable_hash assert_equal({ - name: 'Name 1', description: 'Description 1' - }, @profile_serializer.serializable_hash) + 'ar_post' => { + title: 'New post', body: 'A body!!!', + ar_comments: [{ body: 'what a dumb post', ar_tags: [{ name: 'short' }, { name: 'whiny' }] }, + { body: 'i liked it', ar_tags: [{:name=>"short"}, {:name=>"happy"}] }], + ar_tags: [{ name: 'short' }, { name: 'whiny' }, { name: 'happy' }], + ar_section: { name: 'ruby' } + } + }, post_serializer.as_json) end - def test_attributes_serialization_using_as_json - assert_equal({ - 'ar_profile' => { name: 'Name 1', description: 'Description 1' } - }, @profile_serializer.as_json) + def test_serialization_embedding_ids + post_serializer = ARPostSerializer.new(@post) + + embed(ARPostSerializer, embed: :ids) do + assert_equal({ + 'ar_post' => { + title: 'New post', body: 'A body!!!', + 'ar_comment_ids' => [1, 2], + 'ar_tag_ids' => [1, 2, 3], + 'ar_section_id' => 1 + } + }, post_serializer.as_json) + end + end + + def test_serialization_embedding_ids_including_in_root + post_serializer = ARPostSerializer.new(@post) + + embed(ARPostSerializer, embed: :ids, include: true) do + assert_equal({ + 'ar_post' => { + title: 'New post', body: 'A body!!!', + 'ar_comment_ids' => [1, 2], + 'ar_tag_ids' => [1, 2, 3], + 'ar_section_id' => 1 + }, + ar_comments: [{ body: 'what a dumb post', ar_tags: [{ name: 'short' }, { name: 'whiny' }] }, + { body: 'i liked it', ar_tags: [{:name=>"short"}, {:name=>"happy"}] }], + ar_tags: [{ name: 'short' }, { name: 'whiny' }, { name: 'happy' }], + ar_section: { name: 'ruby' } + }, post_serializer.as_json) + end + end + + private + + def embed(klass, options = {}) + old_assocs = Hash[ARPostSerializer._associations.to_a.map { |(name, association)| [name, association.dup] }] + + ARPostSerializer._associations.each_value do |association| + association.embed = options[:embed] + association.embed_in_root = options[:include] + end + + yield + ensure + ARPostSerializer._associations = old_assocs end end end From b9a6dfac2f3a8bfb4ac239675e54730f77113e5e Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 11 Oct 2013 13:54:38 -0200 Subject: [PATCH 54/73] Do not call send on associated objects, do it through elem.read_attribute_for_serialization --- lib/active_model/serializer.rb | 4 ++-- test/fixtures/poro.rb | 10 +++++----- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index 88f96d10..beff728a 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -160,9 +160,9 @@ module ActiveModel def serialize_ids(association) associated_data = send(association.name) if associated_data.respond_to?(:to_ary) - associated_data.map { |elem| elem.send(association.embed_key) } + associated_data.map { |elem| elem.read_attribute_for_serialization(association.embed_key) } else - associated_data.send(association.embed_key) if associated_data + associated_data.read_attribute_for_serialization(association.embed_key) if associated_data end end diff --git a/test/fixtures/poro.rb b/test/fixtures/poro.rb index e9d41da3..a43954b5 100644 --- a/test/fixtures/poro.rb +++ b/test/fixtures/poro.rb @@ -4,11 +4,11 @@ class Model end def read_attribute_for_serialization(name) - @attributes[name] - end - - def id - object_id + if name == :id || name == 'id' + object_id + else + @attributes[name] + end end end From 91f93177132962abd8da727e67154e849f36ebbc Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 11 Oct 2013 13:54:59 -0200 Subject: [PATCH 55/73] There's no need to define read_attribute_for_serialization in the Serializer class --- lib/active_model/serializer.rb | 2 -- 1 file changed, 2 deletions(-) diff --git a/lib/active_model/serializer.rb b/lib/active_model/serializer.rb index beff728a..e7f74ced 100644 --- a/lib/active_model/serializer.rb +++ b/lib/active_model/serializer.rb @@ -99,8 +99,6 @@ module ActiveModel attr_accessor :object, :scope, :meta_key, :meta attr_reader :root - alias read_attribute_for_serialization send - def root=(root) @root = root @root = self.class._root if @root.nil? From 73774649e34c5cd4f440df8263f4d8e19895d6f3 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 11 Oct 2013 14:30:16 -0200 Subject: [PATCH 56/73] Implement Generators --- .../generators/resource_override.rb | 13 ++++++ .../serializer/generators/serializer/USAGE | 9 ++++ .../serializer/serializer_generator.rb | 37 +++++++++++++++++ .../serializer/templates/serializer.rb | 8 ++++ lib/active_model/serializer/railtie.rb | 10 +++++ lib/active_model_serializers.rb | 1 + .../generators/resource_generator_test.rb | 27 ++++++++++++ .../generators/serializer_generator_test.rb | 41 +++++++++++++++++++ test/test_app.rb | 8 ++++ 9 files changed, 154 insertions(+) create mode 100644 lib/active_model/serializer/generators/resource_override.rb create mode 100644 lib/active_model/serializer/generators/serializer/USAGE create mode 100644 lib/active_model/serializer/generators/serializer/serializer_generator.rb create mode 100644 lib/active_model/serializer/generators/serializer/templates/serializer.rb create mode 100644 lib/active_model/serializer/railtie.rb create mode 100644 test/integration/generators/resource_generator_test.rb create mode 100644 test/integration/generators/serializer_generator_test.rb create mode 100644 test/test_app.rb diff --git a/lib/active_model/serializer/generators/resource_override.rb b/lib/active_model/serializer/generators/resource_override.rb new file mode 100644 index 00000000..9fad8dc1 --- /dev/null +++ b/lib/active_model/serializer/generators/resource_override.rb @@ -0,0 +1,13 @@ +require 'rails/generators' +require 'rails/generators/rails/resource/resource_generator' + +module Rails + module Generators + class ResourceGenerator + def add_serializer + invoke 'serializer' + end + end + end +end + diff --git a/lib/active_model/serializer/generators/serializer/USAGE b/lib/active_model/serializer/generators/serializer/USAGE new file mode 100644 index 00000000..a49f7ea1 --- /dev/null +++ b/lib/active_model/serializer/generators/serializer/USAGE @@ -0,0 +1,9 @@ +Description: + Generates a serializer for the given resource with tests. + +Example: + `rails generate serializer Account name created_at` + + For TestUnit it creates: + Serializer: app/serializers/account_serializer.rb + TestUnit: test/unit/account_serializer_test.rb diff --git a/lib/active_model/serializer/generators/serializer/serializer_generator.rb b/lib/active_model/serializer/generators/serializer/serializer_generator.rb new file mode 100644 index 00000000..7c4c036d --- /dev/null +++ b/lib/active_model/serializer/generators/serializer/serializer_generator.rb @@ -0,0 +1,37 @@ +module Rails + module Generators + class SerializerGenerator < NamedBase + source_root File.expand_path('../templates', __FILE__) + check_class_collision suffix: 'Serializer' + + argument :attributes, type: :array, default: [], banner: 'field:type field:type' + + class_option :parent, type: :string, desc: 'The parent class for the generated serializer' + + def create_serializer_file + template 'serializer.rb', File.join('app/serializers', class_path, "#{file_name}_serializer.rb") + end + + private + + def attributes_names + [:id] + attributes.select { |attr| !attr.reference? }.map { |a| a.name.to_sym } + end + + def association_names + attributes.select { |attr| attr.reference? }.map { |a| a.name.to_sym } + end + + def parent_class_name + if options[:parent] + options[:parent] + elsif (ns = Rails::Generators.namespace) && ns.const_defined?(:ApplicationSerializer) || + defined?(::ApplicationSerializer) + 'ApplicationSerializer' + else + 'ActiveModel::Serializer' + end + end + end + end +end diff --git a/lib/active_model/serializer/generators/serializer/templates/serializer.rb b/lib/active_model/serializer/generators/serializer/templates/serializer.rb new file mode 100644 index 00000000..4ebb004e --- /dev/null +++ b/lib/active_model/serializer/generators/serializer/templates/serializer.rb @@ -0,0 +1,8 @@ +<% module_namespacing do -%> +class <%= class_name %>Serializer < <%= parent_class_name %> + attributes <%= attributes_names.map(&:inspect).join(", ") %> +<% association_names.each do |attribute| -%> + has_one :<%= attribute %> +<% end -%> +end +<% end -%> diff --git a/lib/active_model/serializer/railtie.rb b/lib/active_model/serializer/railtie.rb new file mode 100644 index 00000000..080fc53a --- /dev/null +++ b/lib/active_model/serializer/railtie.rb @@ -0,0 +1,10 @@ +module ActiveModel + class Railtie < Rails::Railtie + initializer 'generators' do |app| + require 'rails/generators' + require 'active_model/serializer/generators/serializer/serializer_generator' + Rails::Generators.configure!(app.config.generators) + require 'active_model/serializer/generators/resource_override' + end + end +end diff --git a/lib/active_model_serializers.rb b/lib/active_model_serializers.rb index c3e40c4b..102ea3c0 100644 --- a/lib/active_model_serializers.rb +++ b/lib/active_model_serializers.rb @@ -1,6 +1,7 @@ require 'active_model' require 'active_model/serializer' require 'active_model/serializer/version' +require 'active_model/serializer/railtie' if defined?(Rails) begin require 'action_controller' diff --git a/test/integration/generators/resource_generator_test.rb b/test/integration/generators/resource_generator_test.rb new file mode 100644 index 00000000..aa9eed13 --- /dev/null +++ b/test/integration/generators/resource_generator_test.rb @@ -0,0 +1,27 @@ +require 'test_helper' +require 'rails' +require 'test_app' +require 'rails/generators/rails/resource/resource_generator' +require 'active_model/serializer/generators/resource_override' + +class ResourceGeneratorTest < Rails::Generators::TestCase + destination File.expand_path('../../../tmp', __FILE__) + setup :prepare_destination, :copy_routes + + tests Rails::Generators::ResourceGenerator + arguments %w(account) + + def test_serializer_file_is_generated + run_generator + + assert_file 'app/serializers/account_serializer.rb', /class AccountSerializer < ActiveModel::Serializer/ + end + + private + + def copy_routes + config_dir = File.join(destination_root, 'config') + FileUtils.mkdir_p(config_dir) + File.write(File.join(config_dir, 'routes.rb'), 'Rails.application.routes.draw { }') + end +end diff --git a/test/integration/generators/serializer_generator_test.rb b/test/integration/generators/serializer_generator_test.rb new file mode 100644 index 00000000..ff75e772 --- /dev/null +++ b/test/integration/generators/serializer_generator_test.rb @@ -0,0 +1,41 @@ +require 'test_helper' +require 'rails' +require 'test_app' +require 'active_model/serializer/generators/serializer/serializer_generator' + +class SerializerGeneratorTest < Rails::Generators::TestCase + destination File.expand_path('../../../tmp', __FILE__) + setup :prepare_destination + + tests Rails::Generators::SerializerGenerator + arguments %w(account name:string description:text business:references) + + def test_generates_a_serializer_with_attributes_and_associations + run_generator + assert_file 'app/serializers/account_serializer.rb', /class AccountSerializer < ActiveModel::Serializer/ do |serializer| + assert_match(/attributes :id, :name, :description/, serializer) + assert_match(/has_one :business/, serializer) + end + end + + def test_generates_a_namespaced_serializer + run_generator ['admin/account'] + assert_file 'app/serializers/admin/account_serializer.rb', /class Admin::AccountSerializer < ActiveModel::Serializer/ + end + + def test_uses_application_serializer_if_one_exists + Object.const_set(:ApplicationSerializer, Class.new) + run_generator + assert_file 'app/serializers/account_serializer.rb', /class AccountSerializer < ApplicationSerializer/ + ensure + Object.send :remove_const, :ApplicationSerializer + end + + def test_uses_given_parent + Object.const_set(:ApplicationSerializer, Class.new) + run_generator ['Account', '--parent=MySerializer'] + assert_file 'app/serializers/account_serializer.rb', /class AccountSerializer < MySerializer/ + ensure + Object.send :remove_const, :ApplicationSerializer + end +end diff --git a/test/test_app.rb b/test/test_app.rb new file mode 100644 index 00000000..8b702e51 --- /dev/null +++ b/test/test_app.rb @@ -0,0 +1,8 @@ +class TestApp < Rails::Application + if Rails.version.to_s.first >= '4' + config.eager_load = false + config.secret_key_base = 'abc123' + end +end + +TestApp.load_generators From 82a4f25002d4881f20e9c0a8b579eece971b30b6 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 14 Oct 2013 17:13:42 -0200 Subject: [PATCH 57/73] Add CHANGELOG entries --- CHANGELOG.md | 80 +++++++++++----------------------------------------- 1 file changed, 16 insertions(+), 64 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87cabff9..b7513569 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,68 +1,20 @@ -# UNRELEASED +# VERSION 0.9.0.pre -* 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 - of the serialization and a serialize method that returns an object to - be converted to json by the module. This also removes duplicate code. - https://github.com/rails-api/active_model_serializers/commit/6c6bc8872d3b0f040a200854fa5530a775824dbf +* The following methods were removed + - Model#active\_model\_serializer + - Serializer#include! + - Serializer#include? + - Serializer#attr\_disabled= + - Serializer#cache + - Serializer#perform\_caching + - Serializer#schema (needs more discussion) + - Serializer#attribute + - Serializer#include\_#{name}? (filter method added) + - Serializer#attributes (took a hash) -* ActiveModel::Serializer::Caching module was created it enables - Serializers to be able to cache to\_json and serialize calls. This - also helps removing duplicate code. - https://github.com/rails-api/active_model_serializers/commit/3e27110df78696ac48cafd1568f72216f348a188 - -* We got rid of the Association.refine method which generated - subclasses. - https://github.com/rails-api/active_model_serializers/commit/24923722d4f215c7cfcdf553fd16582e28e3801b - -* Associations doesn't know anymore about the source serializer. - That didn't make any sense. - https://github.com/rails-api/active_model_serializers/commit/2252e8fe6dbf45660c6a35f35e2423792f2c3abf - https://github.com/rails-api/active_model_serializers/commit/87eadd09b9a988bc1d9b30d9a501ef7e3fc6bb87 - https://github.com/rails-api/active_model_serializers/commit/79a6e13e8f7fae2eb4f48e83a9633e74beb6739e - -* Passing options[:hash] is not public API of include!. That was - removed. - https://github.com/rails-api/active_model_serializers/commit/5cbf9317051002a32c90c3f995b8b2f126f70d0c - -* ActiveModel::Serializer::Associations::Config is now - ActiveModel::Serializer::Association but it's an internal - thing so shouldn't bother. - ActiveModel::Serializer::Associations::Has\* are now - ActiveModel::Serializer::Association::Has\* and inherit from - ActiveModel::Serializer::Association - https://github.com/rails-api/active_model_serializers/commit/f5de334ddf1f3b9764d914a717311532021785d2 - https://github.com/rails-api/active_model_serializers/commit/3dd422d99e8c57f113880da34f6abe583c4dadf9 - -* serialize\_ids call methods on the corresponding serializer if they - are defined, instead of talking directly with the serialized object. - Serializers are decorators so we shouldn't talk directly with - serialized objects. - -* Array items are not wrapped anymore in root element. - -* embed and include were removed from AM::Serializer and there's a global config for them in AM::Serializers - - So code like ... - - class PostSerializer < ActiveModel::Serializer - embed :ids, :include => true - has_many :comments - end - - should be changed to ... - - class PostSerializer < ActiveModel::Serializer - has_many :comments, :embed => :ids, :include => true - end - - or you could change the global defaults by adding ... - - config.active\_model\_serializers.embed = :ids - config.active\_model\_serializers.include = true - - to the config/application.rb file +* The following things were added + - Serializer#filter method + - SETTINGS object # VERSION 0.8.1 @@ -125,7 +77,7 @@ * Allow serialization_scope to be disabled with serialization_scope nil * Array serializer should support pure ruby objects besides serializers -# VERSION 0.5.0 (May 16, 2012) +# VERSION 0.5.0 * First tagged version * Changes generators to always generate an ApplicationSerializer From bd9d322d171e05d72ccaeec5391f2a394d2415e5 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 14 Oct 2013 17:15:08 -0200 Subject: [PATCH 58/73] Leave only MIT-LICENSE file --- MIT-LICENSE.txt | 21 --------------------- 1 file changed, 21 deletions(-) delete mode 100644 MIT-LICENSE.txt diff --git a/MIT-LICENSE.txt b/MIT-LICENSE.txt deleted file mode 100644 index 75e6db1a..00000000 --- a/MIT-LICENSE.txt +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (c) 2011-2012 José Valim & Yehuda Katz - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - From c941952eb2a411886ce7086d130561aade96a944 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 14 Oct 2013 17:16:49 -0200 Subject: [PATCH 59/73] Add back CONTRIBUTING.md file --- CONTRIBUTING.md | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 CONTRIBUTING.md diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..9811ef28 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,20 @@ +Contributing to AMS +=================== + +First of all, **thank you**! + +Now, for the details: + +Please file issues on the [GitHub Issues +list](https://github.com/rails-api/active_model_serializers/issues). + +Please discuss new features or ask for feedback about a new feature [on +rails-api-core](https://groups.google.com/forum/#!forum/rails-api-core). + +If you want a feature implemented, the best way to get it done is to submit a +pull request that implements it. Tests and docs would be nice. + +Please include a CHANGELOG with all entries that change behavior. + +:heart: :sparkling_heart: :heart: + From 1817398b579d9ec608b45cd7dd24c7a6271a4806 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 14 Oct 2013 17:17:01 -0200 Subject: [PATCH 60/73] Add back DESIGN.textile file --- DESIGN.textile | 586 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 586 insertions(+) create mode 100644 DESIGN.textile diff --git a/DESIGN.textile b/DESIGN.textile new file mode 100644 index 00000000..559982e4 --- /dev/null +++ b/DESIGN.textile @@ -0,0 +1,586 @@ +This was the original design document for serializers. It is useful mostly for historical purposes as the public API has changed. + +h2. Rails Serializers + +This guide describes how to use Active Model serializers to build non-trivial JSON services in Rails. By reading this guide, you will learn: + +* When to use the built-in Active Model serialization +* When to use a custom serializer for your models +* How to use serializers to encapsulate authorization concerns +* How to create serializer templates to describe the application-wide structure of your serialized JSON +* How to build resources not backed by a single database table for use with JSON services + +This guide covers an intermediate topic and assumes familiarity with Rails conventions. It is suitable for applications that expose a +JSON API that may return different results based on the authorization status of the user. + +h3. Serialization + +By default, Active Record objects can serialize themselves into JSON by using the `to_json` method. This method takes a series of additional +parameter to control which properties and associations Rails should include in the serialized output. + +When building a web application that uses JavaScript to retrieve JSON data from the server, this mechanism has historically been the primary +way that Rails developers prepared their responses. This works great for simple cases, as the logic for serializing an Active Record object +is neatly encapsulated in Active Record itself. + +However, this solution quickly falls apart in the face of serialization requirements based on authorization. For instance, a web service +may choose to expose additional information about a resource only if the user is entitled to access it. In addition, a JavaScript front-end +may want information that is not neatly described in terms of serializing a single Active Record object, or in a different format than. + +In addition, neither the controller nor the model seems like the correct place for logic that describes how to serialize an model object +*for the current user*. + +Serializers solve these problems by encapsulating serialization in an object designed for this purpose. If the default +to_json+ semantics, +with at most a few configuration options serve your needs, by all means continue to use the built-in +to_json+. If you find yourself doing +hash-driven-development in your controllers, juggling authorization logic and other concerns, serializers are for you! + +h3. The Most Basic Serializer + +A basic serializer is a simple Ruby object named after the model class it is serializing. + +
+class PostSerializer
+  def initialize(post, scope)
+    @post, @scope = post, scope
+  end
+
+  def as_json
+    { post: { title: @post.name, body: @post.body } }
+  end
+end
+
+ +A serializer is initialized with two parameters: the model object it should serialize and an authorization scope. By default, the +authorization scope is the current user (+current_user+) but you can use a different object if you want. The serializer also +implements an +as_json+ method, which returns a Hash that will be sent to the JSON encoder. + +Rails will transparently use your serializer when you use +render :json+ in your controller. + +
+class PostsController < ApplicationController
+  def show
+    @post = Post.find(params[:id])
+    render json: @post
+  end
+end
+
+ +Because +respond_with+ uses +render :json+ under the hood for JSON requests, Rails will automatically use your serializer when +you use +respond_with+ as well. + +h4. +serializable_hash+ + +In general, you will want to implement +serializable_hash+ and +as_json+ to allow serializers to embed associated content +directly. The easiest way to implement these two methods is to have +as_json+ call +serializable_hash+ and insert the root. + +
+class PostSerializer
+  def initialize(post, scope)
+    @post, @scope = post, scope
+  end
+
+  def serializable_hash
+    { title: @post.name, body: @post.body }
+  end
+
+  def as_json
+    { post: serializable_hash }
+  end
+end
+
+ +h4. Authorization + +Let's update our serializer to include the email address of the author of the post, but only if the current user has superuser +access. + +
+class PostSerializer
+  def initialize(post, scope)
+    @post, @scope = post, scope
+  end
+
+  def as_json
+    { post: serializable_hash }
+  end
+
+  def serializable_hash
+    hash = post
+    hash.merge!(super_data) if super?
+    hash
+  end
+
+private
+  def post
+    { title: @post.name, body: @post.body }
+  end
+
+  def super_data
+    { email: @post.email }
+  end
+
+  def super?
+    @scope.superuser?
+  end
+end
+
+ +h4. Testing + +One benefit of encapsulating our objects this way is that it becomes extremely straight-forward to test the serialization +logic in isolation. + +
+require "ostruct"
+
+class PostSerializerTest < ActiveSupport::TestCase
+  # For now, we use a very simple authorization structure. These tests will need
+  # refactoring if we change that.
+  plebe = OpenStruct.new(super?: false)
+  god   = OpenStruct.new(super?: true)
+
+  post  = OpenStruct.new(title: "Welcome to my blog!", body: "Blah blah blah", email: "tenderlove@gmail.com")
+
+  test "a regular user sees just the title and body" do
+    json = PostSerializer.new(post, plebe).to_json
+    hash = JSON.parse(json)
+
+    assert_equal post.title, hash.delete("title")
+    assert_equal post.body, hash.delete("body")
+    assert_empty hash
+  end
+
+  test "a superuser sees the title, body and email" do
+    json = PostSerializer.new(post, god).to_json
+    hash = JSON.parse(json)
+
+    assert_equal post.title, hash.delete("title")
+    assert_equal post.body, hash.delete("body")
+    assert_equal post.email, hash.delete("email")
+    assert_empty hash
+  end
+end
+
+ +It's important to note that serializer objects define a clear interface specifically for serializing an existing object. +In this case, the serializer expects to receive a post object with +name+, +body+ and +email+ attributes and an authorization +scope with a +super?+ method. + +By defining a clear interface, it's must easier to ensure that your authorization logic is behaving correctly. In this case, +the serializer doesn't need to concern itself with how the authorization scope decides whether to set the +super?+ flag, just +whether it is set. In general, you should document these requirements in your serializer files and programatically via tests. +The documentation library +YARD+ provides excellent tools for describing this kind of requirement: + +
+class PostSerializer
+  # @param [~body, ~title, ~email] post the post to serialize
+  # @param [~super] scope the authorization scope for this serializer
+  def initialize(post, scope)
+    @post, @scope = post, scope
+  end
+
+  # ...
+end
+
+ +h3. Attribute Sugar + +To simplify this process for a number of common cases, Rails provides a default superclass named +ActiveModel::Serializer+ +that you can use to implement your serializers. + +For example, you will sometimes want to simply include a number of existing attributes from the source model into the outputted +JSON. In the above example, the +title+ and +body+ attributes were always included in the JSON. Let's see how to use ++ActiveModel::Serializer+ to simplify our post serializer. + +
+class PostSerializer < ActiveModel::Serializer
+  attributes :title, :body
+
+  def serializable_hash
+    hash = attributes
+    hash.merge!(super_data) if super?
+    hash
+  end
+
+private
+  def super_data
+    { email: @post.email }
+  end
+
+  def super?
+    @scope.superuser?
+  end
+end
+
+ +First, we specified the list of included attributes at the top of the class. This will create an instance method called ++attributes+ that extracts those attributes from the post model. + +NOTE: Internally, +ActiveModel::Serializer+ uses +read_attribute_for_serialization+, which defaults to +read_attribute+, which defaults to +send+. So if you're rolling your own models for use with the serializer, you can use simple Ruby accessors for your attributes if you like. + +Next, we use the attributes method in our +serializable_hash+ method, which allowed us to eliminate the +post+ method we hand-rolled +earlier. We could also eliminate the +as_json+ method, as +ActiveModel::Serializer+ provides a default +as_json+ method for +us that calls our +serializable_hash+ method and inserts a root. But we can go a step further! + +
+class PostSerializer < ActiveModel::Serializer
+  attributes :title, :body
+
+private
+  def attributes
+    hash = super
+    hash.merge!(email: post.email) if super?
+    hash
+  end
+
+  def super?
+    @scope.superuser?
+  end
+end
+
+ +The superclass provides a default +initialize+ method as well as a default +serializable_hash+ method, which uses ++attributes+. We can call +super+ to get the hash based on the attributes we declared, and then add in any additional +attributes we want to use. + +NOTE: +ActiveModel::Serializer+ will create an accessor matching the name of the current class for the resource you pass in. In this case, because we have defined a PostSerializer, we can access the resource with the +post+ accessor. + +h3. Associations + +In most JSON APIs, you will want to include associated objects with your serialized object. In this case, let's include +the comments with the current post. + +
+class PostSerializer < ActiveModel::Serializer
+  attributes :title, :body
+  has_many :comments
+
+private
+  def attributes
+    hash = super
+    hash.merge!(email: post.email) if super?
+    hash
+  end
+
+  def super?
+    @scope.superuser?
+  end
+end
+
+ +The default +serializable_hash+ method will include the comments as embedded objects inside the post. + +
+{
+  post: {
+    title: "Hello Blog!",
+    body: "This is my first post. Isn't it fabulous!",
+    comments: [
+      {
+        title: "Awesome",
+        body: "Your first post is great"
+      }
+    ]
+  }
+}
+
+ +Rails uses the same logic to generate embedded serializations as it does when you use +render :json+. In this case, +because you didn't define a +CommentSerializer+, Rails used the default +as_json+ on your comment object. + +If you define a serializer, Rails will automatically instantiate it with the existing authorization scope. + +
+class CommentSerializer
+  def initialize(comment, scope)
+    @comment, @scope = comment, scope
+  end
+
+  def serializable_hash
+    { title: @comment.title }
+  end
+
+  def as_json
+    { comment: serializable_hash }
+  end
+end
+
+ +If we define the above comment serializer, the outputted JSON will change to: + +
+{
+  post: {
+    title: "Hello Blog!",
+    body: "This is my first post. Isn't it fabulous!",
+    comments: [{ title: "Awesome" }]
+  }
+}
+
+ +Let's imagine that our comment system allows an administrator to kill a comment, and we only want to allow +users to see the comments they're entitled to see. By default, +has_many :comments+ will simply use the ++comments+ accessor on the post object. We can override the +comments+ accessor to limit the comments used +to just the comments we want to allow for the current user. + +
+class PostSerializer < ActiveModel::Serializer
+  attributes :title. :body
+  has_many :comments
+
+private
+  def attributes
+    hash = super
+    hash.merge!(email: post.email) if super?
+    hash
+  end
+
+  def comments
+    post.comments_for(scope)
+  end
+
+  def super?
+    @scope.superuser?
+  end
+end
+
+ ++ActiveModel::Serializer+ will still embed the comments, but this time it will use just the comments +for the current user. + +NOTE: The logic for deciding which comments a user should see still belongs in the model layer. In general, you should encapsulate concerns that require making direct Active Record queries in scopes or public methods on your models. + +h4. Modifying Associations + +You can also rename associations if required. Say for example you have an association that +makes sense to be named one thing in your code, but another when data is serialized. +You can use the option to specify a different name for an association. +Here is an example: + +
+class UserSerializer < ActiveModel::Serializer
+  has_many :followed_posts, key: :posts
+  has_one :owned_account, key: :account
+end
+
+ +Using the :key without a :serializer option will use implicit detection +to determine a serializer. In this example, you'd have to define two classes: PostSerializer +and AccountSerializer. You can also add the :serializer option +to set it explicitly: + +
+class UserSerializer < ActiveModel::Serializer
+  has_many :followed_posts, key: :posts, serializer: CustomPostSerializer
+  has_one :owne_account, key: :account, serializer: PrivateAccountSerializer
+end
+
+ +h3. Customizing Associations + +Not all front-ends expect embedded documents in the same form. In these cases, you can override the +default +serializable_hash+, and use conveniences provided by +ActiveModel::Serializer+ to avoid having to +build up the hash manually. + +For example, let's say our front-end expects the posts and comments in the following format: + +
+{
+  post: {
+    id: 1
+    title: "Hello Blog!",
+    body: "This is my first post. Isn't it fabulous!",
+    comments: [1,2]
+  },
+  comments: [
+    {
+      id: 1
+      title: "Awesome",
+      body: "Your first post is great"
+    },
+    {
+      id: 2
+      title: "Not so awesome",
+      body: "Why is it so short!"
+    }
+  ]
+}
+
+ +We could achieve this with a custom +as_json+ method. We will also need to define a serializer for comments. + +
+class CommentSerializer < ActiveModel::Serializer
+  attributes :id, :title, :body
+
+  # define any logic for dealing with authorization-based attributes here
+end
+
+class PostSerializer < ActiveModel::Serializer
+  attributes :title, :body
+  has_many :comments
+
+  def as_json
+    { post: serializable_hash }.merge!(associations)
+  end
+
+  def serializable_hash
+    post_hash = attributes
+    post_hash.merge!(association_ids)
+    post_hash
+  end
+
+private
+  def attributes
+    hash = super
+    hash.merge!(email: post.email) if super?
+    hash
+  end
+
+  def comments
+    post.comments_for(scope)
+  end
+
+  def super?
+    @scope.superuser?
+  end
+end
+
+ +Here, we used two convenience methods: +associations+ and +association_ids+. The first, ++associations+, creates a hash of all of the define associations, using their defined +serializers. The second, +association_ids+, generates a hash whose key is the association +name and whose value is an Array of the association's keys. + +The +association_ids+ helper will use the overridden version of the association, so in +this case, +association_ids+ will only include the ids of the comments provided by the ++comments+ method. + + +h3. Special Association Serializers + +So far, associations defined in serializers use either the +as_json+ method on the model +or the defined serializer for the association type. Sometimes, you may want to serialize +associated models differently when they are requested as part of another resource than +when they are requested on their own. + +For instance, we might want to provide the full comment when it is requested directly, +but only its title when requested as part of the post. To achieve this, you can define +a serializer for associated objects nested inside the main serializer. + +
+class PostSerializer < ActiveModel::Serializer
+  class CommentSerializer < ActiveModel::Serializer
+    attributes :id, :title
+  end
+
+  # same as before
+  # ...
+end
+
+ +In other words, if a +PostSerializer+ is trying to serialize comments, it will first +look for +PostSerializer::CommentSerializer+ before falling back to +CommentSerializer+ +and finally +comment.as_json+. + +h3. Overriding the Defaults + +h4. Authorization Scope + +By default, the authorization scope for serializers is +:current_user+. This means +that when you call +render json: @post+, the controller will automatically call +its +current_user+ method and pass that along to the serializer's initializer. + +If you want to change that behavior, simply use the +serialization_scope+ class +method. + +
+class PostsController < ApplicationController
+  serialization_scope :current_app
+end
+
+ +You can also implement an instance method called (no surprise) +serialization_scope+, +which allows you to define a dynamic authorization scope based on the current request. + +WARNING: If you use different objects as authorization scopes, make sure that they all implement whatever interface you use in your serializers to control what the outputted JSON looks like. + +h3. Using Serializers Outside of a Request + +The serialization API encapsulates the concern of generating a JSON representation of +a particular model for a particular user. As a result, you should be able to easily use +serializers, whether you define them yourself or whether you use +ActiveModel::Serializer+ +outside a request. + +For instance, if you want to generate the JSON representation of a post for a user outside +of a request: + +
+user = get_user # some logic to get the user in question
+PostSerializer.new(post, user).to_json # reliably generate JSON output
+
+ +If you want to generate JSON for an anonymous user, you should be able to use whatever +technique you use in your application to generate anonymous users outside of a request. +Typically, that means creating a new user and not saving it to the database: + +
+user = User.new # create a new anonymous user
+PostSerializer.new(post, user).to_json
+
+ +In general, the better you encapsulate your authorization logic, the more easily you +will be able to use the serializer outside of the context of a request. For instance, +if you use an authorization library like Cancan, which uses a uniform +user.can?(action, model)+, +the authorization interface can very easily be replaced by a plain Ruby object for +testing or usage outside the context of a request. + +h3. Collections + +So far, we've talked about serializing individual model objects. By default, Rails +will serialize collections, including when using the +associations+ helper, by +looping over each element of the collection, calling +serializable_hash+ on the element, +and then grouping them by their type (using the plural version of their class name +as the root). + +For example, an Array of post objects would serialize as: + +
+{
+  posts: [
+    {
+      title: "FIRST POST!",
+      body: "It's my first pooooost"
+    },
+    { title: "Second post!",
+      body: "Zomg I made it to my second post"
+    }
+  ]
+}
+
+ +If you want to change the behavior of serialized Arrays, you need to create +a custom Array serializer. + +
+class ArraySerializer < ActiveModel::ArraySerializer
+  def serializable_array
+    serializers.map do |serializer|
+      serializer.serializable_hash
+    end
+  end
+
+  def as_json
+    hash = { root => serializable_array }
+    hash.merge!(associations)
+    hash
+  end
+end
+
+ +When generating embedded associations using the +associations+ helper inside a +regular serializer, it will create a new ArraySerializer with the +associated content and call its +serializable_array+ method. In this case, those +embedded associations will not recursively include associations. + +When generating an Array using +render json: posts+, the controller will invoke +the +as_json+ method, which will include its associations and its root. From b81deac3203991b6f3be998673dfc2df2e3e1be8 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 14 Oct 2013 17:17:55 -0200 Subject: [PATCH 61/73] Add coverage tools --- Gemfile | 4 +++- test/coverage_setup.rb | 12 ++++++++++++ test/test_helper.rb | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) create mode 100644 test/coverage_setup.rb diff --git a/Gemfile b/Gemfile index 9e639683..6e810339 100644 --- a/Gemfile +++ b/Gemfile @@ -3,4 +3,6 @@ source 'https://rubygems.org' # Specify gem dependencies in active_model_serializers.gemspec gemspec -gem "sqlite3" +gem 'sqlite3' +gem 'coveralls', :require => false +gem 'simplecov', :require => false diff --git a/test/coverage_setup.rb b/test/coverage_setup.rb new file mode 100644 index 00000000..7fe12c67 --- /dev/null +++ b/test/coverage_setup.rb @@ -0,0 +1,12 @@ +require 'simplecov' +require 'coveralls' + +SimpleCov.formatter = SimpleCov::Formatter::MultiFormatter[ + SimpleCov::Formatter::HTMLFormatter, + Coveralls::SimpleCov::Formatter +] + +SimpleCov.start do + add_group "lib", "lib" + add_group "test", "test" +end diff --git a/test/test_helper.rb b/test/test_helper.rb index f5c6d8f9..378a96db 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,4 +1,5 @@ require 'bundler/setup' +require 'coverage_setup' require 'test/unit' require 'active_model_serializers' require 'fixtures/poro' From ae879d21ac6f012fa7ec514e91632cdba480ef64 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 14 Oct 2013 17:18:56 -0200 Subject: [PATCH 62/73] Add Gemfile.edge --- Gemfile.edge | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 Gemfile.edge diff --git a/Gemfile.edge b/Gemfile.edge new file mode 100644 index 00000000..281edceb --- /dev/null +++ b/Gemfile.edge @@ -0,0 +1,13 @@ +source 'https://rubygems.org' + +gemspec + +gem 'sqlite3' +gem 'coveralls', :require => false +gem 'simplecov', :require => false + +# Use Rails master +gem 'rails', github: 'rails/rails' + +# Current dependencies of edge rails +gem 'arel', github: 'rails/arel' From 7d7bc75290567419febca4220f50e12f6effe6b9 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 14 Oct 2013 17:19:10 -0200 Subject: [PATCH 63/73] Add .travis.yml file --- .travis.yml | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 00000000..7911e745 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,15 @@ +language: ruby +rvm: + - 1.9.3 + - 2.0.0 + - jruby-19mode + - rbx-19mode +gemfile: + - Gemfile + - Gemfile.edge +notifications: + email: false + campfire: + on_success: change + rooms: + - secure: "TP0fJ4aqXCRD7CaAgaYW7Pa22j4/uLChdBb59ob/sJvHtfi4Zx3I54xWApmp\nZl1KItFGCV8oQZhQl5hAmHJfJ+1gCNeBvIKwY6TsIyTmyDg1KcJUcJDrwYxO\ntAeYI2PvU5PtKMmpnfnwFQMxL+2nfWJWNzboBCDr4YvoFI+rN+A=" From 0155f41003d7de373087983a9327f4a13b8152f9 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 14 Oct 2013 17:20:08 -0200 Subject: [PATCH 64/73] AMS requires Ruby 1.9.3+ and Rails 3.2+ --- active_model_serializers.gemspec | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/active_model_serializers.gemspec b/active_model_serializers.gemspec index 98105777..4592f6a7 100644 --- a/active_model_serializers.gemspec +++ b/active_model_serializers.gemspec @@ -10,16 +10,15 @@ Gem::Specification.new do |gem| gem.summary = %q{Bringing consistency and object orientation to model serialization. Works great for client-side MVC frameworks!} gem.homepage = "https://github.com/rails-api/active_model_serializers" - gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) } - gem.files = `git ls-files`.split("\n") - gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n") + gem.files = Dir['README.md', 'CHANGELOG.md', 'CONTRIBUTING.md', 'DESIGN.textile', 'MIT-LICENSE', 'lib/**/*', 'test/**/*'] + gem.test_files = Dir['test/**/*'] + gem.name = "active_model_serializers" gem.require_paths = ["lib"] gem.version = ActiveModel::Serializer::VERSION - gem.add_dependency 'activemodel', '>= 3.0' - gem.add_development_dependency "rails", ">= 3.0" - gem.add_development_dependency "pry" - gem.add_development_dependency "simplecov" - gem.add_development_dependency "coveralls" + gem.required_ruby_version = ">= 1.9.3" + + gem.add_dependency "activemodel", ">= 3.2" + gem.add_development_dependency "rails", ">= 3.2" end From 477a0ae1d2f7167f54b39c18707aacb4518c33b4 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 14 Oct 2013 17:20:28 -0200 Subject: [PATCH 65/73] Add back README.md file --- README.md | 703 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 703 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 00000000..8e748a3f --- /dev/null +++ b/README.md @@ -0,0 +1,703 @@ +[![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 + +The purpose of `ActiveModel::Serializers` is to provide an object to +encapsulate serialization of objects which respond to +read\_attribute\_for\_serialization like ActiveModel ones and including +`ActiveRecord` objects. + +Serializers know about both a model and the `current_user`, so you can +customize serialization based upon whether a user is authorized to see the +content. + +In short, **serializers replace hash-driven development with object-oriented +development.** + +# Installing + +The easiest way to install `ActiveModel::Serializers` is to add it to your +`Gemfile`: + +```ruby +gem "active_model_serializers" +``` + +Then, install it on the command line: + +``` +$ bundle install +``` + +#### 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: + +```ruby +gem "active_model_serializers", "~> 0.8.0" +``` + + +# Creating a Serializer + +The easiest way to create a new serializer is to generate a new resource, which +will generate a serializer at the same time: + +``` +$ rails g resource post title:string body:string +``` + +This will generate a serializer in `app/serializers/post_serializer.rb` for +your new model. You can also generate a serializer for an existing model with +the serializer generator: + +``` +$ rails g serializer post +``` + +### Support for POROs + +Currently `ActiveModel::Serializers` expects objects to implement +read\_attribute\_for\_serialization. That's all you need to do to have +your POROs supported. + +# ActiveModel::Serializer + +All new serializers descend from ActiveModel::Serializer + +# render :json + +In your controllers, when you use `render :json`, Rails will now first search +for a serializer for the object and use it if available. + +```ruby +class PostsController < ApplicationController + def show + @post = Post.find(params[:id]) + render json: @post + end +end +``` + +In this case, Rails will look for a serializer named `PostSerializer`, and if +it exists, use it to serialize the `Post`. + +This also works with `respond_with`, which uses `to_json` under the hood. Also +note that any options passed to `render :json` will be passed to your +serializer and available as `@options` inside. + +To specify a custom serializer for an object, you can specify the +serializer when you render the object: + +```ruby +render json: @post, serializer: FancyPostSerializer +``` + +## Arrays + +In your controllers, when you use `render :json` for an array of objects, AMS will +use `ActiveModel::ArraySerializer` (included in this project) as the base serializer, +and the individual `Serializer` for the objects contained in that array. + +```ruby +class PostSerializer < ActiveModel::Serializer + attributes :title, :body +end + +class PostsController < ApplicationController + def index + @posts = Post.all + render json: @posts + end +end +``` + +Given the example above, the index action will return + +```json +{ + "posts": + [ + { "title": "Post 1", "body": "Hello!" }, + { "title": "Post 2", "body": "Goodbye!" } + ] +} +``` + +By default, the root element is the name of the controller. For example, `PostsController` +generates a root element "posts". To change it: + +```ruby +render json: @posts, root: "some_posts" +``` + +You may disable the root element for arrays at the top level, which will result in +more concise json. See the next section for ways on how to do this. Disabling the +root element of the array with any of those methods will produce + +```json +[ + { "title": "Post 1", "body": "Hello!" }, + { "title": "Post 2", "body": "Goodbye!" } +] +``` + +To specify a custom serializer for the items within an array: + +```ruby +render json: @posts, each_serializer: FancyPostSerializer +``` + +## Disabling the root element + +You have 4 options to disable the root element, each with a slightly different scope: + +#### 1. Disable root globally for all, or per class + +In an initializer: + +```ruby +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 +``` + +#### 2. Disable root per render call in your controller + +```ruby +render json: @posts, root: false +``` + +#### 3. Subclass the serializer, and specify using it + +```ruby +class CustomArraySerializer < ActiveModel::ArraySerializer + self.root = false +end + +# controller: +render json: @posts, serializer: CustomArraySerializer +``` + +#### 4. Define default_serializer_options in your controller + +If you define `default_serializer_options` method in your controller, +all serializers in actions of this controller and it's children will use them. +One of the options may be `root: false` + +```ruby +def default_serializer_options + { + root: false + } +end +``` + +## Getting the old version + +If you find that your project is already relying on the old rails to_json +change `render :json` to `render json: @your_object.to_json`. + +# Attributes and Associations + +Once you have a serializer, you can specify which attributes and associations +you would like to include in the serialized form. + +```ruby +class PostSerializer < ActiveModel::Serializer + attributes :id, :title, :body + has_many :comments +end +``` + +## Attributes + +For specified attributes, a serializer will look up the attribute on the +object you passed to `render :json`. It uses +`read_attribute_for_serialization`, which `ActiveRecord` objects implement as a +regular attribute lookup. + +Before looking up the attribute on the object, a serializer will check for the +presence of a method with the name of the attribute. This allows serializers to +include properties beyond the simple attributes of the model. For example: + +```ruby +class PersonSerializer < ActiveModel::Serializer + attributes :first_name, :last_name, :full_name + + def full_name + "#{object.first_name} #{object.last_name}" + end +end +``` + +Within a serializer's methods, you can access the object being +serialized as `object`. + +Since this shadows any attribute named `object`, you can include them through `object.object`. For example: + +```ruby +class VersionSerializer < ActiveModel::Serializer + attribute :version_object, key: :object + + def version_object + object.object + end +end +``` + +You can also access the `scope` method, which provides an +authorization context to your serializer. By default, the context +is the current user of your application, but this +[can be customized](#customizing-scope). + +Serializers provides a method named `filter` used to determine what +attributes and associations should be included in the output. This is +typically used to customize output based on `current_user`. For example: + +```ruby +class PostSerializer < ActiveModel::Serializer + attributes :id, :title, :body, :author + + def filter(keys) + if scope.admin? + keys + else + keys - [:author] + end + end +end +``` + +And it's also safe to mutate keys argument by doing keys.delete(:author) +in case you want to avoid creating two extra arrays. + +If you would like the key in the outputted JSON to be different from its name +in ActiveRecord, you can declare the attribute with the different name +and redefine that method: + +```ruby +class PostSerializer < ActiveModel::Serializer + # look up subject on the model, but use title in the JSON + def title + object.subject + end + + attributes :id, :body, :title + has_many :comments +end +``` + +If you would like to add meta information to the outputted JSON, use the `:meta` +option: + +```ruby +render json: @posts, serializer: CustomArraySerializer, meta: {total: 10} +``` + +The above usage of `:meta` will produce the following: + +```json +{ + "meta": { "total": 10 }, + "posts": [ + { "title": "Post 1", "body": "Hello!" }, + { "title": "Post 2", "body": "Goodbye!" } + ] +} +``` + +If you would like to change the meta key name you can use the `:meta_key` option: + +```ruby +render json: @posts, serializer: CustomArraySerializer, meta: {total: 10}, meta_key: 'meta_object' +``` + +The above usage of `:meta_key` will produce the following: + +```json +{ + "meta_object": { "total": 10 }, + "posts": [ + { "title": "Post 1", "body": "Hello!" }, + { "title": "Post 2", "body": "Goodbye!" } + ] +} +``` + +If you would like direct, low-level control of attribute serialization, you can +completely override the `attributes` method to return the hash you need: + +```ruby +class PersonSerializer < ActiveModel::Serializer + attributes :first_name, :last_name + + def attributes + hash = super + if scope.admin? + hash["ssn"] = object.ssn + hash["secret"] = object.mothers_maiden_name + end + hash + end +end +``` + +## Associations + +For specified associations, the serializer will look up the association and +then serialize each element of the association. For instance, a `has_many +:comments` association will create a new `CommentSerializer` for each comment +and use it to serialize the comment. + +By default, serializers simply look up the association on the original object. +You can customize this behavior by implementing a method with the name of the +association and returning a different Array. Often, you will do this to +customize the objects returned based on the current user. + +```ruby +class PostSerializer < ActiveModel::Serializer + attributes :id, :title, :body + has_many :comments + + # only let the user see comments he created. + def comments + object.comments.where(created_by: current_user) + end +end +``` + +As with attributes, you can change the JSON key that the serializer should +use for a particular association. + +```ruby +class PostSerializer < ActiveModel::Serializer + attributes :id, :title, :body + + # look up comments, but use +my_comments+ as the key in JSON + has_many :comments, key: :my_comments +end +``` + +Also, as with attributes, serializers will execute a filter method to +determine which associations should be included in the output. For +example: + +```ruby +class PostSerializer < ActiveModel::Serializer + attributes :id, :title, :body + has_many :comments + + def filter(keys) + keys.delete :comments if object.comments_disabled? + keys + end +end +``` + +Or ... + +```ruby +class PostSerializer < ActiveModel::Serializer + attributes :id, :title, :body + has_one :author + has_many :comments + + def filter(keys) + keys.delete :author unless current_user.admin? + keys.delete :comments if object.comments_disabled? + keys + end +end +``` + +You may also use the `:serializer` option to specify a custom serializer class and the `:polymorphic` option to specify an association that is polymorphic (STI), e.g.: + +```ruby + has_many :comments, serializer: CommentShortSerializer + has_one :reviewer, polymorphic: true +``` + +Serializers are only concerned with multiplicity, and not ownership. `belongs_to` ActiveRecord associations can be included using `has_one` in your serializer. + +NOTE: polymorphic was removed because was only supported for has\_one +associations and is in the TODO list of the project. + +## Embedding Associations + +By default, associations will be embedded inside the serialized object. So if +you have a post, the outputted JSON will look like: + +```json +{ + "post": { + "id": 1, + "title": "New post", + "body": "A body!", + "comments": [ + { "id": 1, "body": "what a dumb post" } + ] + } +} +``` + +This is convenient for simple use-cases, but for more complex clients, it is +better to supply an Array of IDs for the association. This makes your API more +flexible from a performance standpoint and avoids wasteful duplication. + +To embed IDs instead of associations, simply use the `embed` class method: + +```ruby +class PostSerializer < ActiveModel::Serializer + embed :ids + + attributes :id, :title, :body + has_many :comments +end +``` + +Now, any associations will be supplied as an Array of IDs: + +```json +{ + "post": { + "id": 1, + "title": "New post", + "body": "A body!", + "comment_ids": [ 1, 2, 3 ] + } +} +``` + +Alternatively, you can choose to embed only the ids or the associated objects per association: + +```ruby +class PostSerializer < ActiveModel::Serializer + attributes :id, :title, :body + + has_many :comments, embed: :objects + has_many :tags, embed: :ids +end +``` + +The JSON will look like this: + +```json +{ + "post": { + "id": 1, + "title": "New post", + "body": "A body!", + "comments": [ + { "id": 1, "body": "what a dumb post" } + ], + "tag_ids": [ 1, 2, 3 ] + } +} +``` + +In addition to supplying an Array of IDs, you may want to side-load the data +alongside the main object. This makes it easier to process the entire package +of data without having to recursively scan the tree looking for embedded +information. It also ensures that associations that are shared between several +objects (like tags), are only delivered once for the entire payload. + +You can specify that the data be included like this: + +```ruby +class PostSerializer < ActiveModel::Serializer + embed :ids, include: true + + attributes :id, :title, :body + has_many :comments +end +``` + +Assuming that the comments also `has_many :tags`, you will get a JSON like +this: + +```json +{ + "post": { + "id": 1, + "title": "New post", + "body": "A body!", + "comment_ids": [ 1, 2 ] + }, + "comments": [ + { "id": 1, "body": "what a dumb post", "tag_ids": [ 1, 2 ] }, + { "id": 2, "body": "i liked it", "tag_ids": [ 1, 3 ] }, + ], + "tags": [ + { "id": 1, "name": "short" }, + { "id": 2, "name": "whiny" }, + { "id": 3, "name": "happy" } + ] +} +``` + +You can also specify a different root for the embedded objects than the key +used to reference them: + +```ruby +class PostSerializer < ActiveModel::Serializer + embed :ids, include: true + + attributes :id, :title, :body + has_many :comments, key: :comment_ids, root: :comment_objects +end +``` + +This would generate JSON that would look like this: + +```json +{ + "post": { + "id": 1, + "title": "New post", + "body": "A body!", + "comment_ids": [ 1 ] + }, + "comment_objects": [ + { "id": 1, "body": "what a dumb post" } + ] +} +``` + +You can also specify a different attribute to use rather than the ID of the +objects: + +```ruby +class PostSerializer < ActiveModel::Serializer + embed :ids, include: true + + attributes :id, :title, :body + has_many :comments, embed_key: :external_id +end +``` + +This would generate JSON that would look like this: + +```json +{ + "post": { + "id": 1, + "title": "New post", + "body": "A body!", + "comment_ids": [ "COMM001" ] + }, + "comments": [ + { "id": 1, "external_id": "COMM001", "body": "what a dumb post" } + ] +} +``` + +**NOTE**: The `embed :ids` mechanism is primary useful for clients that process +data in bulk and load it into a local store. For these clients, the ability to +easily see all of the data per type, rather than having to recursively scan the +data looking for information, is extremely useful. + +If you are mostly working with the data in simple scenarios and manually making +Ajax requests, you probably just want to use the default embedded behavior. + +## Customizing Scope + +In a serializer, `current_user` is the current authorization scope which the controller +provides to the serializer when you call `render :json`. By default, this is +`current_user`, but can be customized in your controller by calling +`serialization_scope`: + +```ruby +class ApplicationController < ActionController::Base + serialization_scope :current_admin +end +``` + +The above example will also change the scope from `current_user` to +`current_admin`. + +Please note that, until now, `serialization_scope` doesn't accept a second +object with options for specifying which actions should or should not take a +given scope in consideration. + +To be clear, it's not possible, yet, to do something like this: + +```ruby +class SomeController < ApplicationController + serialization_scope :current_admin, except: [:index, :show] +end +``` + +So, in order to have a fine grained control of what each action should take in +consideration for its scope, you may use something like this: + +```ruby +class CitiesController < ApplicationController + serialization_scope nil + + def index + @cities = City.all + + render json: @cities, each_serializer: CitySerializer + end + + def show + @city = City.find(params[:id]) + + render json: @city, scope: current_admin, scope_name: :current_admin + end +end +``` + +Assuming that the `current_admin` method needs to make a query in the database +for the current user, the advantage of this approach is that, by setting +`serialization_scope` to `nil`, the `index` action no longer will need to make +that query, only the `show` action will. + +## Caching + +NOTE: This functionality was removed from AMS and it's in the TODO list. +We need to re-think and re-design the caching strategy for the next +version of AMS. + +To cache a serializer, call `cached` and define a `cache_key` method: + +```ruby +class PostSerializer < ActiveModel::Serializer + cached # enables caching for this serializer + + attributes :title, :body + + def cache_key + [object, current_user] + end +end +``` + +The caching interface uses `Rails.cache` under the hood. + +# Design and Implementation + +## Keep it Simple + +ActiveModel::Serializers is capable of producing complex JSON views/large object +trees, and it may be tempting to design in this way so that your client can make +fewer requests to get data and so that related querying can be optimized. +However, keeping things simple in your serializers and controllers may +significantly reduce complexity and maintenance over the long-term development +of your application. Please consider reducing the complexity of the JSON views +you provide via the serializers as you build out your application, so that +controllers/services can be more easily reused without a lot of complexity +later. + +## Performance + +As you develop your controllers or other code that utilizes serializers, try to +avoid n+1 queries by ensuring that data loads in an optimal fashion, e.g. if you +are using ActiveRecord, you might want to use query includes or joins as needed +to make the data available that the serializer(s) need. From 4ab16381bdfaaeeb8056bf914bebe3526a66dda7 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Tue, 15 Oct 2013 13:45:52 -0200 Subject: [PATCH 66/73] Bump version to 0.9.0.pre --- lib/active_model/serializer/version.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/active_model/serializer/version.rb b/lib/active_model/serializer/version.rb index 10e62668..fa2ed76d 100644 --- a/lib/active_model/serializer/version.rb +++ b/lib/active_model/serializer/version.rb @@ -1,5 +1,5 @@ module ActiveModel class Serializer - VERSION = "0.8.1" + VERSION = "0.9.0.pre" end end From cf8b55ea01f89d1437530b465b45ed341e03d230 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Thu, 17 Oct 2013 13:35:00 -0400 Subject: [PATCH 67/73] Setting join_table on AR tests --- test/fixtures/active_record.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/fixtures/active_record.rb b/test/fixtures/active_record.rb index 8df19059..6ae75f43 100644 --- a/test/fixtures/active_record.rb +++ b/test/fixtures/active_record.rb @@ -38,13 +38,13 @@ end class ARPost < ActiveRecord::Base has_many :ar_comments, class_name: 'ARComment' - has_and_belongs_to_many :ar_tags, class_name: 'ARTag' + has_and_belongs_to_many :ar_tags, class_name: 'ARTag', join_table: :ar_posts_tags belongs_to :ar_section, class_name: 'ARSection' end class ARComment < ActiveRecord::Base belongs_to :ar_post, class_name: 'ARPost' - has_and_belongs_to_many :ar_tags, class_name: 'ARTag' + has_and_belongs_to_many :ar_tags, class_name: 'ARTag', join_table: :ar_comments_tags end class ARTag < ActiveRecord::Base From 21bf1889beeda003dc5563d2340f2ba87a8139db Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Thu, 17 Oct 2013 13:35:44 -0400 Subject: [PATCH 68/73] Add rails3 Gemfile Also point the main Gemfile to rails4 --- Gemfile | 2 ++ Gemfile.rails3 | 10 ++++++++++ 2 files changed, 12 insertions(+) create mode 100644 Gemfile.rails3 diff --git a/Gemfile b/Gemfile index 6e810339..00cd24d9 100644 --- a/Gemfile +++ b/Gemfile @@ -6,3 +6,5 @@ gemspec gem 'sqlite3' gem 'coveralls', :require => false gem 'simplecov', :require => false + +gem 'rails', "~> 4.0.0" diff --git a/Gemfile.rails3 b/Gemfile.rails3 new file mode 100644 index 00000000..7afd0941 --- /dev/null +++ b/Gemfile.rails3 @@ -0,0 +1,10 @@ +source 'https://rubygems.org' + +# Specify gem dependencies in active_model_serializers.gemspec +gemspec + +gem 'sqlite3' +gem 'coveralls', :require => false +gem 'simplecov', :require => false + +gem 'rails', "~> 3.2.15" From 3c2af149d3ac1af9cd32e2126f8dfaba0292e1c0 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Thu, 17 Oct 2013 13:40:54 -0400 Subject: [PATCH 69/73] Ignore all .lock files --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index f61f58b8..9fab38c6 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,7 @@ .bundle .config .yardoc -Gemfile.lock +*.lock InstalledFiles _yardoc coverage From f6d5340f8999b53a1fb47d6b8f610702dd5b98ea Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Thu, 17 Oct 2013 14:19:40 -0400 Subject: [PATCH 70/73] Require minitest instead of test/unit --- test/test_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/test_helper.rb b/test/test_helper.rb index 378a96db..d60b8bc6 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,6 +1,6 @@ require 'bundler/setup' require 'coverage_setup' -require 'test/unit' +require 'minitest/autorun' require 'active_model_serializers' require 'fixtures/poro' From 3032bd7086275ea21fe0b2ce0b08baa0cbe7e909 Mon Sep 17 00:00:00 2001 From: Arthur Neves Date: Thu, 17 Oct 2013 14:21:32 -0400 Subject: [PATCH 71/73] Add minitest to rails3 Also add rails3 gemfile to travis And remote .15 from rails verison --- .travis.yml | 1 + Gemfile.rails3 | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 7911e745..a044659d 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,6 +6,7 @@ rvm: - rbx-19mode gemfile: - Gemfile + - Gemfile.rails3 - Gemfile.edge notifications: email: false diff --git a/Gemfile.rails3 b/Gemfile.rails3 index 7afd0941..7dc0bc7e 100644 --- a/Gemfile.rails3 +++ b/Gemfile.rails3 @@ -3,8 +3,9 @@ source 'https://rubygems.org' # Specify gem dependencies in active_model_serializers.gemspec gemspec +gem 'minitest' gem 'sqlite3' gem 'coveralls', :require => false gem 'simplecov', :require => false -gem 'rails', "~> 3.2.15" +gem 'rails', "~> 3.2" From c51fbcafb4b9431d2d3d56e8bf37699377f4fb83 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Thu, 17 Oct 2013 16:37:55 -0200 Subject: [PATCH 72/73] Use minitest 4.0 in Rails 3.2 --- Gemfile.rails3 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gemfile.rails3 b/Gemfile.rails3 index 7dc0bc7e..5f6012a4 100644 --- a/Gemfile.rails3 +++ b/Gemfile.rails3 @@ -3,9 +3,9 @@ source 'https://rubygems.org' # Specify gem dependencies in active_model_serializers.gemspec gemspec -gem 'minitest' gem 'sqlite3' gem 'coveralls', :require => false gem 'simplecov', :require => false -gem 'rails', "~> 3.2" +gem 'minitest', '~> 4.0' +gem 'rails', '~> 3.2' From 5ae47f785903bd2840274d1db6c4e5da39414c9f Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Fri, 18 Oct 2013 17:41:57 -0200 Subject: [PATCH 73/73] Add missing entries to CHANGELOG.md --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b7513569..ab66c950 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,10 @@ - Serializer#filter method - SETTINGS object +* Remove support for ruby 1.8 versions. + +* Require rails >= 3.2. + # VERSION 0.8.1 * Fix bug whereby a serializer using 'options' would blow up.