virtuous 0.0.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.
Files changed (52) hide show
  1. checksums.yaml +7 -0
  2. data/.github/workflows/test.yml +26 -0
  3. data/.gitignore +5 -0
  4. data/.reek.yml +36 -0
  5. data/.rubocop.yml +87 -0
  6. data/.ruby-version +1 -0
  7. data/.yardopts +1 -0
  8. data/CHANGELOG.md +18 -0
  9. data/Gemfile +17 -0
  10. data/LICENSE +21 -0
  11. data/README.md +54 -0
  12. data/Rakefile +24 -0
  13. data/lib/virtuous/client/contact.rb +220 -0
  14. data/lib/virtuous/client/contact_address.rb +78 -0
  15. data/lib/virtuous/client/gift.rb +394 -0
  16. data/lib/virtuous/client/gift_designation.rb +59 -0
  17. data/lib/virtuous/client/individual.rb +125 -0
  18. data/lib/virtuous/client/recurring_gift.rb +86 -0
  19. data/lib/virtuous/client.rb +272 -0
  20. data/lib/virtuous/error.rb +54 -0
  21. data/lib/virtuous/helpers/hash_helper.rb +28 -0
  22. data/lib/virtuous/helpers/string_helper.rb +31 -0
  23. data/lib/virtuous/parse_oj.rb +24 -0
  24. data/lib/virtuous/version.rb +5 -0
  25. data/lib/virtuous.rb +12 -0
  26. data/logo/virtuous.svg +1 -0
  27. data/spec/spec_helper.rb +25 -0
  28. data/spec/support/client_factory.rb +10 -0
  29. data/spec/support/fixtures/contact.json +112 -0
  30. data/spec/support/fixtures/contact_address.json +20 -0
  31. data/spec/support/fixtures/contact_addresses.json +42 -0
  32. data/spec/support/fixtures/contact_gifts.json +80 -0
  33. data/spec/support/fixtures/gift.json +55 -0
  34. data/spec/support/fixtures/gift_designation_query_options.json +2701 -0
  35. data/spec/support/fixtures/gift_designations.json +175 -0
  36. data/spec/support/fixtures/gifts.json +112 -0
  37. data/spec/support/fixtures/import.json +0 -0
  38. data/spec/support/fixtures/individual.json +46 -0
  39. data/spec/support/fixtures/recurring_gift.json +26 -0
  40. data/spec/support/fixtures_helper.rb +5 -0
  41. data/spec/support/virtuous_mock.rb +101 -0
  42. data/spec/virtuous/client_spec.rb +270 -0
  43. data/spec/virtuous/error_spec.rb +74 -0
  44. data/spec/virtuous/resources/contact_address_spec.rb +75 -0
  45. data/spec/virtuous/resources/contact_spec.rb +137 -0
  46. data/spec/virtuous/resources/gift_designation_spec.rb +70 -0
  47. data/spec/virtuous/resources/gift_spec.rb +249 -0
  48. data/spec/virtuous/resources/individual_spec.rb +95 -0
  49. data/spec/virtuous/resources/recurring_gift_spec.rb +67 -0
  50. data/spec/virtuous_spec.rb +7 -0
  51. data/virtuous.gemspec +25 -0
  52. metadata +121 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 3c435c6a389b27d1644f8fa32d0df952aa9bcb83baf152973c8e2f55b0b418a1
