rdstation-ruby-client 1.2.1 → 2.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/.github/ISSUE_TEMPLATE/rdsm-ruby-client-issue-template.md +49 -0
- data/.rspec +2 -0
- data/CHANGELOG.md +167 -0
- data/README.md +243 -43
- data/lib/rdstation-ruby-client.rb +6 -0
- data/lib/rdstation.rb +19 -0
- data/lib/rdstation/api_response.rb +3 -3
- data/lib/rdstation/authentication.rb +32 -3
- data/lib/rdstation/authorization.rb +24 -0
- data/lib/rdstation/builder/field.rb +70 -0
- data/lib/rdstation/client.rb +17 -74
- data/lib/rdstation/contacts.rb +21 -16
- data/lib/rdstation/error.rb +23 -15
- data/lib/rdstation/error/format.rb +21 -3
- data/lib/rdstation/error/formatter.rb +53 -7
- data/lib/rdstation/error_handler.rb +31 -26
- data/lib/rdstation/error_handler/bad_request.rb +30 -0
- data/lib/rdstation/error_handler/unauthorized.rb +17 -9
- data/lib/rdstation/events.rb +7 -19
- data/lib/rdstation/fields.rb +31 -7
- data/lib/rdstation/retryable_request.rb +35 -0
- data/lib/rdstation/version.rb +1 -1
- data/lib/rdstation/webhooks.rb +25 -17
- data/rdstation-ruby-client.gemspec +4 -1
- data/spec/lib/rdstation-ruby-client_spec.rb +1 -1
- data/spec/lib/rdstation/api_response_spec.rb +34 -0
- data/spec/lib/rdstation/authentication_spec.rb +164 -0
- data/spec/lib/rdstation/authorization_spec.rb +24 -0
- data/spec/lib/rdstation/builder/field_spec.rb +69 -0
- data/spec/lib/rdstation/client_spec.rb +37 -0
- data/spec/lib/rdstation/contacts_spec.rb +54 -41
- data/spec/lib/rdstation/error/format_spec.rb +46 -0
- data/spec/lib/rdstation/error/formatter_spec.rb +83 -0
- data/spec/lib/rdstation/error_handler/unauthorized_spec.rb +0 -29
- data/spec/lib/rdstation/error_handler_spec.rb +162 -26
- data/spec/lib/rdstation/events_spec.rb +20 -9
- data/spec/lib/rdstation/fields_spec.rb +10 -3
- data/spec/lib/rdstation/retryable_request_spec.rb +142 -0
- data/spec/lib/rdstation/webhooks_spec.rb +41 -13
- data/spec/lib/rdstation_spec.rb +18 -0
- metadata +41 -12
- data/lib/rdstation/error_handler/default.rb +0 -15
- data/lib/rdstation/error_handler/resource_not_found.rb +0 -24
- data/spec/lib/rdstation/error_handler/default_spec.rb +0 -14
- data/spec/lib/rdstation/error_handler/resource_not_found_spec.rb +0 -54
@@ -4,7 +4,10 @@ require 'httparty'
|
|
4
4
|
require 'rdstation/api_response'
|
5
5
|
|
6
6
|
# API requests
|
7
|
+
require 'rdstation'
|
8
|
+
require 'rdstation/retryable_request'
|
7
9
|
require 'rdstation/authentication'
|
10
|
+
require 'rdstation/authorization'
|
8
11
|
require 'rdstation/client'
|
9
12
|
require 'rdstation/contacts'
|
10
13
|
require 'rdstation/events'
|
@@ -14,3 +17,6 @@ require 'rdstation/webhooks'
|
|
14
17
|
# Error handling
|
15
18
|
require 'rdstation/error'
|
16
19
|
require 'rdstation/error_handler'
|
20
|
+
|
21
|
+
# Builders
|
22
|
+
require 'rdstation/builder/field'
|
data/lib/rdstation.rb
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
module RDStation
|
2
|
+
class << self
|
3
|
+
attr_accessor :configuration
|
4
|
+
|
5
|
+
def configure
|
6
|
+
self.configuration ||= Configuration.new
|
7
|
+
yield(configuration)
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Configuration
|
12
|
+
attr_accessor :client_id, :client_secret
|
13
|
+
attr_reader :access_token_refresh_callback
|
14
|
+
|
15
|
+
def on_access_token_refresh(&block)
|
16
|
+
@access_token_refresh_callback = block
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
module RDStation
|
2
2
|
module ApiResponse
|
3
3
|
def self.build(response)
|
4
|
-
|
5
|
-
|
6
|
-
RDStation::ErrorHandler.new(response).
|
4
|
+
return JSON.parse(response.body) if response.code.between?(200, 299)
|
5
|
+
|
6
|
+
RDStation::ErrorHandler.new(response).raise_error
|
7
7
|
end
|
8
8
|
end
|
9
9
|
end
|
@@ -5,10 +5,12 @@ module RDStation
|
|
5
5
|
|
6
6
|
AUTH_TOKEN_URL = 'https://api.rd.services/auth/token'.freeze
|
7
7
|
DEFAULT_HEADERS = { 'Content-Type' => 'application/json' }.freeze
|
8
|
+
REVOKE_URL = 'https://api.rd.services/auth/revoke'.freeze
|
8
9
|
|
9
|
-
def initialize(client_id, client_secret)
|
10
|
-
|
11
|
-
@
|
10
|
+
def initialize(client_id = nil, client_secret = nil)
|
11
|
+
warn_deprecation if client_id || client_secret
|
12
|
+
@client_id = client_id || RDStation.configuration&.client_id
|
13
|
+
@client_secret = client_secret || RDStation.configuration&.client_secret
|
12
14
|
end
|
13
15
|
|
14
16
|
#
|
@@ -47,8 +49,31 @@ module RDStation
|
|
47
49
|
ApiResponse.build(response)
|
48
50
|
end
|
49
51
|
|
52
|
+
def self.revoke(access_token:)
|
53
|
+
response = self.post(
|
54
|
+
REVOKE_URL,
|
55
|
+
body: revoke_body(access_token),
|
56
|
+
headers: revoke_headers(access_token)
|
57
|
+
)
|
58
|
+
ApiResponse.build(response)
|
59
|
+
end
|
60
|
+
|
50
61
|
private
|
51
62
|
|
63
|
+
def self.revoke_body(access_token)
|
64
|
+
URI.encode_www_form({
|
65
|
+
token: access_token,
|
66
|
+
token_type_hint: 'access_token'
|
67
|
+
})
|
68
|
+
end
|
69
|
+
|
70
|
+
def self.revoke_headers(access_token)
|
71
|
+
{
|
72
|
+
"Authorization" => "Bearer #{access_token}",
|
73
|
+
"Content-Type" => "application/x-www-form-urlencoded"
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
52
77
|
def post_to_auth_endpoint(params)
|
53
78
|
default_body = { client_id: @client_id, client_secret: @client_secret }
|
54
79
|
body = default_body.merge(params)
|
@@ -59,5 +84,9 @@ module RDStation
|
|
59
84
|
headers: DEFAULT_HEADERS
|
60
85
|
)
|
61
86
|
end
|
87
|
+
|
88
|
+
def warn_deprecation
|
89
|
+
warn "DEPRECATION WARNING: Providing client_id and client_secret directly to RDStation::Authentication.new is deprecated and will be removed in future versions. Use RDStation.configure instead."
|
90
|
+
end
|
62
91
|
end
|
63
92
|
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module RDStation
|
2
|
+
class Authorization
|
3
|
+
attr_reader :refresh_token
|
4
|
+
attr_accessor :access_token, :access_token_expires_in
|
5
|
+
def initialize(access_token:, refresh_token: nil, access_token_expires_in: nil)
|
6
|
+
@access_token = access_token
|
7
|
+
@refresh_token = refresh_token
|
8
|
+
@access_token_expires_in = access_token_expires_in
|
9
|
+
validate_access_token access_token
|
10
|
+
end
|
11
|
+
|
12
|
+
def headers
|
13
|
+
{ "Authorization" => "Bearer #{@access_token}", "Content-Type" => "application/json" }
|
14
|
+
end
|
15
|
+
|
16
|
+
private
|
17
|
+
|
18
|
+
def validate_access_token(access_token)
|
19
|
+
access_token_msg = ':access_token is required'
|
20
|
+
raise ArgumentError, access_token_msg unless access_token
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module RDStation
|
4
|
+
class Builder
|
5
|
+
# More info: https://developers.rdstation.com/pt-BR/reference/fields#methodPostDetails
|
6
|
+
class Field
|
7
|
+
DATA_TYPES = %w(STRING INTEGER BOOLEAN STRING[]).freeze
|
8
|
+
PRESENTATION_TYPES = %w[TEXT_INPUT TEXT_AREA URL_INPUT PHONE_INPUT
|
9
|
+
EMAIL_INPUT CHECK_BOX NUMBER_INPUT COMBO_BOX
|
10
|
+
RADIO_BUTTON MULTIPLE_CHOICE].freeze
|
11
|
+
|
12
|
+
REQUIRED_FIELDS = %w[api_identifier data_type presentation_type label name].freeze
|
13
|
+
|
14
|
+
def initialize(api_identifier)
|
15
|
+
raise 'api_identifier required' unless api_identifier
|
16
|
+
unless valid_identifier(api_identifier)
|
17
|
+
raise 'api_identifier is not in a valid format, need start with "cf_"'
|
18
|
+
end
|
19
|
+
|
20
|
+
@values = {}
|
21
|
+
@values['api_identifier'] = api_identifier
|
22
|
+
end
|
23
|
+
|
24
|
+
def data_type(data_type)
|
25
|
+
raise "Not valid data_type - #{DATA_TYPES}" unless DATA_TYPES.include? data_type
|
26
|
+
|
27
|
+
@values['data_type'] = data_type
|
28
|
+
end
|
29
|
+
|
30
|
+
def presentation_type(presentation_type)
|
31
|
+
unless PRESENTATION_TYPES.include? presentation_type
|
32
|
+
raise "Not valid presentation_type - #{PRESENTATION_TYPES}"
|
33
|
+
end
|
34
|
+
|
35
|
+
@values['presentation_type'] = presentation_type
|
36
|
+
end
|
37
|
+
|
38
|
+
def label(language, label)
|
39
|
+
@values['label'] = { language.to_s => label }
|
40
|
+
end
|
41
|
+
|
42
|
+
def name(language, name)
|
43
|
+
@values['name'] = { language.to_s => name }
|
44
|
+
end
|
45
|
+
|
46
|
+
def validation_rules(validation_rules)
|
47
|
+
@values['validation_rules'] = validation_rules
|
48
|
+
end
|
49
|
+
|
50
|
+
def valid_options(valid_options)
|
51
|
+
@values['valid_options'] = valid_options
|
52
|
+
end
|
53
|
+
|
54
|
+
def build
|
55
|
+
empty_fields = REQUIRED_FIELDS.select { |field| @values[field].nil? }
|
56
|
+
unless empty_fields.empty?
|
57
|
+
raise "Required fields are missing - #{empty_fields}"
|
58
|
+
end
|
59
|
+
|
60
|
+
@values
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def valid_identifier(api_identifier)
|
66
|
+
api_identifier.start_with? 'cf_'
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
data/lib/rdstation/client.rb
CHANGED
@@ -1,90 +1,33 @@
|
|
1
|
-
# encoding: utf-8
|
2
1
|
module RDStation
|
3
|
-
#
|
4
|
-
# Mais informações em http://ajuda.rdstation.com.br/hc/pt-br/articles/204526429-Guia-de-integra%C3%A7%C3%B5es-com-o-RD-Station
|
5
|
-
#
|
6
2
|
class Client
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
@auth_token = auth_token
|
14
|
-
end
|
15
|
-
|
16
|
-
#
|
17
|
-
# A hash do Lead pode conter os seguintes parâmetros:
|
18
|
-
# (obrigatório) :email
|
19
|
-
# :identificador
|
20
|
-
# :nome
|
21
|
-
# :empresa
|
22
|
-
# :cargo
|
23
|
-
# :telefone
|
24
|
-
# :celular
|
25
|
-
# :website
|
26
|
-
# :twitter
|
27
|
-
# :c_utmz
|
28
|
-
# :created_at
|
29
|
-
# :tags
|
30
|
-
#
|
31
|
-
# Caso algum parâmetro não seja identificado como campo padrão ou como
|
32
|
-
# campo personalizado, este parâmetro desconhecido será gravado nos
|
33
|
-
# "Detalhes do Lead".
|
34
|
-
#
|
35
|
-
def create_lead(lead_hash)
|
36
|
-
warn "DEPRECATION WARNING: create_lead has been deprecated because the version 1.x is no longer under active development and will be removed in version 2.0.0. See more details about the new version here: https://developers.rdstation.com/en/overview"
|
37
|
-
lead_hash = rdstation_token_hash.merge(lead_hash)
|
38
|
-
lead_hash = lead_hash.merge(identifier_hash) unless lead_hash.has_key?(:identificador)
|
39
|
-
post_with_body("/conversions", {:body => lead_hash})
|
40
|
-
end
|
41
|
-
alias_method :update_lead_info, :create_lead
|
42
|
-
|
43
|
-
#
|
44
|
-
# param lead:
|
45
|
-
# id ou email do Lead a ser alterado
|
46
|
-
#
|
47
|
-
# param lead_hash:
|
48
|
-
# Hash contendo:
|
49
|
-
# :lifecycle_stage
|
50
|
-
# 0 - Lead; 1 - Lead Qualificado; 2 - Cliente
|
51
|
-
# :opportunity
|
52
|
-
# true ou false
|
53
|
-
#
|
54
|
-
def change_lead(lead, lead_hash)
|
55
|
-
warn "DEPRECATION WARNING: change_lead has been deprecated because the version 1.x is no longer under active development and will be removed in version 2.0.0. See more details about the new version here: https://developers.rdstation.com/en/overview"
|
56
|
-
lead_hash = auth_token_hash.merge({:lead => lead_hash})
|
57
|
-
put_with_body("/leads/#{lead}", :body => lead_hash.to_json, :headers => {'Content-Type' => 'application/json'})
|
58
|
-
end
|
59
|
-
|
60
|
-
def change_lead_status(lead_hash)
|
61
|
-
warn "DEPRECATION WARNING: change_lead_status has been deprecated because the version 1.x is no longer under active development and will be removed in version 2.0.0. See more details about the new version here: https://developers.rdstation.com/en/overview"
|
62
|
-
post_with_body("/services/#{@auth_token}/generic", :body => lead_hash )
|
3
|
+
def initialize(access_token:, refresh_token: nil)
|
4
|
+
warn_deprecation unless refresh_token
|
5
|
+
@authorization = Authorization.new(
|
6
|
+
access_token: access_token,
|
7
|
+
refresh_token: refresh_token
|
8
|
+
)
|
63
9
|
end
|
64
10
|
|
65
|
-
|
66
|
-
|
67
|
-
"https://www.rdstation.com.br/api/1.2"
|
11
|
+
def contacts
|
12
|
+
@contacts ||= RDStation::Contacts.new(authorization: @authorization)
|
68
13
|
end
|
69
14
|
|
70
|
-
def
|
71
|
-
|
15
|
+
def events
|
16
|
+
@events ||= RDStation::Events.new(authorization: @authorization)
|
72
17
|
end
|
73
18
|
|
74
|
-
def
|
75
|
-
|
19
|
+
def fields
|
20
|
+
@fields ||= RDStation::Fields.new(authorization: @authorization)
|
76
21
|
end
|
77
22
|
|
78
|
-
def
|
79
|
-
|
23
|
+
def webhooks
|
24
|
+
@webhooks ||= RDStation::Webhooks.new(authorization: @authorization)
|
80
25
|
end
|
81
26
|
|
82
|
-
|
83
|
-
self.class.post("#{base_url}#{path}", opts)
|
84
|
-
end
|
27
|
+
private
|
85
28
|
|
86
|
-
def
|
87
|
-
|
29
|
+
def warn_deprecation
|
30
|
+
warn "DEPRECATION WARNING: Specifying refresh_token in RDStation::Client.new(access_token: 'at', refresh_token: 'rt') is optional right now, but will be mandatory in future versions. "
|
88
31
|
end
|
89
32
|
end
|
90
33
|
end
|
data/lib/rdstation/contacts.rb
CHANGED
@@ -3,9 +3,10 @@ module RDStation
|
|
3
3
|
# More info: https://developers.rdstation.com/pt-BR/reference/contacts
|
4
4
|
class Contacts
|
5
5
|
include HTTParty
|
6
|
+
include ::RDStation::RetryableRequest
|
6
7
|
|
7
|
-
def initialize(
|
8
|
-
@
|
8
|
+
def initialize(authorization:)
|
9
|
+
@authorization = authorization
|
9
10
|
end
|
10
11
|
|
11
12
|
#
|
@@ -13,13 +14,17 @@ module RDStation
|
|
13
14
|
# The unique uuid associated to each RD Station Contact.
|
14
15
|
#
|
15
16
|
def by_uuid(uuid)
|
16
|
-
|
17
|
-
|
17
|
+
retryable_request(@authorization) do |authorization|
|
18
|
+
response = self.class.get(base_url(uuid), headers: authorization.headers)
|
19
|
+
ApiResponse.build(response)
|
20
|
+
end
|
18
21
|
end
|
19
22
|
|
20
23
|
def by_email(email)
|
21
|
-
|
22
|
-
|
24
|
+
retryable_request(@authorization) do |authorization|
|
25
|
+
response = self.class.get(base_url("email:#{email}"), headers: authorization.headers)
|
26
|
+
ApiResponse.build(response)
|
27
|
+
end
|
23
28
|
end
|
24
29
|
|
25
30
|
# The Contact hash may contain the following parameters:
|
@@ -34,8 +39,10 @@ module RDStation
|
|
34
39
|
# :website
|
35
40
|
# :tags
|
36
41
|
def update(uuid, contact_hash)
|
37
|
-
|
38
|
-
|
42
|
+
retryable_request(@authorization) do |authorization|
|
43
|
+
response = self.class.patch(base_url(uuid), :body => contact_hash.to_json, :headers => authorization.headers)
|
44
|
+
ApiResponse.build(response)
|
45
|
+
end
|
39
46
|
end
|
40
47
|
|
41
48
|
#
|
@@ -47,19 +54,17 @@ module RDStation
|
|
47
54
|
# Contact data
|
48
55
|
#
|
49
56
|
def upsert(identifier, identifier_value, contact_hash)
|
50
|
-
|
51
|
-
|
52
|
-
|
57
|
+
retryable_request(@authorization) do |authorization|
|
58
|
+
path = "#{identifier}:#{identifier_value}"
|
59
|
+
response = self.class.patch(base_url(path), body: contact_hash.to_json, headers: authorization.headers)
|
60
|
+
ApiResponse.build(response)
|
61
|
+
end
|
53
62
|
end
|
54
63
|
|
55
64
|
private
|
56
65
|
|
57
|
-
def base_url(path =
|
66
|
+
def base_url(path = '')
|
58
67
|
"https://api.rd.services/platform/contacts/#{path}"
|
59
68
|
end
|
60
|
-
|
61
|
-
def required_headers
|
62
|
-
{ "Authorization" => "Bearer #{@auth_token}", "Content-Type" => "application/json" }
|
63
|
-
end
|
64
69
|
end
|
65
70
|
end
|
data/lib/rdstation/error.rb
CHANGED
@@ -1,5 +1,4 @@
|
|
1
1
|
module RDStation
|
2
|
-
|
3
2
|
class Error < StandardError
|
4
3
|
attr_reader :details, :http_status, :headers, :body
|
5
4
|
|
@@ -8,23 +7,32 @@ module RDStation
|
|
8
7
|
message = details['error_message']
|
9
8
|
raise ArgumentError, 'The details hash must contain an error message' unless message
|
10
9
|
|
11
|
-
# Those three arguments are kept only for compatibility reasons.
|
12
|
-
# They aren't needed since we can get them directly in the details hash.
|
13
|
-
# Consider removing them when update the major version.
|
14
|
-
@http_status = details['http_status']
|
15
|
-
@headers = details['headers']
|
16
|
-
@body = details['body']
|
17
|
-
|
18
10
|
super(message)
|
19
11
|
end
|
20
12
|
|
21
|
-
class
|
22
|
-
class Default < Error; end
|
23
|
-
class ExpiredAccessToken < Error; end
|
24
|
-
class ExpiredCodeGrant < Error; end
|
25
|
-
class InvalidCredentials < Error; end
|
26
|
-
class InvalidEventType < Error; end
|
27
|
-
class ResourceNotFound < Error; end
|
13
|
+
class BadRequest < Error; end
|
28
14
|
class Unauthorized < Error; end
|
15
|
+
class Forbidden < Error; end
|
16
|
+
class NotFound < Error; end
|
17
|
+
class MethodNotAllowed < Error; end
|
18
|
+
class NotAcceptable < Error; end
|
19
|
+
class Conflict < Error; end
|
20
|
+
class UnsupportedMediaType < Error; end
|
21
|
+
class UnprocessableEntity < Error; end
|
22
|
+
class InternalServerError < Error; end
|
23
|
+
class NotImplemented < Error; end
|
24
|
+
class BadGateway < Error; end
|
25
|
+
class ServiceUnavailable < Error; end
|
26
|
+
class ServerError < Error; end
|
27
|
+
class UnknownError < Error; end
|
28
|
+
|
29
|
+
# 400 - Bad Request
|
30
|
+
class ConflictingField < BadRequest; end
|
31
|
+
class InvalidEventType < BadRequest; end
|
32
|
+
|
33
|
+
# 401 - Unauthorized
|
34
|
+
class ExpiredAccessToken < Unauthorized; end
|
35
|
+
class ExpiredCodeGrant < Unauthorized; end
|
36
|
+
class InvalidCredentials < Unauthorized; end
|
29
37
|
end
|
30
38
|
end
|
@@ -1,9 +1,13 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module RDStation
|
2
4
|
class Error
|
3
5
|
class Format
|
4
|
-
FLAT_HASH = 'FLAT_HASH'
|
5
|
-
HASH_OF_ARRAYS = 'HASH_OF_ARRAYS'
|
6
|
-
ARRAY_OF_HASHES = 'ARRAY_OF_HASHES'
|
6
|
+
FLAT_HASH = 'FLAT_HASH'
|
7
|
+
HASH_OF_ARRAYS = 'HASH_OF_ARRAYS'
|
8
|
+
ARRAY_OF_HASHES = 'ARRAY_OF_HASHES'
|
9
|
+
HASH_OF_MULTIPLE_TYPES = 'HASH_OF_MULTIPLE_TYPES'
|
10
|
+
HASH_OF_HASHES = 'HASH_OF_HASHES'
|
7
11
|
|
8
12
|
def initialize(errors)
|
9
13
|
@errors = errors
|
@@ -12,6 +16,9 @@ module RDStation
|
|
12
16
|
def format
|
13
17
|
return FLAT_HASH if flat_hash?
|
14
18
|
return HASH_OF_ARRAYS if hash_of_arrays?
|
19
|
+
return HASH_OF_HASHES if hash_of_hashes?
|
20
|
+
return HASH_OF_MULTIPLE_TYPES if hash_of_multiple_types?
|
21
|
+
|
15
22
|
ARRAY_OF_HASHES
|
16
23
|
end
|
17
24
|
|
@@ -19,12 +26,23 @@ module RDStation
|
|
19
26
|
|
20
27
|
def flat_hash?
|
21
28
|
return unless @errors.is_a?(Hash)
|
29
|
+
|
22
30
|
@errors.key?('error_type')
|
23
31
|
end
|
24
32
|
|
25
33
|
def hash_of_arrays?
|
26
34
|
@errors.is_a?(Hash) && @errors.values.all? { |error| error.is_a? Array }
|
27
35
|
end
|
36
|
+
|
37
|
+
def hash_of_hashes?
|
38
|
+
@errors.is_a?(Hash) && @errors.values.all? { |error| error.is_a? Hash }
|
39
|
+
end
|
40
|
+
|
41
|
+
def hash_of_multiple_types?
|
42
|
+
@errors.is_a?(Hash) &&
|
43
|
+
@errors.values.any? { |error| error.is_a? Hash } &&
|
44
|
+
@errors.values.any? { |error| error.is_a? Array }
|
45
|
+
end
|
28
46
|
end
|
29
47
|
end
|
30
48
|
end
|