Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
language: ruby
rvm:
- 2.2.3
- 2.2.3
before_install: gem install bundler -v 1.11.2

script: "bundle exec rake"

install: bundle install --retry=3 --jobs=3

gemfile:
- gemfiles/rails_4.gemfile
- gemfiles/rails_5.gemfile

deploy:
provider: rubygems
api_key: $RUBYGEMS_API_KEY
Expand Down
7 changes: 6 additions & 1 deletion Rakefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
require "bundler/gem_tasks"
require "rspec/core/rake_task"
require "appraisal"

RSpec::Core::RakeTask.new(:spec)

task :default => :spec
if !ENV["APPRAISAL_INITIALIZED"] && !ENV["TRAVIS"]
task :default => :appraisal
else
task :default => :spec
end
19 changes: 7 additions & 12 deletions gemfiles/rails_4.gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
PATH
remote: ../
remote: ..
specs:
jsonapi_errorable (0.1.1)
active_model_serializers (~> 0.10)
rails (>= 4.1, < 6)
jsonapi_errorable (0.7.0)
jsonapi-serializable (~> 0.1)

GEM
remote: https://rubygems.org/
Expand All @@ -27,11 +26,6 @@ GEM
erubis (~> 2.7.0)
rails-dom-testing (~> 1.0, >= 1.0.5)
rails-html-sanitizer (~> 1.0, >= 1.0.2)
active_model_serializers (0.10.2)
actionpack (>= 4.1, < 6)
activemodel (>= 4.1, < 6)
jsonapi (~> 0.1.1.beta2)
railties (>= 4.1, < 6)
activejob (4.2.6)
activesupport (= 4.2.6)
globalid (>= 0.3.0)
Expand Down Expand Up @@ -63,8 +57,9 @@ GEM
activesupport (>= 4.1.0)
i18n (0.7.0)
json (1.8.3)
jsonapi (0.1.1.beta2)
json (~> 1.8)
jsonapi-renderer (0.2.0)
jsonapi-serializable (0.3.0)
jsonapi-renderer (~> 0.2.0)
jsonapi_spec_helpers (0.2.0)
loofah (2.0.3)
nokogiri (>= 1.5.9)
Expand Down Expand Up @@ -162,4 +157,4 @@ DEPENDENCIES
sqlite3

BUNDLED WITH
1.12.5
1.15.4
10 changes: 5 additions & 5 deletions gemfiles/rails_5.gemfile.lock
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
PATH
remote: ..
specs:
jsonapi_errorable (0.6.2)
jsonapi_errorable (0.7.0)
jsonapi-serializable (~> 0.1)

GEM
Expand Down Expand Up @@ -58,9 +58,9 @@ GEM
globalid (0.3.7)
activesupport (>= 4.1.0)
i18n (0.7.0)
jsonapi-renderer (0.1.2)
jsonapi-serializable (0.1.3)
jsonapi-renderer (~> 0.1)
jsonapi-renderer (0.2.0)
jsonapi-serializable (0.3.0)
jsonapi-renderer (~> 0.2.0)
jsonapi_spec_helpers (0.2.0)
loofah (2.0.3)
nokogiri (>= 1.5.9)
Expand Down Expand Up @@ -161,4 +161,4 @@ DEPENDENCIES
sqlite3

BUNDLED WITH
1.15.0
1.15.4
1 change: 1 addition & 0 deletions jsonapi_errorable.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ Gem::Specification.new do |spec|

spec.add_dependency 'jsonapi-serializable', '~> 0.1'

# Rails is added in Appraisals
spec.add_development_dependency "bundler", "~> 1.11"
spec.add_development_dependency "rake", "~> 10.0"
spec.add_development_dependency "rspec-rails", "~> 3.0"
Expand Down
139 changes: 100 additions & 39 deletions lib/jsonapi_errorable/serializers/validation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,85 +3,146 @@ module Serializers
class Validation
attr_reader :object

def initialize(object, relationship_params = {}, relationship_message = {})
def initialize(object, relationship_payloads = {}, relationship_meta = {})
@object = object
@relationship_params = relationship_params || {}
@relationship_message = relationship_message
@relationship_payloads = relationship_payloads
@relationship_meta = relationship_meta
end

def errors
return [] unless object.respond_to?(:errors)

