Skip to content

Commit

Permalink
Refactor. (#56)
Browse files Browse the repository at this point in the history
  • Loading branch information
beauby authored Sep 13, 2017
1 parent 27aeea8 commit 14a9421
Show file tree
Hide file tree
Showing 8 changed files with 198 additions and 158 deletions.
5 changes: 4 additions & 1 deletion lib/jsonapi/rails.rb
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
require 'jsonapi/deserializable'
require 'jsonapi/serializable'
require 'jsonapi/rails/configuration'
require 'jsonapi/rails/railtie'

module JSONAPI
module Rails
require 'jsonapi/rails/configuration'
require 'jsonapi/rails/logging'

extend Configurable
extend Logging
end
end
158 changes: 4 additions & 154 deletions lib/jsonapi/rails/controller.rb
Original file line number Diff line number Diff line change
@@ -1,163 +1,13 @@
require 'jsonapi/deserializable'
require 'jsonapi/parser'
require 'jsonapi/rails/configuration'
require 'jsonapi/rails/controller/deserialization'
require 'jsonapi/rails/controller/hooks'

module JSONAPI
module Rails
module Deserializable
# @private
class Resource < JSONAPI::Deserializable::Resource
id
type
attributes
has_one do |_rel, id, type, key|
type = type.to_s.singularize.camelize
{ "#{key}_id".to_sym => id, "#{key}_type".to_sym => type }
end
has_many do |_rel, ids, types, key|
key = key.to_s.singularize
types = types.map { |t| t.to_s.singularize.camelize }
{ "#{key}_ids".to_sym => ids, "#{key}_types".to_sym => types }
end
end
end

# ActionController methods and hooks for JSON API deserialization and
# rendering.
module Controller
extend ActiveSupport::Concern

JSONAPI_POINTERS_KEY = 'jsonapi-rails.jsonapi_pointers'.freeze

class_methods do
# Declare a deserializable resource.
#
# @param key [Symbol] The key under which the deserialized hash will be
# available within the `params` hash.
# @param options [Hash]
# @option class [Class] A custom deserializer class. Optional.
# @option only List of actions for which deserialization should happen.
# Optional.
# @option except List of actions for which deserialization should not
# happen. Optional.
# @yieldreturn Optional block for in-line definition of custom
# deserializers.
#
# @example
# class ArticlesController < ActionController::Base
# deserializable_resource :article, only: [:create, :update]
#
# def create
# article = Article.new(params[:article])
#
# if article.save
# render jsonapi: article
# else
# render jsonapi_errors: article.errors
# end
# end
#
# # ...
# end
#
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
def deserializable_resource(key, options = {}, &block)
options = options.dup
klass = options.delete(:class) ||
Class.new(JSONAPI::Rails::Deserializable::Resource, &block)

before_action(options) do |controller|
hash = controller.params.to_unsafe_hash[:_jsonapi]
if hash.nil?
JSONAPI::Rails.config[:logger].warn do
"Unable to deserialize #{key} because no JSON API payload was" \
" found. (#{controller.controller_name}##{params[:action]})"
end
next
end

ActiveSupport::Notifications
.instrument('parse.jsonapi-rails',
key: key, payload: hash, class: klass) do
JSONAPI::Parser::Resource.parse!(hash)
resource = klass.new(hash[:data])
controller.request.env[JSONAPI_POINTERS_KEY] =
resource.reverse_mapping
controller.params[key.to_sym] = resource.to_hash
end
end
end
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
end

# Hook for serializable class mapping (for resources).
# Overridden by the `class` renderer option.
# @return [Hash{Symbol=>Class}]
def jsonapi_class
JSONAPI::Rails.config[:jsonapi_class].dup
end

# Hook for serializable class mapping (for errors).
# Overridden by the `class` renderer option.
# @return [Hash{Symbol=>Class}]
def jsonapi_errors_class
JSONAPI::Rails.config[:jsonapi_errors_class].dup
end

# Hook for the jsonapi object.
# Overridden by the `jsonapi_object` renderer option.
# @return [Hash,nil]
def jsonapi_object
JSONAPI::Rails.config[:jsonapi_object]
end

# Hook for default exposures.
# @return [Hash]
def jsonapi_expose
instance_exec(&JSONAPI::Rails.config[:jsonapi_expose])
end

# Hook for default cache.
# @return [#fetch_multi]
def jsonapi_cache
instance_exec(&JSONAPI::Rails.config[:jsonapi_cache])
end

# Hook for default fields.
# @return [Hash{Symbol=>Array<Symbol>},nil]
def jsonapi_fields
instance_exec(&JSONAPI::Rails.config[:jsonapi_fields])
end

# Hook for default includes.
# @return [IncludeDirective]
def jsonapi_include
instance_exec(&JSONAPI::Rails.config[:jsonapi_include])
end

# Hook for default links.
# @return [Hash]
def jsonapi_links
instance_exec(&JSONAPI::Rails.config[:jsonapi_links])
end

# Hook for default meta.
# @return [Hash,nil]
def jsonapi_meta
instance_exec(&JSONAPI::Rails.config[:jsonapi_meta])
end

# Hook for pagination scheme.
# @return [Hash]
def jsonapi_pagination(resources)
instance_exec(resources, &JSONAPI::Rails.config[:jsonapi_pagination])
end

# JSON pointers for deserialized fields.
# @return [Hash{Symbol=>String}]
def jsonapi_pointers
request.env[JSONAPI_POINTERS_KEY] || {}
end
include Deserialization
include Hooks
end
end
end
83 changes: 83 additions & 0 deletions lib/jsonapi/rails/controller/deserialization.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
require 'jsonapi/parser'
require 'jsonapi/rails/deserializable_resource'

module JSONAPI
module Rails
module Controller
# Controller class and instance methods for deserialization of incoming
# JSON API payloads.
module Deserialization
extend ActiveSupport::Concern

JSONAPI_POINTERS_KEY = 'jsonapi-rails.jsonapi_pointers'.freeze

class_methods do
# Declare a deserializable resource.
#
# @param key [Symbol] The key under which the deserialized hash will be
# available within the `params` hash.
# @param options [Hash]
# @option class [Class] A custom deserializer class. Optional.
# @option only List of actions for which deserialization should happen.
# Optional.
# @option except List of actions for which deserialization should not
# happen. Optional.
# @yieldreturn Optional block for in-line definition of custom
# deserializers.
#
# @example
# class ArticlesController < ActionController::Base
# deserializable_resource :article, only: [:create, :update]
#
# def create
# article = Article.new(params[:article])
#
# if article.save
# render jsonapi: article
# else
# render jsonapi_errors: article.errors
# end
# end
#
# # ...
# end
#
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize
def deserializable_resource(key, options = {}, &block)
options = options.dup
klass = options.delete(:class) ||
Class.new(JSONAPI::Rails::DeserializableResource, &block)

before_action(options) do |controller|
hash = controller.params.to_unsafe_hash[:_jsonapi]
if hash.nil?
JSONAPI::Rails.logger.warn do
"Unable to deserialize #{key} because no JSON API payload was" \
" found. (#{controller.controller_name}##{params[:action]})"
end
next
end

ActiveSupport::Notifications
.instrument('parse.jsonapi-rails',
key: key, payload: hash, class: klass) do
JSONAPI::Parser::Resource.parse!(hash)
resource = klass.new(hash[:data])
controller.request.env[JSONAPI_POINTERS_KEY] =
resource.reverse_mapping
controller.params[key.to_sym] = resource.to_hash
end
end
end
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
end

# JSON pointers for deserialized fields.
# @return [Hash{Symbol=>String}]
def jsonapi_pointers
request.env[JSONAPI_POINTERS_KEY] || {}
end
end
end
end
end
75 changes: 75 additions & 0 deletions lib/jsonapi/rails/controller/hooks.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
require 'jsonapi/rails/configuration'

module JSONAPI
module Rails
module Controller
extend ActiveSupport::Concern

# Hooks for customizing rendering default options at controller-level.
module Hooks
# Hook for serializable class mapping (for resources).
# Overridden by the `class` renderer option.
# @return [Hash{Symbol=>Class}]
def jsonapi_class
JSONAPI::Rails.config[:jsonapi_class].dup
end

# Hook for serializable class mapping (for errors).
# Overridden by the `class` renderer option.
# @return [Hash{Symbol=>Class}]
def jsonapi_errors_class
JSONAPI::Rails.config[:jsonapi_errors_class].dup
end

# Hook for the jsonapi object.
# Overridden by the `jsonapi_object` renderer option.
# @return [Hash,nil]
def jsonapi_object
JSONAPI::Rails.config[:jsonapi_object]
end

# Hook for default exposures.
# @return [Hash]
def jsonapi_expose
instance_exec(&JSONAPI::Rails.config[:jsonapi_expose])
end

# Hook for default cache.
# @return [#fetch_multi]
def jsonapi_cache
instance_exec(&JSONAPI::Rails.config[:jsonapi_cache])
end

# Hook for default fields.
# @return [Hash{Symbol=>Array<Symbol>},nil]
def jsonapi_fields
instance_exec(&JSONAPI::Rails.config[:jsonapi_fields])
end

# Hook for default includes.
# @return [IncludeDirective]
def jsonapi_include
instance_exec(&JSONAPI::Rails.config[:jsonapi_include])
end

# Hook for default links.
# @return [Hash]
def jsonapi_links
instance_exec(&JSONAPI::Rails.config[:jsonapi_links])
end

# Hook for default meta.
# @return [Hash,nil]
def jsonapi_meta
instance_exec(&JSONAPI::Rails.config[:jsonapi_meta])
end

# Hook for pagination scheme.
# @return [Hash]
def jsonapi_pagination(resources)
instance_exec(resources, &JSONAPI::Rails.config[:jsonapi_pagination])
end
end
end
end
end
21 changes: 21 additions & 0 deletions lib/jsonapi/rails/deserializable_resource.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
require 'jsonapi/deserializable/resource'

module JSONAPI
module Rails
# Customized deserializable resource class to match ActiveRecord's API.
class DeserializableResource < JSONAPI::Deserializable::Resource
id
type
attributes
has_one do |_rel, id, type, key|
type = type.to_s.singularize.camelize
{ "#{key}_id".to_sym => id, "#{key}_type".to_sym => type }
end
has_many do |_rel, ids, types, key|
key = key.to_s.singularize
types = types.map { |t| t.to_s.singularize.camelize }
{ "#{key}_ids".to_sym => ids, "#{key}_types".to_sym => types }
end
end
end
end
2 changes: 1 addition & 1 deletion lib/jsonapi/rails/log_subscriber.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ def parse(event)
end

def logger
JSONAPI::Rails.config[:logger]
JSONAPI::Rails.logger
end
end
end
Expand Down
10 changes: 10 additions & 0 deletions lib/jsonapi/rails/logging.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
module JSONAPI
module Rails
# @private
module Logging
def logger
config[:logger]
end
end
end
end
2 changes: 0 additions & 2 deletions lib/jsonapi/rails/railtie.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
require 'rails/railtie'
require 'action_controller'
require 'active_support'

require 'jsonapi/rails/log_subscriber'
require 'jsonapi/rails/renderer'
Expand Down

0 comments on commit 14a9421

Please sign in to comment.