4
+ data.tar.gz: 13075eb9634a01b1597c6a8be58444b1ec89738efe8842b124e4e640237163a4
5
+ SHA512:
6
+ metadata.gz: 720aaf08ccabd9f7d1b7da3b07b4d028646c910c32605d85c4fe915050799eec9880b70f377674af848af13cab25fe18e92c3c6f460393499739188d5010ed29
7
+ data.tar.gz: 8b65f36c8bef728c092210f0448769b2d581d445755ca35d23eea6d1ae9e624bc9bad1602fa5edc4f0f45ac9920cf5f6c02b5877f07444366133422f9457d4bf
@@ -0,0 +1,26 @@
1
+ name: Run specs
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ jobs:
10
+ test:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - uses: actions/checkout@v2
15
+ - name: Set up Ruby
16
+ uses: ruby/setup-ruby@v1
17
+ with:
18
+ bundler-cache: true
19
+ - name: Install dependencies
20
+ run: bundle install
21
+ - name: Lint
22
+ run: |
23
+ bundle exec rubocop
24
+ bundle exec reek
25
+ - name: Run tests
26
+ run: bundle exec rspec
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ virtuous-*.gem
2
+ .env
3
+ Gemfile.lock
4
+ doc
5
+ .yardoc
data/.reek.yml ADDED
@@ -0,0 +1,36 @@
1
+ detectors:
2
+ IrresponsibleModule:
3
+ enabled: false
4
+ DataClump:
5
+ enabled: false
6
+ FeatureEnvy:
7
+ exclude:
8
+ - "Virtuous::Client#connection"
9
+ - "Virtuous::Client#unauthorized_connection"
10
+ - "Virtuous::HashHelper#self.deep_transform_keys"
11
+ - "FaradayMiddleware::ParseOj#on_complete"
12
+ NilCheck:
13
+ exclude:
14
+ - "Virtuous::Client#initialize"
15
+ UtilityFunction:
16
+ public_methods_only: true
17
+ NestedIterators:
18
+ exclude:
19
+ - "Virtuous::HashHelper#self.deep_transform_keys"
20
+ TooManyStatements:
21
+ exclude:
22
+ - "Virtuous::HashHelper#self.deep_transform_keys"
23
+ - "Virtuous::Client#connection"
24
+ - "Virtuous::Client#unauthorized_connection"
25
+ - "FaradayMiddleware::VirtuousErrorHandler#on_complete"
26
+ ControlParameter:
27
+ exclude:
28
+ - "Virtuous::Client#initialize"
29
+ NilCheck:
30
+ enabled: false
31
+ TooManyInstanceVariables:
32
+ exclude:
33
+ - "Virtuous::Client"
34
+ InstanceVariableAssumption:
35
+ exclude:
36
+ - "Virtuous::Client"
data/.rubocop.yml ADDED
@@ -0,0 +1,87 @@
1
+ # All cops
2
+
3
+ AllCops:
4
+ TargetRubyVersion: 2.7
5
+ NewCops: "enable"
6
+ Exclude:
7
+ - "bin/*"
8
+ - "vendor/**/*"
9
+
10
+ # Layout cops
11
+
12
+ Layout/LineLength:
13
+ Max: 100
14
+ # To make it possible to copy or click on URIs in the code, we allow lines
15
+ # containing a URI to be longer than Max.
16
+ AllowURI: true
17
+ URISchemes:
18
+ - http
19
+ - https
20
+
21
+ # Metrics cops
22
+
23
+ Metrics/AbcSize:
24
+ # The ABC size is a calculated magnitude, so this number can be an Integer or
25
+ # a Float.
26
+ Max: 15
27
+ Exclude:
28
+ - lib/virtuous/error.rb
29
+
30
+ Metrics/BlockLength:
31
+ CountComments: false # count full line comments?
32
+ Max: 25
33
+ Exclude:
34
+ - config/**/*
35
+ - spec/**/*
36
+
37
+ Metrics/BlockNesting:
38
+ Max: 4
39
+
40
+ Metrics/ClassLength:
41
+ CountComments: false # count full line comments?
42
+ Max: 200
43
+
44
+ # Avoid complex methods.
45
+ Metrics/CyclomaticComplexity:
46
+ Max: 6
47
+ Exclude:
48
+ - lib/virtuous/error.rb
49
+
50
+ Metrics/MethodLength:
51
+ CountComments: false # count full line comments?
52
+ Max: 24
53
+
54
+ Metrics/ModuleLength:
55
+ CountComments: false # count full line comments?
56
+ Max: 200
57
+
58
+ Metrics/ParameterLists:
59
+ Max: 5
60
+ CountKeywordArgs: true
61
+
62
+ Metrics/PerceivedComplexity:
63
+ Max: 12
64
+
65
+ # Style cops
66
+
67
+ Style/Documentation:
68
+ Enabled: false
69
+
70
+ Style/FrozenStringLiteralComment:
71
+ Enabled: false
72
+
73
+ Style/ModuleFunction:
74
+ Enabled: false
75
+
76
+ Style/SymbolArray:
77
+ Enabled: false
78
+
79
+ # Lint cops
80
+
81
+ Lint/AmbiguousBlockAssociation:
82
+ Exclude:
83
+ - spec/**/*
84
+
85
+ Naming/MemoizedInstanceVariableName:
86
+ Exclude:
87
+ - lib/virtuous/client.rb
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.2.2
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ --markup markdown
data/CHANGELOG.md ADDED
@@ -0,0 +1,18 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
11
+
12
+ - A Client class with support for api key and OAuth authentication
13
+ - Methods to find, create and update contacts
14
+ - Methods to find, create, update and delete individuals
15
+ - Methods to find, create, update and delete gifts
16
+ - Method query gift designations
17
+
18
+ [unreleased]: https://github.com/taylorbrooks/virtuous/compare/v0.0.0...HEAD
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ gemspec
6
+
7
+ gem 'bundler', '~> 2.3'
8
+ gem 'dotenv', '~> 2.8.1'
9
+ gem 'pry', '~> 0.14.2'
10
+ gem 'puma', '~> 6.4'
11
+ gem 'rake', '~> 12.3.3'
12
+ gem 'reek', '~> 6.1'
13
+ gem 'rspec', '~> 3.7'
14
+ gem 'rubocop', '~> 1.57'
15
+ gem 'sinatra', '~> 2.0'
16
+ gem 'webmock', '~> 3.1'
17
+ gem 'yard', '~> 0.9'
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Taylor Brooks
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,54 @@
1
+ <img src="./logo/virtuous.svg" width="200" />
2
+
3
+ # Virtuous Ruby Client ![example workflow](https://github.com/taylorbrooks/virtuous/actions/workflows/test.yml/badge.svg)
4
+
5
+ A Ruby wrapper for the Virtuous API
6
+
7
+ To get a general overview of Virtuous: https://virtuous.org
8
+
9
+ [RDocs](https://rubydoc.info/github/taylorbrooks/virtuous/master)
10
+
11
+ ### Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ ```ruby
16
+ # in your Gemfile
17
+ gem 'virtuous', '~> 0.0.1'
18
+
19
+ # then...
20
+ bundle install
21
+ ```
22
+
23
+ ### Usage
24
+
25
+ ```ruby
26
+ # Authenticating with username and password
27
+ client = Virtuous::Client.new
28
+ client.authenticate(username: ..., password: ...)
29
+
30
+ # Authenticating with api keys
31
+ client = Virtuous::Client.new(api_key: ...)
32
+
33
+ # Find a specific contact
34
+ client.find_contact_by_email('gob@bluthco.com')
35
+ ```
36
+
37
+ ### History
38
+
39
+ View the [changelog](https://github.com/taylorbrooks/virtuous/blob/master/CHANGELOG.md)
40
+
41
+ This gem follows [Semantic Versioning](http://semver.org/)
42
+
43
+ ### Contributing
44
+
45
+ Everyone is encouraged to help improve this project. Here are a few ways you can help:
46
+
47
+ - [Report bugs](https://github.com/taylorbrooks/virtuous/issues)
48
+ - Fix bugs and [submit pull requests](https://github.com/taylorbrooks/virtuous/pulls)
49
+ - Write, clarify, or fix documentation
50
+ - Suggest or add new features
51
+
52
+ ### Copyright
53
+
54
+ Copyright (c) 2018 Taylor Brooks. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,24 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task default: :spec
7
+
8
+ task :environment do
9
+ require 'dotenv'
10
+ Dotenv.load
11
+
12
+ $LOAD_PATH.unshift File.expand_path('lib', __dir__)
13
+ require 'virtuous'
14
+ end
15
+
16
+ desc 'Launch a pry shell with libraries loaded'
17
+ task pry: :environment do
18
+ options = {}
19
+ options[:logger] = Logger.new($stdout) unless ENV['CLIENT_LOGGER'].nil?
20
+ @client = Virtuous::Client.new(**options) if ENV['VIRTUOUS_KEY']
21
+
22
+ require 'pry'
23
+ Pry.start
24
+ end
@@ -0,0 +1,220 @@
1
+ module Virtuous
2
+ class Client
3
+ ##
4
+ # ### Contact data
5
+ #
6
+ # {
7
+ # contact_type: [String],
8
+ # reference_source: [String],
9
+ # reference_id: [String],
10
+ # name: [String],
11
+ # informal_name: [String],
12
+ # description: [String],
13
+ # website: [String],
14
+ # marital_status: [String],
15
+ # anniversary_month: [Integer],
16
+ # anniversary_day: [Integer],
17
+ # anniversary_year: [Integer],
18
+ # origin_segment_id: [Integer],
19
+ # is_private: [Boolean],
20
+ # is_archived: [Boolean],
21
+ # contact_addresses: [
22
+ # {
23
+ # label: [String],
24
+ # address1: [String],
25
+ # address2: [String],
26
+ # city: [String],
27
+ # state_code: [String],
28
+ # postal: [String],
29
+ # country_code: [String],
30
+ # is_primary: [Boolean],
31
+ # latitude: [Float],
32
+ # longitude: [Float]
33
+ # }
34
+ # ],
35
+ # contact_individuals: [
36
+ # {
37
+ # first_name: [String],
38
+ # last_name: [String],
39
+ # prefix: [String],
40
+ # middle_name: [String],
41
+ # suffix: [String],
42
+ # birth_month: [Integer],
43
+ # birth_day: [Integer],
44
+ # birth_year: [Integer],
45
+ # approximate_age: [Integer],
46
+ # gender: [String],
47
+ # passion: [String],
48
+ # is_primary: [Boolean],
49
+ # is_secondary: [Boolean],
50
+ # is_deceased: [Boolean],
51
+ # contact_methods: [
52
+ # {
53
+ # type: [String],
54
+ # value: [String],
55
+ # is_opted_in: [Boolean],
56
+ # is_primary: [Boolean]
57
+ # }
58
+ # ],
59
+ # custom_fields: [Hash]
60
+ # }
61
+ # ],
62
+ # custom_fields: [Hash],
63
+ # custom_collections: [
64
+ # {
65
+ # name: [String],
66
+ # fields: [
67
+ # {
68
+ # name: [String],
69
+ # value: [String]
70
+ # }
71
+ # ]
72
+ # }
73
+ # ]
74
+ # }
75
+ #
76
+ module Contact
77
+ ##
78
+ # Fetches a contact record by email.
79
+ #
80
+ # @example
81
+ # client.find_contact_by_email('contact@email.com')
82
+ #
83
+ # @param email [String] The email of the contact.
84
+ #
85
+ # @return [Hash] The contact information in a hash.
86
+ def find_contact_by_email(email)
87
+ parse(get('api/Contact/Find', { email: email }))
88
+ end
89
+
90
+ ##
91
+ # Fetches a contact record by id.
92
+ #
93
+ # @example
94
+ # client.get_contact(1)
95
+ #
96
+ # @param id [Integer] The id of the contact.
97
+ #
98
+ # @return [Hash] The contact information in a hash.
99
+ def get_contact(id)
100
+ parse(get("api/Contact/#{id}"))
101
+ end
102
+
103
+ ##
104
+ # Creates a contact. This will use the virtuous import tool to match the new contact
105
+ # with existing ones. If the contact record exists already but there is new
106
+ # information the record will be flagged for review.
107
+ #
108
+ # Transactions are posted to the API and are set to a holding state.
109
+ # At midnight, transactions are bundled into imports based on the source they were posted
110
+ # with.
111
+ # The organization reviews the imported transactions, and then clicks run.
112
+ #
113
+ # @example
114
+ # client.import_contact(
115
+ # contact_type: 'Organization', name: 'Org name',
116
+ # first_name: 'John', last_name: 'Doe'
117
+ # )
118
+ #
119
+ # @param data [Hash] A hash containing the contact details.
120
+ #
121
+ # #### Required fields
122
+ # - `contact_type`: "Household", "Organization", "Foundation" or a custom type.
123
+ # - `contact_name`: required if Organization or Foundation.
124
+ # - `first_name`
125
+ # - `last_name`
126
+ #
127
+ # #### Suggested fields
128
+ # - `reference_source`: the system it came from.
129
+ # - `reference_id`: the ID in the original system.
130
+ # - `email`
131
+ # - `phone`
132
+ # - `address`
133
+ #
134
+ # #### Full list of accepted fields
135
+ #
136
+ # {
137
+ # reference_source: [String],
138
+ # reference_id: [String],
139
+ # contact_type: [String],
140
+ # name: [String],
141
+ # title: [String],
142
+ # first_name: [String],
143
+ # middle_name: [String],
144
+ # last_name: [String],
145
+ # suffix: [String],
146
+ # email_type: [String],
147
+ # email: [String],
148
+ # phone_type: [String],
149
+ # phone: [String],
150
+ # address1: [String],
151
+ # address2: [String],
152
+ # city: [String],
153
+ # state: [String],
154
+ # postal: [String],
155
+ # country: [String],
156
+ # event_id: [Integer],
157
+ # event_name: [String],
158
+ # invited: [Boolean],
159
+ # rsvp: [Boolean],
160
+ # rsvp_response: [Boolean],
161
+ # attended: [Boolean],
162
+ # tags: [String],
163
+ # origin_segment_code: [String],
164
+ # email_lists: [String[]],
165
+ # custom_fields: [Hash],
166
+ # volunteer_attendances: [
167
+ # {
168
+ # volunteer_opportunity_id: [Integer],
169
+ # volunteer_opportunity_name: [String],
170
+ # date: [String],
171
+ # hours: [String]
172
+ # }
173
+ # ]
174
+ # }
175
+ #
176
+ def import_contact(data)
177
+ post('api/Contact/Transaction', format(data))
178
+ end
179
+
180
+ ##
181
+ # Creates a contact.
182
+ #
183
+ # @example
184
+ # client.create_contact(
185
+ # contact_type: 'Organization', name: 'Org name',
186
+ # contact_individuals: [
187
+ # { first_name: 'John', last_name: 'Doe' }
188
+ # ]
189
+ # )
190
+ #
191
+ # @param data [Hash] A hash containing the contact details.
192
+ # Refer to the [Contact data](#label-Contact+data) section
193
+ # above to see the available attributes.
194
+ #
195
+ # @return [Hash] The contact that has been created.
196
+ def create_contact(data)
197
+ parse(post('api/Contact', format(data)))
198
+ end
199
+
200
+ ##
201
+ # Updates a contact.
202
+ #
203
+ # @example
204
+ # client.update_contact(1, contact_type: 'Organization', name: 'New name')
205
+ #
206
+ # @note Excluding a property will remove it's value from the object.
207
+ # If you're only updating a single property, the entire model is still required.
208
+ #
209
+ # @param id [Integer] The id of the contact to update.
210
+ # @param data [Hash] A hash containing the contact details.
211
+ # Refer to the [Contact data](#label-Contact+data) section
212
+ # above to see the available attributes.
213
+ #
214
+ # @return [Hash] The contact that has been updated.
215
+ def update_contact(id, data)
216
+ parse(put("api/Contact/#{id}", format(data)))
217
+ end
218
+ end
219
+ end
220
+ end
@@ -0,0 +1,78 @@
1
+ module Virtuous
2
+ class Client
3
+ ##
4
+ # ### Contact Address data
5
+ #
6
+ # {
7
+ # contact_id: [Integer],
8
+ # label: [String],
9
+ # address1: [String],
10
+ # address2: [String],
11
+ # city: [String],
12
+ # state: [String],
13
+ # postal: [String],
14
+ # country: [String],
15
+ # set_as_primary: [Boolean],
16
+ # start_month: [Integer],
17
+ # start_day: [Integer],
18
+ # end_month: [Integer],
19
+ # end_day: [Integer]
20
+ # }
21
+ #
22
+ module ContactAddress
23
+ ##
24
+ # Gets the addresses of a contact.
25
+ #
26
+ # @example
27
+ # client.get_contact_addresses(1)
28
+ #
29
+ # @param contact_id [Integer] The id of the Contact.
30
+ #
31
+ # @return [Array] An array with all the addresses of a contact.
32
+ #
33
+ def get_contact_addresses(contact_id)
34
+ response = get("api/ContactAddress/ByContact/#{contact_id}")
35
+ response.map { |address| parse(address) }
36
+ end
37
+
38
+ ##
39
+ # Updates an address.
40
+ #
41
+ # @example
42
+ # client.update_contact_address(
43
+ # 1, label: 'Home address', address1: '324 Frank Island', address2: 'Apt. 366',
44
+ # city: 'Antonioborough', state: 'Massachusetts', postal: '27516', country: 'USA'
45
+ # )
46
+ #
47
+ # @note Excluding a property will remove it's value from the object.
48
+ # If you're only updating a single property, the entire model is still required.
49
+ #
50
+ # @param id [Integer] The id of the address to update.
51
+ # @param data [Hash] A hash containing the address details.
52
+ # Refer to the data section above to see the available attributes.
53
+ #
54
+ # @return [Hash] The address that has been updated.
55
+ def update_contact_address(id, data)
56
+ parse(put("api/ContactAddress/#{id}", format(data)))
57
+ end
58
+
59
+ ##
60
+ # Creates an address.
61
+ #
62
+ # @example
63
+ # client.create_contact_address(
64
+ # contact_id: 1, label: 'Home address', address1: '324 Frank Island',
65
+ # address2: 'Apt. 366', city: 'Antonioborough', state: 'Massachusetts', postal: '27516',
66
+ # country: 'USA'
67
+ # )
68
+ #
69
+ # @param data [Hash] A hash containing the address details.
70
+ # Refer to the data section above to see the available attributes.
71
+ #
72
+ # @return [Hash] The address that has been created.
73
+ def create_contact_address(data)
74
+ parse(post('api/ContactAddress', format(data)))
75
+ end
76
+ end
77
+ end
78
+ end