Provide Rails url_helpers via SerializationContext

This commit is contained in:
Ben Mills 2016-03-01 19:23:55 -07:00
parent 3ba5254a46
commit cc10928472
15 changed files with 177 additions and 52 deletions

View File

@ -3,6 +3,8 @@
Breaking changes: Breaking changes:
Features: Features:
- [#1550](https://github.com/rails-api/active_model_serializers/pull/1550) Add
Rails url_helpers to `SerializationContext` for use in links. (@remear, @bf4)
- [#1004](https://github.com/rails-api/active_model_serializers/pull/1004) JSON API errors object implementation. - [#1004](https://github.com/rails-api/active_model_serializers/pull/1004) JSON API errors object implementation.
- Only implements `detail` and `source` as derived from `ActiveModel::Error` - Only implements `detail` and `source` as derived from `ActiveModel::Error`
- Provides checklist of remaining questions and remaining parts of the spec. - Provides checklist of remaining questions and remaining parts of the spec.

View File

@ -45,25 +45,23 @@ Rake::TestTask.new do |t|
end end
desc 'Run isolated tests' desc 'Run isolated tests'
task isolated: ['test:isolated:railtie'] task isolated: ['test:isolated']
namespace :test do namespace :test do
namespace :isolated do task :isolated do
desc 'Run isolated tests for Railtie' desc 'Run isolated tests for Railtie'
task :railtie do
require 'shellwords' require 'shellwords'
dir = File.dirname(__FILE__) dir = File.dirname(__FILE__)
file = Shellwords.shellescape("#{dir}/test/active_model_serializers/railtie_test_isolated.rb")
dir = Shellwords.shellescape(dir) dir = Shellwords.shellescape(dir)
isolated_test_files = FileList['test/**/*_test_isolated.rb']
# https://github.com/rails/rails/blob/3d590add45/railties/lib/rails/generators/app_base.rb#L345-L363 # https://github.com/rails/rails/blob/3d590add45/railties/lib/rails/generators/app_base.rb#L345-L363
_bundle_command = Gem.bin_path('bundler', 'bundle') _bundle_command = Gem.bin_path('bundler', 'bundle')
require 'bundler' require 'bundler'
Bundler.with_clean_env do Bundler.with_clean_env do
command = "-w -I#{dir}/lib -I#{dir}/test #{file}" isolated_test_files.all? do |test_file|
command = "-w -I#{dir}/lib -I#{dir}/test #{Shellwords.shellescape(test_file)}"
full_command = %("#{Gem.ruby}" #{command}) full_command = %("#{Gem.ruby}" #{command})
system(full_command) or # rubocop:disable Style/AndOr system(full_command)
fail 'Failures' end or fail 'Failures' # rubocop:disable Style/AndOr
end
end end
end end
end end

View File

@ -96,3 +96,12 @@ class PostsController < ApplicationController
end end
``` ```
If you wish to use Rails url helpers for link generation, e.g., `link(:resources) { resources_url }`, ensure your application sets
`Rails.application.routes.default_url_options`.
```ruby
Rails.application.routes.default_url_options = {
host: 'example.com'
}
```

View File

@ -103,7 +103,10 @@ PR please :)
#### links #### links
##### How to add top-level links If you wish to use Rails url helpers for link generation, e.g., `link(:resources) { resources_url }`, ensure your application sets
`Rails.application.routes.default_url_options`.
##### Top-level
JsonApi supports a [links object](http://jsonapi.org/format/#document-links) to be specified at top-level, that you can specify in the `render`: JsonApi supports a [links object](http://jsonapi.org/format/#document-links) to be specified at top-level, that you can specify in the `render`:
@ -144,6 +147,33 @@ That's the result:
This feature is specific to JsonApi, so you have to use the use the [JsonApi Adapter](adapters.md#jsonapi) This feature is specific to JsonApi, so you have to use the use the [JsonApi Adapter](adapters.md#jsonapi)
##### Resource-level
In your serializer, define each link in one of the following methods:
As a static string
```ruby
link :link_name, 'https://example.com/resource'
```
As a block to be evaluated. When using Rails, URL helpers are available.
Ensure your application sets `Rails.application.routes.default_url_options`.
```ruby
link :link_name_ do
"https://example.com/resource/#{object.id}"
end
link(:link_name) { "https://example.com/resource/#{object.id}" }
link(:link_name) { resource_url(object) }
link(:link_name) { url_for(controller: 'controller_name', action: 'index', only_path: false) }
```
### serializer_opts ### serializer_opts
#### include #### include

View File

@ -135,13 +135,15 @@ With the `:json_api` adapter, the previous serializers would be rendered as:
#### ::link #### ::link
e.g.
```ruby ```ruby
link :other, 'https://example.com/resource'
link :self do link :self do
href "https://example.com/link_author/#{object.id}" href "https://example.com/link_author/#{object.id}"
end end
link :author { link_author_url(object) }
link :link_authors { link_authors_url }
link :other, 'https://example.com/resource'
link :posts { link_author_posts_url(object) }
```
``` ```
#### #object #### #object

View File

@ -20,9 +20,11 @@ module ActiveModel
# Define a link on a serializer. # Define a link on a serializer.
# @example # @example
# link :self { "//example.com/posts/#{object.id}" } # link(:self) { resource_url(object) }
# @example # @example
# link :self, "//example.com/user" # link(:self) { "http://example.com/resource/#{object.id}" }
# @example
# link :resource, "http://example.com/resource"
# #
def link(name, value = nil, &block) def link(name, value = nil, &block)
_links[name] = block || value _links[name] = block || value

View File

@ -1,7 +1,12 @@
require 'active_support/core_ext/module/delegation'
module ActiveModelSerializers module ActiveModelSerializers
module Adapter module Adapter
class JsonApi class JsonApi
class Link class Link
include SerializationContext.url_helpers
delegate :default_url_options, to: SerializationContext
def initialize(serializer, value) def initialize(serializer, value)
@object = serializer.object @object = serializer.object
@scope = serializer.scope @scope = serializer.scope

View File

@ -15,6 +15,11 @@ module ActiveModelSerializers
end end
end end
initializer 'active_model_serializers.prepare_serialization_context' do
SerializationContext.url_helpers = Rails.application.routes.url_helpers
SerializationContext.default_url_options = Rails.application.routes.default_url_options
end
# This hook is run after the action_controller railtie has set the configuration # This hook is run after the action_controller railtie has set the configuration
# based on the *environment* configuration and before any config/initializers are run # based on the *environment* configuration and before any config/initializers are run
# and also before eager_loading (if enabled). # and also before eager_loading (if enabled).

View File

@ -1,10 +1,24 @@
module ActiveModelSerializers module ActiveModelSerializers
class SerializationContext class SerializationContext
class << self
attr_writer :url_helpers, :default_url_options
end
attr_reader :request_url, :query_parameters attr_reader :request_url, :query_parameters
def initialize(request) def initialize(request, options = {})
@request_url = request.original_url[/\A[^?]+/] @request_url = request.original_url[/\A[^?]+/]
@query_parameters = request.query_parameters @query_parameters = request.query_parameters
@url_helpers = options.delete(:url_helpers) || self.class.url_helpers
@default_url_options = options.delete(:default_url_options) || self.class.default_url_options
end
def self.url_helpers
@url_helpers ||= Module.new
end
def self.default_url_options
@default_url_options ||= {}
end end
end end
end end

View File

@ -18,6 +18,12 @@ class RailtieTest < ActiveSupport::TestCase
"ActionController::Serialization should be included in ActionController::Base, but isn't" "ActionController::Serialization should be included in ActionController::Base, but isn't"
end end
test 'prepares url_helpers for SerializationContext' do
assert ActiveModelSerializers::SerializationContext.url_helpers.respond_to? :url_for
assert_equal Rails.application.routes.default_url_options,
ActiveModelSerializers::SerializationContext.default_url_options
end
test 'sets the ActiveModelSerializers.logger to Rails.logger' do test 'sets the ActiveModelSerializers.logger to Rails.logger' do
refute_nil Rails.logger refute_nil Rails.logger
refute_nil ActiveModelSerializers.logger refute_nil ActiveModelSerializers.logger

View File

@ -1,18 +0,0 @@
require 'test_helper'
class ActiveModelSerializers::SerializationContextTest < ActiveSupport::TestCase
def create_context
request = Minitest::Mock.new
request.expect(:original_url, 'original_url')
request.expect(:query_parameters, 'query_parameters')
ActiveModelSerializers::SerializationContext.new(request)
end
def test_create_context_with_request_url_and_query_parameters
context = create_context
assert_equal context.request_url, 'original_url'
assert_equal context.query_parameters, 'query_parameters'
end
end

View File

@ -0,0 +1,58 @@
# Execute this test in isolation
require 'support/isolated_unit'
require 'minitest/mock'
class SerializationContextTest < ActiveSupport::TestCase
include ActiveSupport::Testing::Isolation
def create_request
request = Minitest::Mock.new
request.expect(:original_url, 'original_url')
request.expect(:query_parameters, 'query_parameters')
end
class WithRails < SerializationContextTest
setup do
require 'rails'
require 'active_model_serializers'
make_basic_app
@context = ActiveModelSerializers::SerializationContext.new(create_request)
end
test 'create context with request url and query parameters' do
assert_equal @context.request_url, 'original_url'
assert_equal @context.query_parameters, 'query_parameters'
end
test 'url_helpers is set up for Rails url_helpers' do
assert_equal Module, ActiveModelSerializers::SerializationContext.url_helpers.class
assert ActiveModelSerializers::SerializationContext.url_helpers.respond_to? :url_for
end
test 'default_url_options returns Rails.application.routes.default_url_options' do
assert_equal Rails.application.routes.default_url_options,
ActiveModelSerializers::SerializationContext.default_url_options
end
end
class WithoutRails < SerializationContextTest
setup do
require 'active_model_serializers/serialization_context'
@context = ActiveModelSerializers::SerializationContext.new(create_request)
end
test 'create context with request url and query parameters' do
assert_equal @context.request_url, 'original_url'
assert_equal @context.query_parameters, 'query_parameters'
end
test 'url_helpers is a module when Rails is not present' do
assert_equal Module, ActiveModelSerializers::SerializationContext.url_helpers.class
refute ActiveModelSerializers::SerializationContext.url_helpers.respond_to? :url_for
end
test 'default_url_options return a Hash' do
assert Hash, ActiveModelSerializers::SerializationContext.default_url_options.class
end
end
end

View File

@ -7,18 +7,24 @@ module ActiveModelSerializers
LinkAuthor = Class.new(::Model) LinkAuthor = Class.new(::Model)
class LinkAuthorSerializer < ActiveModel::Serializer class LinkAuthorSerializer < ActiveModel::Serializer
link :self do link :self do
href "//example.com/link_author/#{object.id}" href "http://example.com/link_author/#{object.id}"
meta stuff: 'value' meta stuff: 'value'
end end
link(:author) { link_author_url(object.id) }
link :other, '//example.com/resource' link(:link_authors) { url_for(controller: 'link_authors', action: 'index', only_path: false) }
link(:posts) { link_author_posts_url(object.id) }
link :resource, 'http://example.com/resource'
link :yet_another do link :yet_another do
"//example.com/resource/#{object.id}" "http://example.com/resource/#{object.id}"
end end
end end
def setup def setup
Rails.application.routes.draw do
resources :link_authors do
resources :posts
end
end
@post = Post.new(id: 1337, comments: [], author: nil) @post = Post.new(id: 1337, comments: [], author: nil)
@author = LinkAuthor.new(id: 1337, posts: [@post]) @author = LinkAuthor.new(id: 1337, posts: [@post])
end end
@ -29,7 +35,7 @@ module ActiveModelSerializers
adapter: :json_api, adapter: :json_api,
links: { links: {
self: { self: {
href: '//example.com/posts', href: 'http://example.com/posts',
meta: { meta: {
stuff: 'value' stuff: 'value'
} }
@ -37,7 +43,7 @@ module ActiveModelSerializers
}).serializable_hash }).serializable_hash
expected = { expected = {
self: { self: {
href: '//example.com/posts', href: 'http://example.com/posts',
meta: { meta: {
stuff: 'value' stuff: 'value'
} }
@ -68,13 +74,16 @@ module ActiveModelSerializers
hash = serializable(@author, adapter: :json_api).serializable_hash hash = serializable(@author, adapter: :json_api).serializable_hash
expected = { expected = {
self: { self: {
href: '//example.com/link_author/1337', href: 'http://example.com/link_author/1337',
meta: { meta: {
stuff: 'value' stuff: 'value'
} }
}, },
other: '//example.com/resource', author: 'http://example.com/link_authors/1337',
yet_another: '//example.com/resource/1337' link_authors: 'http://example.com/link_authors',
resource: 'http://example.com/resource',
posts: 'http://example.com/link_authors/1337/posts',
yet_another: 'http://example.com/resource/1337'
} }
assert_equal(expected, hash[:data][:links]) assert_equal(expected, hash[:data][:links])
end end

View File

@ -63,6 +63,7 @@ module TestHelpers
# Set a fake logger to avoid creating the log directory automatically # Set a fake logger to avoid creating the log directory automatically
fake_logger = Logger.new(nil) fake_logger = Logger.new(nil)
config.logger = fake_logger config.logger = fake_logger
Rails.application.routes.default_url_options = { host: 'example.com' }
end end
@app.respond_to?(:secrets) && @app.secrets.secret_key_base = '3b7cd727ee24e8444053437c36cc66c4' @app.respond_to?(:secrets) && @app.secrets.secret_key_base = '3b7cd727ee24e8444053437c36cc66c4'

View File

@ -10,6 +10,8 @@ class ActiveModelSerializers::RailsApplication < Rails::Application
config.action_controller.perform_caching = true config.action_controller.perform_caching = true
ActionController::Base.cache_store = :memory_store ActionController::Base.cache_store = :memory_store
Rails.application.routes.default_url_options = { host: 'example.com' }
end end
end end
ActiveModelSerializers::RailsApplication.initialize! ActiveModelSerializers::RailsApplication.initialize!