pact_broker 1.3.0 → 1.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 +5 -13
- data/.travis.yml +6 -0
- data/CHANGELOG.md +6 -0
- data/README.md +3 -1
- data/lib/pact_broker/api/contracts/consumer_version_number_validation.rb +32 -0
- data/lib/pact_broker/api/contracts/pacticipant_name_contract.rb +27 -0
- data/lib/pact_broker/api/contracts/pacticipant_name_validation.rb +30 -0
- data/lib/pact_broker/api/contracts/put_pact_params_contract.rb +55 -0
- data/lib/pact_broker/api/contracts/request_validations.rb +40 -0
- data/lib/pact_broker/api/contracts/webhook_contract.rb +32 -0
- data/lib/pact_broker/api/pact_broker_urls.rb +7 -0
- data/lib/pact_broker/api/resources/base_resource.rb +9 -0
- data/lib/pact_broker/api/resources/pact.rb +10 -3
- data/lib/pact_broker/api/resources/pact_webhooks.rb +9 -0
- data/lib/pact_broker/constants.rb +5 -0
- data/lib/pact_broker/doc/views/pacticipants.markdown +2 -2
- data/lib/pact_broker/locale/en.yml +6 -0
- data/lib/pact_broker/messages.rb +4 -0
- data/lib/pact_broker/models/webhook.rb +10 -8
- data/lib/pact_broker/models/webhook_request.rb +5 -17
- data/lib/pact_broker/pacts/pact_params.rb +79 -0
- data/lib/pact_broker/services/webhook_service.rb +6 -0
- data/lib/pact_broker/version.rb +1 -1
- data/pact_broker.gemspec +4 -3
- data/spec/fixtures/webhook_valid.json +14 -0
- data/spec/integration/endpoints/pact_put_spec.rb +16 -0
- data/spec/integration/endpoints/pact_webhooks_spec.rb +96 -0
- data/spec/lib/pact_broker/api/contracts/put_pact_params_contract_spec.rb +122 -0
- data/spec/lib/pact_broker/api/contracts/webhook_contract_spec.rb +106 -0
- data/spec/lib/pact_broker/api/resources/pact_spec.rb +15 -1
- data/spec/lib/pact_broker/api/resources/pact_webhooks_spec.rb +11 -8
- data/spec/lib/pact_broker/models/webhook_request_spec.rb +0 -31
- data/spec/lib/pact_broker/models/webhook_spec.rb +0 -21
- data/spec/lib/pact_broker/pacts/pact_params_spec.rb +69 -0
- data/spec/support/shared_examples_for_responses.rb +14 -0
- metadata +88 -55
checksums.yaml
CHANGED
@@ -1,15 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
|
5
|
-
data.tar.gz: !binary |-
|
6
|
-
N2QwYjkzYzk0YWQ1NjFkODVkZjIyOGJiYThhMzEwYzRlNGIxM2RkNw==
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c2efe6a0f09d53fcd3eda935e760ffdddf06c39c
|
4
|
+
data.tar.gz: 35000029a9b755d9c4f14be9ef900864dcb50a51
|
7
5
|
SHA512:
|
8
|
-
metadata.gz:
|
9
|
-
|
10
|
-
MmE3Yzc1NGVmZDA0YmQ2YjJlYjZhODIwODAxOGRhYTgxZGI1MTZmODRlOGY5
|
11
|
-
ZGYyMDRiNzBkN2MzMmIxOWI4ZTI4MDM1MDIwNmFiNTU5N2I0ZTk=
|
12
|
-
data.tar.gz: !binary |-
|
13
|
-
MDNkNzJkNjgwYzI2ZjdiOGM2NTNhZmJiMDBmNWZjNWVjZTVmM2VlYTQyOTI4
|
14
|
-
NzEwN2JjYzUxYWViMzJiZGI1NzQwYTkyNTJjZTM2NGI1Y2FmNjE4OWVhYTUx
|
15
|
-
NTA1NDgyYjYxZDQyNDE4NGEyYWY3Yzc5M2VlYWJiYjBjNjRiYmY=
|
6
|
+
metadata.gz: f5852a4d2cd9bedd28fc5dff28da6d6464d9936a1cb9e693956364d7c6a0c421b364af5be6d3ff229dd24ac36462b8af530ae01209f433dd971574cd87691e8a
|
7
|
+
data.tar.gz: a2224dbd381b2313175752bc002c2d118363c90fdd9a08b54f20d6a868dd1599c334c3574fdeac5fc1c6cf3fff99b46c226695ee4bb7a4d2f6a3e24f29430b57
|
data/.travis.yml
ADDED
data/CHANGELOG.md
CHANGED
@@ -2,6 +2,12 @@ Do this to generate your change history
|
|
2
2
|
|
3
3
|
$ git log --pretty=format:' * %h - %s (%an, %ad)'
|
4
4
|
|
5
|
+
#### 1.3.1 (2014-10-23)
|
6
|
+
|
7
|
+
* e61b40e - Added Travis configuration. (Beth, Fri Oct 17 16:32:26 2014 +1100)
|
8
|
+
* b320fe4 - Fixed pact publish validation for ruby 1.9.3 (Beth, Fri Oct 17 16:31:41 2014 +1100)
|
9
|
+
* b9b4d2b - Added validation to ensure that the participant names in the path match the participant names in the pact. (Beth, Thu Oct 16 20:21:10 2014 +1100)
|
10
|
+
|
5
11
|
#### 1.3.0 (2014-10-14)
|
6
12
|
|
7
13
|
* ed08811 - Converted raw SQL create view statements to Sequel so they will run on Postgres (Beth, Sat Oct 11 22:07:37 2014 +1100)
|
data/README.md
CHANGED
@@ -2,6 +2,8 @@
|
|
2
2
|
|
3
3
|
The Pact Broker provides a repository for pacts created using the pact gem. It solves the problem of how to share pacts between consumer and provider projects.
|
4
4
|
|
5
|
+
Travis CI Status: [](https://travis-ci.org/bethesque/pact_broker)
|
6
|
+
|
5
7
|
The Pact Broker:
|
6
8
|
|
7
9
|
* Enables pacts to be shared between consumer and provider projects.
|
@@ -18,7 +20,7 @@ See the [wiki](https://github.com/bethesque/pact_broker/wiki) for documentation.
|
|
18
20
|
|
19
21
|
## Usage
|
20
22
|
|
21
|
-
* Create a database using a product that is supported by the Sequel gem (listed on this page http://sequel.jeremyevans.net/rdoc/files/README_rdoc.html). At time of writing, Sequel has adapters for: ADO, Amalgalite, CUBRID, DataObjects, DB2, DBI, Firebird, IBM_DB, Informix, JDBC, MySQL, Mysql2, ODBC, OpenBase, Oracle, PostgreSQL, SQLAnywhere, SQLite3, Swift, and TinyTDS.
|
23
|
+
* Create a database using a product that is supported by the Sequel gem (listed on this page http://sequel.jeremyevans.net/rdoc/files/README_rdoc.html). At time of writing, Sequel has adapters for: ADO, Amalgalite, CUBRID, DataObjects, DB2, DBI, Firebird, IBM_DB, Informix, JDBC, MySQL, Mysql2, ODBC, OpenBase, Oracle, PostgreSQL, SQLAnywhere, SQLite3, Swift, and TinyTDS.
|
22
24
|
* __Note:__ It is recommended to use __PostgreSQL__ as it will support JSON search features that are planned in a future release.
|
23
25
|
* Install ruby 1.9.3 or later
|
24
26
|
* Copy the [example](/example) directory to your workstation.
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module PactBroker
|
2
|
+
module Api
|
3
|
+
module Contracts
|
4
|
+
module ConsumerVersionNumberValidation
|
5
|
+
|
6
|
+
include PactBroker::Messages
|
7
|
+
|
8
|
+
def consumer_version_number_present
|
9
|
+
unless consumer_version_number
|
10
|
+
errors.add(:base, validation_message('consumer_version_number_missing'))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def consumer_version_number_valid
|
15
|
+
if consumer_version_number && invalid_consumer_version_number?
|
16
|
+
errors.add(:base, consumer_version_number_validation_message)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def invalid_consumer_version_number?
|
21
|
+
begin
|
22
|
+
Versionomy.parse(consumer_version_number)
|
23
|
+
false
|
24
|
+
rescue Versionomy::Errors::ParseError => e
|
25
|
+
true
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
require 'reform'
|
2
|
+
require 'reform/contract'
|
3
|
+
|
4
|
+
module PactBroker
|
5
|
+
module Api
|
6
|
+
module Contracts
|
7
|
+
|
8
|
+
class PacticipantNameContract < Reform::Contract
|
9
|
+
property :name
|
10
|
+
property :name_in_pact
|
11
|
+
property :pacticipant
|
12
|
+
property :message_args
|
13
|
+
|
14
|
+
|
15
|
+
include PactBroker::Messages
|
16
|
+
|
17
|
+
def blank? string
|
18
|
+
string && string.strip.empty?
|
19
|
+
end
|
20
|
+
|
21
|
+
def empty? string
|
22
|
+
string.nil? || blank?(string)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module PactBroker
|
2
|
+
module Api
|
3
|
+
module Contracts
|
4
|
+
module PacticipantNameValidation
|
5
|
+
|
6
|
+
include PactBroker::Messages
|
7
|
+
|
8
|
+
def name_in_pact_present
|
9
|
+
unless name_in_pact
|
10
|
+
errors.add(:'name', validation_message('pact_missing_pacticipant_name', pacticipant: pacticipant))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def name_not_blank
|
15
|
+
if blank? name
|
16
|
+
errors.add(:'name', validation_message('blank'))
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def blank? string
|
21
|
+
string && string.strip.empty?
|
22
|
+
end
|
23
|
+
|
24
|
+
def empty? string
|
25
|
+
string.nil? || blank?(string)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,55 @@
|
|
1
|
+
require 'reform'
|
2
|
+
require 'reform/contract'
|
3
|
+
require 'versionomy'
|
4
|
+
require 'pact_broker/messages'
|
5
|
+
require 'pact_broker/constants'
|
6
|
+
require 'pact_broker/api/contracts/pacticipant_name_contract'
|
7
|
+
require 'pact_broker/api/contracts/consumer_version_number_validation'
|
8
|
+
|
9
|
+
module PactBroker
|
10
|
+
module Api
|
11
|
+
module Contracts
|
12
|
+
|
13
|
+
class PutPacticipantNameContract < PacticipantNameContract
|
14
|
+
|
15
|
+
validates :name, presence: true, blank: false
|
16
|
+
validate :name_in_path_matches_name_in_pact
|
17
|
+
|
18
|
+
def name_in_path_matches_name_in_pact
|
19
|
+
if present?(name) && present?(name_in_pact)
|
20
|
+
if name != name_in_pact
|
21
|
+
errors.add(:name, validation_message('pacticipant_name_mismatch', message_args))
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def present? string
|
27
|
+
string && !blank?(string)
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
class PutPactParamsContract < Reform::Contract
|
33
|
+
|
34
|
+
include PactBroker::Messages
|
35
|
+
|
36
|
+
property :consumer_version_number
|
37
|
+
property :consumer, form: PutPacticipantNameContract
|
38
|
+
property :provider, form: PutPacticipantNameContract
|
39
|
+
|
40
|
+
validates :consumer_version_number, presence: true
|
41
|
+
validate :consumer_version_number_valid
|
42
|
+
|
43
|
+
|
44
|
+
include ConsumerVersionNumberValidation
|
45
|
+
|
46
|
+
def consumer_version_number_validation_message
|
47
|
+
validation_message('consumer_version_number_invalid', consumer_version_number: consumer_version_number)
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'reform'
|
2
|
+
require 'reform/contract'
|
3
|
+
require 'net/http'
|
4
|
+
|
5
|
+
module PactBroker
|
6
|
+
module Api
|
7
|
+
module Contracts
|
8
|
+
|
9
|
+
module RequestValidations
|
10
|
+
def method_is_valid
|
11
|
+
if http_method && !valid_method?
|
12
|
+
errors.add(:method, "is not a recognised HTTP method")
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def valid_method?
|
17
|
+
Net::HTTP.const_defined?(http_method.capitalize)
|
18
|
+
end
|
19
|
+
|
20
|
+
def url_is_valid
|
21
|
+
if url && !url_valid?
|
22
|
+
errors.add(:url, "is not a valid URL eg. http://example.org")
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def url_valid?
|
27
|
+
uri && uri.scheme && uri.host
|
28
|
+
end
|
29
|
+
|
30
|
+
def uri
|
31
|
+
begin
|
32
|
+
URI(url)
|
33
|
+
rescue URI::InvalidURIError
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'reform'
|
2
|
+
require 'reform/contract'
|
3
|
+
require 'pact_broker/api/contracts/request_validations'
|
4
|
+
|
5
|
+
module PactBroker
|
6
|
+
module Api
|
7
|
+
module Contracts
|
8
|
+
|
9
|
+
class WebhookContract < Reform::Contract
|
10
|
+
|
11
|
+
property :request
|
12
|
+
validates :request, presence: true
|
13
|
+
|
14
|
+
property :request do
|
15
|
+
|
16
|
+
include RequestValidations
|
17
|
+
|
18
|
+
property :url
|
19
|
+
property :http_method
|
20
|
+
|
21
|
+
validates :url, presence: true
|
22
|
+
validates :http_method, presence: true
|
23
|
+
|
24
|
+
validate :method_is_valid
|
25
|
+
validate :url_is_valid
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -29,6 +29,13 @@ module PactBroker
|
|
29
29
|
"#{pactigration_base_url(base_url, representable_pact)}/version/#{representable_pact.consumer.version.number}"
|
30
30
|
end
|
31
31
|
|
32
|
+
def pact_url_from_params base_url, params
|
33
|
+
[ base_url, 'pacts',
|
34
|
+
'provider', url_encode(params[:provider_name]),
|
35
|
+
'consumer', url_encode(params[:consumer_name]),
|
36
|
+
'version', url_encode(params[:consumer_version_number]) ].join('/')
|
37
|
+
end
|
38
|
+
|
32
39
|
def latest_pact_url base_url, pact
|
33
40
|
"#{pactigration_base_url(base_url, pact)}/latest"
|
34
41
|
end
|
@@ -36,6 +36,8 @@ module PactBroker
|
|
36
36
|
end
|
37
37
|
end
|
38
38
|
|
39
|
+
alias_method :path_info, :identifier_from_path
|
40
|
+
|
39
41
|
def base_url
|
40
42
|
request.uri.to_s.gsub(/#{request.uri.path}$/,'')
|
41
43
|
end
|
@@ -99,6 +101,13 @@ module PactBroker
|
|
99
101
|
end
|
100
102
|
end
|
101
103
|
|
104
|
+
def contract_validation_errors? contract
|
105
|
+
if (invalid = !contract.validate)
|
106
|
+
set_json_validation_error_messages contract.errors.full_messages
|
107
|
+
end
|
108
|
+
invalid
|
109
|
+
end
|
110
|
+
|
102
111
|
end
|
103
112
|
end
|
104
113
|
end
|
@@ -3,6 +3,8 @@ require 'pact_broker/api/resources/base_resource'
|
|
3
3
|
require 'pact_broker/api/resources/pacticipant_resource_methods'
|
4
4
|
require 'pact_broker/api/decorators/pact_decorator'
|
5
5
|
require 'pact_broker/json'
|
6
|
+
require 'pact_broker/pacts/pact_params'
|
7
|
+
require 'pact_broker/api/contracts/put_pact_params_contract'
|
6
8
|
|
7
9
|
module PactBroker
|
8
10
|
|
@@ -28,7 +30,8 @@ module PactBroker
|
|
28
30
|
def malformed_request?
|
29
31
|
if request.put?
|
30
32
|
return invalid_json? ||
|
31
|
-
|
33
|
+
contract_validation_errors?(Contracts::PutPactParamsContract.new(pact_params)) ||
|
34
|
+
potential_duplicate_pacticipants?(pact_params.pacticipant_names)
|
32
35
|
else
|
33
36
|
false
|
34
37
|
end
|
@@ -40,7 +43,7 @@ module PactBroker
|
|
40
43
|
|
41
44
|
def from_json
|
42
45
|
response_code = pact ? 200 : 201
|
43
|
-
@pact = pact_service.create_or_update_pact(
|
46
|
+
@pact = pact_service.create_or_update_pact(pact_params)
|
44
47
|
response.body = to_json
|
45
48
|
response_code
|
46
49
|
end
|
@@ -50,7 +53,11 @@ module PactBroker
|
|
50
53
|
end
|
51
54
|
|
52
55
|
def pact
|
53
|
-
@pact ||= pact_service.find_pact(
|
56
|
+
@pact ||= pact_service.find_pact(pact_params)
|
57
|
+
end
|
58
|
+
|
59
|
+
def pact_params
|
60
|
+
@pact_params ||= PactBroker::Pacts::PactParams.from_request request, path_info
|
54
61
|
end
|
55
62
|
|
56
63
|
end
|
@@ -2,6 +2,7 @@
|
|
2
2
|
require 'pact_broker/api/resources/base_resource'
|
3
3
|
require 'pact_broker/api/decorators/webhook_decorator'
|
4
4
|
require 'pact_broker/api/decorators/webhooks_decorator'
|
5
|
+
require 'pact_broker/api/contracts/webhook_contract'
|
5
6
|
|
6
7
|
module PactBroker
|
7
8
|
|
@@ -33,6 +34,14 @@ module PactBroker
|
|
33
34
|
false
|
34
35
|
end
|
35
36
|
|
37
|
+
def validation_errors? webhook
|
38
|
+
if (errors = webhook_service.errors(webhook)).any?
|
39
|
+
response.headers['Content-Type'] = 'application/json'
|
40
|
+
response.body = {errors: errors.full_messages }.to_json
|
41
|
+
end
|
42
|
+
errors.any?
|
43
|
+
end
|
44
|
+
|
36
45
|
def create_path
|
37
46
|
webhook_url next_uuid, base_url
|
38
47
|
end
|
@@ -2,9 +2,9 @@
|
|
2
2
|
|
3
3
|
"Pacticipant" - a party that participates in a pact (ie. a Consumer or a Provider).
|
4
4
|
|
5
|
-
### Creating
|
5
|
+
### Creating pacticipants
|
6
6
|
Participants are created automatically when a pact is published to the pact broker. The name is based on the URL compontents used to publish the pact (ie. /pacts/provider/$PROVIDER\_NAME/consumer/$CONSUMER\_NAME/version/$CONSUMER\_VERSION), not on the contents of the pact, as the Pact Broker is designed to be agnostic of the actual pact format as much as possible.
|
7
7
|
|
8
8
|
|
9
9
|
### Deleting pacticipants
|
10
|
-
Deleting a pacticipant will delete all associated pacts, versions, tags and webhooks. To delete a pacticipant, send a DELETE request to the relevant pacticipant URL via the HAL browser.
|
10
|
+
Deleting a pacticipant will delete all associated pacts, versions, tags and webhooks. To delete a pacticipant, send a DELETE request to the relevant pacticipant URL via the HAL browser.
|
@@ -2,9 +2,15 @@ en:
|
|
2
2
|
pact_broker:
|
3
3
|
errors:
|
4
4
|
validation:
|
5
|
+
blank: "cannot be blank."
|
5
6
|
attribute_missing: "Missing required attribute '%{attribute}'"
|
6
7
|
invalid_http_method: "Invalid HTTP method '%{method}'"
|
7
8
|
invalid_url: "Invalid URL '%{url}'. Expected format: http://example.org"
|
9
|
+
pact_missing_pacticipant_name: "was not found at expected path $.%{pacticipant}.name in the submitted pact file."
|
10
|
+
consumer_version_number_missing: "Please specify the consumer version number by setting the X-Pact-Consumer-Version header."
|
11
|
+
consumer_version_number_header_invalid: "X-Pact-Consumer-Version '%{consumer_version_number}' is not recognised as a standard semantic version. eg. 1.3.0 or 2.0.4.rc1"
|
12
|
+
consumer_version_number_invalid: "Consumer version number '%{consumer_version_number}' is not recognised as a standard semantic version. eg. 1.3.0 or 2.0.4.rc1"
|
13
|
+
pacticipant_name_mismatch: "in pact ('%{name_in_pact}') does not match %{pacticipant} name in path ('%{name}')."
|
8
14
|
duplicate_pacticipant: |
|
9
15
|
This is the first time a pact has been published for "%{new_name}".
|
10
16
|
The name "%{new_name}" is very similar to the following existing consumers/providers:
|
data/lib/pact_broker/messages.rb
CHANGED
@@ -19,6 +19,10 @@ module PactBroker
|
|
19
19
|
::I18n.t(key, options.merge(:scope => :pact_broker))
|
20
20
|
end
|
21
21
|
|
22
|
+
def validation_message key, options = {}
|
23
|
+
message('errors.validation.' + key, options)
|
24
|
+
end
|
25
|
+
|
22
26
|
def potential_duplicate_pacticipant_message new_name, potential_duplicate_pacticipants, base_url
|
23
27
|
existing_names = potential_duplicate_pacticipants.
|
24
28
|
collect{ | p | "* #{p.name}" }.join("\n")
|
@@ -1,6 +1,7 @@
|
|
1
1
|
require 'pact_broker/models/webhook_request'
|
2
2
|
require 'pact_broker/messages'
|
3
3
|
require 'pact_broker/logging'
|
4
|
+
require 'pact_broker/api/contracts/webhook_contract'
|
4
5
|
|
5
6
|
module PactBroker
|
6
7
|
|
@@ -22,13 +23,6 @@ module PactBroker
|
|
22
23
|
@updated_at = attributes[:updated_at]
|
23
24
|
end
|
24
25
|
|
25
|
-
def validate
|
26
|
-
messages = []
|
27
|
-
messages << message('errors.validation.attribute_missing', attribute: 'request') unless request
|
28
|
-
messages.concat request.validate if request
|
29
|
-
messages
|
30
|
-
end
|
31
|
-
|
32
26
|
def description
|
33
27
|
"A webhook for the pact between #{consumer.name} and #{provider.name}"
|
34
28
|
end
|
@@ -44,7 +38,15 @@ module PactBroker
|
|
44
38
|
end
|
45
39
|
|
46
40
|
def to_s
|
47
|
-
"webhook for consumer=#{
|
41
|
+
"webhook for consumer=#{consumer_name} provider=#{provider_name} uuid=#{uuid} request=#{request}"
|
42
|
+
end
|
43
|
+
|
44
|
+
def consumer_name
|
45
|
+
consumer && consumer.name
|
46
|
+
end
|
47
|
+
|
48
|
+
def provider_name
|
49
|
+
provider && provider.name
|
48
50
|
end
|
49
51
|
end
|
50
52
|
|