all_errors = object.errors.to_hash.map do |attribute, messages|
messages.map do |message|
meta = { attribute: attribute, message: message }.merge(@relationship_message)
meta = { relationship: meta } if @relationship_message.present?

detail = object.errors.full_message(attribute, message)
detail = message if attribute.to_s.downcase == 'base'

{
def attribute_errors
[].tap do |errors|
each_error do |attribute, message, code|
error = {
code: 'unprocessable_entity',
status: '422',
title: 'Validation Error',
detail: detail,
detail: detail_for(attribute, message),
source: { pointer: pointer_for(object, attribute) },
meta: meta
meta: meta_for(attribute, message, code, @relationship_meta)
}

errors << error
end
end.flatten
all_errors << relationship_errors(@relationship_params)
all_errors.flatten!
all_errors.compact!
end
end

def errors
return [] unless object.respond_to?(:errors)

all_errors = attribute_errors
all_errors |= relationship_errors(@relationship_payloads)
all_errors
end

private

def each_error
object.errors.messages.each_pair do |attribute, messages|
details = if Rails::VERSION::MAJOR >= 5
object.errors.details.find { |k,v| k == attribute }[1]
end

messages.each_with_index do |message, index|
code = details[index][:error] if details
yield attribute, message, code
end
end
end

def relationship?(name)
return false unless activemodel?
relationship_names = []
if activerecord?
relationship_names = object.class
.reflect_on_all_associations.map(&:name)
elsif object.respond_to?(:relationship_names)
relationship_names = object.relationship_names
end

relation_names = object.class.reflect_on_all_associations.map(&:name)
relation_names.include?(name)
relationship_names.include?(name)
end

def attribute?(name)
object.respond_to?(name)
end

private
def meta_for(attribute, message, code, relationship_meta)
meta = {
attribute: attribute,
message: message
}
meta.merge!(code: code) if Rails::VERSION::MAJOR >= 5

unless relationship_meta.empty?
meta = {
relationship: meta.merge(relationship_meta)
}
end

meta
end

def detail_for(attribute, message)
detail = object.errors.full_message(attribute, message)
detail = message if attribute.to_s.downcase == 'base'
detail
end

# @richmolj: Keeping this to support ember-data, but I hate the concept.
def pointer_for(object, name)
if relationship?(name)
"/data/relationships/#{name}"
elsif attribute?(name)
"/data/attributes/#{name}"
elsif name == :base
nil
else
# Probably a nested relation, like post.comments
"/data/relationships/#{name}"
end
end

def activemodel?
def activerecord?
object.class.respond_to?(:reflect_on_all_associations)
end

def relationship_errors(relationship_params)
errors = []
def traverse_relationships(relationship_params)
return unless relationship_params

relationship_params.each_pair do |name, payload|
related = Array(@object.send(name))
related.each do |r|
relationship_objects = Array(@object.send(name))

relationship_objects.each do |relationship_object|
related_payload = payload
if payload.is_a?(Array)
related_payload = payload.find { |p| p[:meta][:temp_id] === r.instance_variable_get(:@_jsonapi_temp_id) || p[:meta][:id] == r.id }
else
related_payload = payload
related_payload = payload.find do |p|
temp_id = relationship_object
.instance_variable_get(:@_jsonapi_temp_id)
p[:meta][:temp_id] === temp_id ||
p[:meta][:id] == relationship_object.id.to_s
end
end
relationship_message = {
name: name,
id: r.id,
:'temp-id' => r.instance_variable_get(:@_jsonapi_temp_id)
}

errors << Validation.new(r, related_payload[:relationships], relationship_message).errors
yield name, relationship_object, related_payload
traverse_relationships(related_payload[:relationships])
end
end
end

def relationship_errors(relationship_payloads)
errors = []
traverse_relationships(relationship_payloads) do |name, model, payload|
meta = {}.tap do |hash|
hash[:name] = name
hash[:type] = payload[:meta][:jsonapi_type]
if temp_id = model.instance_variable_get(:@_jsonapi_temp_id)
hash[:'temp-id'] = temp_id
else
hash[:id] = model.id
end
end

serializer = self.class.new(model, payload[:relationships], meta)
errors |= serializer.errors
end
errors
end
Expand Down
Loading