From 14f51f2ea930130ad695eb86c4f2f13512433271 Mon Sep 17 00:00:00 2001 From: Santiago Pastorino Date: Mon, 16 Sep 2013 17:11:30 -0300 Subject: [PATCH] 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