amorail 0.5.0 → 0.7.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/rspec.yml +23 -0
  3. data/.gitignore +2 -1
  4. data/CHANGELOG.md +27 -0
  5. data/README.md +49 -8
  6. data/RELEASING.md +43 -0
  7. data/amorail.gemspec +5 -3
  8. data/lib/amorail/access_token.rb +44 -0
  9. data/lib/amorail/client.rb +84 -23
  10. data/lib/amorail/config.rb +14 -8
  11. data/lib/amorail/entities/company.rb +2 -0
  12. data/lib/amorail/entities/contact.rb +2 -0
  13. data/lib/amorail/entities/contact_link.rb +2 -0
  14. data/lib/amorail/entities/elementable.rb +4 -2
  15. data/lib/amorail/entities/lead.rb +2 -0
  16. data/lib/amorail/entities/leadable.rb +2 -0
  17. data/lib/amorail/entities/note.rb +2 -0
  18. data/lib/amorail/entities/task.rb +2 -0
  19. data/lib/amorail/entities/webhook.rb +2 -0
  20. data/lib/amorail/entity/finders.rb +2 -0
  21. data/lib/amorail/entity/params.rb +13 -3
  22. data/lib/amorail/entity/persistence.rb +2 -0
  23. data/lib/amorail/entity.rb +20 -3
  24. data/lib/amorail/exceptions.rb +2 -0
  25. data/lib/amorail/property.rb +5 -3
  26. data/lib/amorail/railtie.rb +2 -0
  27. data/lib/amorail/store_adapters/abstract_store_adapter.rb +23 -0
  28. data/lib/amorail/store_adapters/memory_store_adapter.rb +50 -0
  29. data/lib/amorail/store_adapters/redis_store_adapter.rb +83 -0
  30. data/lib/amorail/store_adapters.rb +15 -0
  31. data/lib/amorail/version.rb +3 -1
  32. data/lib/amorail.rb +27 -6
  33. data/lib/tasks/amorail.rake +2 -0
  34. data/spec/access_token_spec.rb +59 -0
  35. data/spec/client_spec.rb +36 -24
  36. data/spec/company_spec.rb +2 -0
  37. data/spec/contact_link_spec.rb +2 -0
  38. data/spec/contact_spec.rb +4 -0
  39. data/spec/entity_spec.rb +2 -0
  40. data/spec/fixtures/amorail_test.yml +5 -3
  41. data/spec/fixtures/authorize.json +6 -0
  42. data/spec/fixtures/contacts/my_contact_find.json +4 -0
  43. data/spec/helpers/webmock_helpers.rb +50 -13
  44. data/spec/lead_spec.rb +2 -0
  45. data/spec/my_contact_spec.rb +5 -2
  46. data/spec/note_spec.rb +2 -0
  47. data/spec/property_spec.rb +2 -0
  48. data/spec/spec_helper.rb +2 -0
  49. data/spec/store_adapters/memory_store_adapter_spec.rb +56 -0
  50. data/spec/store_adapters/redis_store_adapter_spec.rb +67 -0
  51. data/spec/support/elementable_example.rb +2 -0
  52. data/spec/support/entity_class_example.rb +2 -0
  53. data/spec/support/leadable_example.rb +2 -0
  54. data/spec/support/my_contact.rb +3 -0
  55. data/spec/support/my_entity.rb +2 -0
  56. data/spec/task_spec.rb +2 -0
  57. data/spec/webhook_spec.rb +2 -0
  58. metadata +39 -17
  59. data/.travis.yml +0 -9
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f65fb80eb5f426c50e32fa474252e764e42e3887cddbf7f735ad217ad98560cf
4
- data.tar.gz: 5f44b5e558be01b346ad68d4d84c903deec88e05514430531b6aa0b6e02c3e73
3
+ metadata.gz: bbc85a53892ba746e91fb6b52a9d86dedfa4c2885bc550760797cea6b6c1077a
4
+ data.tar.gz: ef947d63cdb4556d3d37934bf7ff6a4298c529b3692ee2617b207737f460b9ad
5
5
  SHA512:
6
- metadata.gz: 14958c70d64dda63dd82c334d08b2c63cbe8183fbffc8b5c5a63893e42280b0439e8eef4411b9b222c52e54d7dfa0949e82061de6feb4b3814ad169d5ab37cc6
7
- data.tar.gz: a370262be82cdde926d639976fea7e23833a6f999cff3100c2a5df57e9962c8ea05b5e6b682e4d2738b4f322a3cba73d81195e721eefe017e8717c103a665f2e
6
+ metadata.gz: 92801120cb72d28e30df5d41ee23cf2f89681eec639f6837d75302204ab0b49d0b1bfffdd2ea544fad88a6e57939b2bfc8ddd1305c439ab4ff4c31b8206f998c
7
+ data.tar.gz: 532f646b965c0f92f7a4e6174932a97e1c0fb023fbfa4d4b2fea1d3d348361e3885ef671fbd0e165c00c274c136fe6d39dc628904e6487cd447375e576782db7
@@ -0,0 +1,23 @@
1
+ name: Build
2
+
3
+ on:
4
+ push:
5
+ branches:
6
+ - master
7
+ pull_request:
8
+
9
+ jobs:
10
+ build:
11
+
12
+ runs-on: ubuntu-latest
13
+
14
+ steps:
15
+ - uses: actions/checkout@v2
16
+ - name: Set up Ruby 2.5
17
+ uses: ruby/setup-ruby@v1
18
+ with:
19
+ ruby-version: 2.5
20
+ bundler-cache: true
21
+ bundler: 1.13.6
22
+ - name: Build and test
23
+ run: bundle exec rspec
data/.gitignore CHANGED
@@ -12,4 +12,5 @@
12
12
  *.o
13
13
  *.a
14
14
  mkmf.log
15
- *.gem
15
+ *.gem
16
+ .idea
data/CHANGELOG.md ADDED
@@ -0,0 +1,27 @@
1
+ # Change log
2
+
3
+ ## master (unreleased)
4
+
5
+ ## 0.7.1 (2021-09-25)
6
+
7
+ ### Features
8
+
9
+ - Adds multiple option to property ([@lHydra][])
10
+
11
+ See spec/support/my_contact.rb and spec/my_contact_spec.rb
12
+
13
+ ## 0.7.0 (2021-07-16)
14
+
15
+ ### Features
16
+
17
+ - Introduce [#53](https://github.com/teachbase/amorail/issues/48) Implement OAuth authentication ([@lHydra][])
18
+
19
+ ### Changes
20
+
21
+ - increased ruby version to >= 2.5.0
22
+ - updates dependencies versions
23
+
24
+
25
+ [@palkan]: https://github.com/palkan
26
+ [@AlexanderShvaykin]: https://github.com/AlexanderShvaykin
27
+ [@lHydra]: https://github.com/lHydra
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![Gem Version](https://badge.fury.io/rb/amorail.svg)](https://rubygems.org/gems/amorail) [![Build Status](https://travis-ci.org/teachbase/amorail.svg?branch=master)](https://travis-ci.org/teachbase/amorail)
1
+ [![Gem Version](https://badge.fury.io/rb/amorail.svg)](https://rubygems.org/gems/amorail) ![Build](https://github.com/teachbase/amorail/workflows/Build/badge.svg)
2
2
 
3
3
  # Amorail
4
4
 
@@ -25,12 +25,48 @@ Or install it yourself as:
25
25
  With Amorail you can manipulate the following AmoCRM entities: Companies, Contacts, Leads and Tasks.
26
26
  We're triying to build simple AR-like interface.
27
27
 
28
+ ### Store configuration
29
+
30
+ In order to configure a token store you should set up a store adapter in a following way: `Amorail.token_store = :redis, { redis_url: 'redis://127.0.0.1:6379/0' }` (options can be omitted). Currently supported stores are `:redis` and `:memory`. Memory adapter is used **by default**.
31
+
32
+ Here is a default configuration for Redis:
33
+
34
+ ```ruby
35
+ Amorail.token_store = :redis, {
36
+ redis_host: "127.0.0.1",
37
+ redis_port: "6379",
38
+ redis_db_name: "0"
39
+ }
40
+ ```
41
+
42
+ You can also provide a Redis URL instead:
43
+
44
+ ```ruby
45
+ Amorail.token_store = :redis, { redis_url: "redis://localhost:6397" }
46
+ ```
47
+
48
+ **NOTE**: if `REDIS_URL` environment variable is set it is used automatically.
49
+
50
+ ### Add custom store
51
+
52
+ To add custom store you need declare a class that implements the interface `AbstractStoreAdapter`.
53
+ For example `class FileStoreAdapter < Amorail::StoreAdapters::AbstractStoreAdapter`
54
+
55
+ The class must contain constructor `initialize(**options)` and **4 required methods**:
56
+
57
+ 1. `fetch_access` — method that should return Hash with token data (**required keys:** `token`, `refresh_token` and `expiration`) or empty Hash (`{}`) if no value was received
58
+ 2. `persist_access` — method that stores data in storage
59
+ 3. `update_access` — method that updates existed token data in storage
60
+ 4. `access_expired?` — method that returns `true` if token was expired otherwise `false`
61
+
28
62
  ### Auth configuration
29
63
 
30
64
  Amorail uses [anyway_config](https://github.com/palkan/anyway_config) for configuration, so you
31
65
  can provide configuration parameters through env vars, seperate config file (`config/amorail.yml`) or `secrets.yml`.
32
66
 
33
- Required params: **usermail**, **api_key** and **api_endpoint**.
67
+ Required params: **client_id**, **client_secret** **code**, **redirect_uri** and **api_endpoint**.
68
+
69
+ An authorization **code** is required for the initial obtaining of a pair of access and refresh tokens. You can see it in the interface or through a Redirect URI if the authorization was run from the modal window for permissions. The lifespan of the code is 20 minutes. [More details](https://www.amocrm.com/developers/content/oauth/oauth/)
34
70
 
35
71
  Example:
36
72
 
@@ -39,9 +75,14 @@ Example:
39
75
  development:
40
76
  ...
41
77
  amorail:
42
- usermail: 'amorail@test.com'
43
- api_key: '75742b166417fe32ae132282ce178cf6'
44
- api_endpoint: 'https://test.amocrm.ru'
78
+ client_id: c0df457d-eacc-47cc-behb-3d8f962g4lbf
79
+ client_secret: a36b564b64398d3e53004c12e4997eb340e32b18ee185389ddb409292ebc5ebae297a3eab96be4a9d38ecbf274d90bbb54a7e8f282f40d1b29e5c9b2e2e357a6
80
+ code: a911ff963f58ea6c846901056114d37a14d2efa4d05ffb6ef0a8d60d32e5d6dae785bd317cbc9b0bd04261cb0cf9905af0cc32b5567c1eb84433328d08888f5c613608b822c1928272769ffd284b
81
+ redirect_uri: https://example.ru
82
+ api_endpoint: https://test.amocrm.ru
83
+ redis_host: 127.0.0.1
84
+ redis_port: 6379
85
+ redis_db_name: 0
45
86
  ```
46
87
 
47
88
  ### Running from console
@@ -50,7 +91,7 @@ You can try amorail in action from console ([PRY](https://github.com/pry/pry) is
50
91
 
51
92
  ```shell
52
93
  # amorail gem directory
53
- AMORAIL_USERMAIL=my_mail@test.com AMORAIL_API_KEY=my_key AMORAIL_API_ENDPOINT=my@amo.com bundle exec rake console
94
+ AMORAIL_CLIENT_ID=integration_id AMORAIL_CLIENT_SECRET=secret_key AMORAIL_CODE=my_code AMORAIL_REDIRECT_URI=https://example.com AMORAIL_API_ENDPOINT=https://test.amocrm.ru bundle exec rake console
54
95
  pry> Amorail.properties
55
96
  # ... prints properties (custom_fields) data
56
97
  pry> Amorail::Contact.find_by_query("test_contact")
@@ -223,12 +264,12 @@ It is possible to use Amorail with multiple AmoCRM accounts. To do so use `Amora
223
264
  which receive client params or client instance and a block to execute within custom context:
224
265
 
225
266
  ```ruby
226
- Amorail.with_client(usermail: "custom@mail.com", api_endpoint: "https://my.acmocrm.ru", api_key: "my_secret_key") do
267
+ Amorail.with_client(client_id: "my_id", client_secret: "my_secret", code: "my_code", api_endpoint: "https://my.acmocrm.ru", redirect_uri: "https://example.com") do
227
268
  # Client specific code here
228
269
  end
229
270
 
230
271
  # or using client instance
231
- my_client = Amorail::Client.new(usermail: "custom@mail.com", api_endpoint: "https://my.acmocrm.ru", api_key: "my_secret_key")
272
+ my_client = Amorail::Client.new(client_id: "my_id", client_secret: "my_secret", code: "my_code", api_endpoint: "https://my.acmocrm.ru", redirect_uri: "https://example.com")
232
273
 
233
274
  Amorail.with_client(client) do
234
275
  ...
data/RELEASING.md ADDED
@@ -0,0 +1,43 @@
1
+ # How to release a gem
2
+
3
+ This document describes a process of releasing a new version of a gem.
4
+
5
+ 1. Bump version.
6
+
7
+ ```sh
8
+ git commit -m "Bump 1.<x>.<y>"
9
+ ```
10
+
11
+ We're (kinda) using semantic versioning:
12
+
13
+ - Bugfixes should be released as fast as possible as patch versions.
14
+ - New features could be combined and released as minor or patch version upgrades (depending on the _size of the feature_—it's up to maintainers to decide).
15
+ - Breaking API changes should be avoided in minor and patch releases.
16
+ - Breaking dependencies changes (e.g., dropping older Ruby support) could be released in minor versions.
17
+
18
+ How to bump a version:
19
+
20
+ - Change the version number in `lib/amorail/version.rb` file.
21
+ - Update the changelog (add new heading with the version name and date).
22
+ - Update the installation documentation if necessary (e.g., during minor and major updates).
23
+
24
+ 2. Push code to GitHub and make sure CI passes.
25
+
26
+ ```sh
27
+ git push
28
+ ```
29
+
30
+ 3. Release a gem.
31
+
32
+ ```sh
33
+ gem release -t
34
+ git push --tags
35
+ ```
36
+
37
+ We use [gem-release](https://github.com/svenfuchs/gem-release) for publishing gems with a single command:
38
+
39
+ ```sh
40
+ gem release -t
41
+ ```
42
+
43
+ Don't forget to push tags and write release notes on GitHub (if necessary).
data/amorail.gemspec CHANGED
@@ -12,6 +12,7 @@ Gem::Specification.new do |spec|
12
12
  spec.description = %q{Ruby API client for AmoCRM. You can integrate your system with it.}
13
13
  spec.homepage = ""
14
14
  spec.license = "MIT"
15
+ spec.required_ruby_version = Gem::Requirement.new(">= 2.5.0")
15
16
 
16
17
  spec.files = `git ls-files -z`.split("\x0")
17
18
  spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
@@ -25,9 +26,10 @@ Gem::Specification.new do |spec|
25
26
  spec.add_development_dependency "pry"
26
27
  spec.add_development_dependency "shoulda-matchers", "~> 2.0"
27
28
  spec.add_development_dependency "rubocop", "~> 0.49"
28
- spec.add_dependency "anyway_config", "~> 0", ">= 0.3"
29
+ spec.add_dependency "anyway_config", ">= 1.0"
29
30
  spec.add_dependency "faraday"
30
31
  spec.add_dependency "faraday_middleware"
31
- spec.add_dependency 'activemodel'
32
- spec.add_dependency 'json'
32
+ spec.add_dependency "activemodel"
33
+ spec.add_dependency "json"
34
+ spec.add_dependency "redis"
33
35
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Amorail
4
+ class AccessToken
5
+ attr_reader :token, :secret, :expiration, :refresh_token, :store
6
+
7
+ def initialize(secret, token, refresh_token, expiration, store)
8
+ @secret = secret
9
+ @token = token
10
+ @refresh_token = refresh_token
11
+ @expiration = expiration
12
+ @store = store
13
+ end
14
+
15
+ def expired?
16
+ store.access_expired?(secret)
17
+ end
18
+
19
+ class << self
20
+ def create(secret, token, refresh_token, expiration, store)
21
+ new(secret, token, refresh_token, expiration, store).tap do |access_token|
22
+ store.persist_access(access_token.secret, access_token.token, access_token.refresh_token, access_token.expiration)
23
+ end
24
+ end
25
+
26
+ def find(secret, store)
27
+ token_attrs = store.fetch_access(secret)
28
+ build_with_token_attrs(store, secret, token_attrs)
29
+ end
30
+
31
+ def refresh(secret, token, refresh_token, expiration, store)
32
+ new(secret, token, refresh_token, expiration, store).tap do |access_token|
33
+ store.update_access(access_token.secret, access_token.token, access_token.refresh_token, access_token.expiration)
34
+ end
35
+ end
36
+
37
+ private
38
+
39
+ def build_with_token_attrs(store, secret, token_attrs)
40
+ new(secret, token_attrs[:token], token_attrs[:refresh_token], token_attrs[:expiration], store)
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'faraday'
2
4
  require 'faraday_middleware'
3
5
  require 'json'
@@ -8,14 +10,31 @@ module Amorail
8
10
  class Client
9
11
  SUCCESS_STATUS_CODES = [200, 204].freeze
10
12
 
11
- attr_reader :usermail, :api_key, :api_endpoint
13
+ attr_accessor :store
14
+ attr_reader :api_endpoint,
15
+ :access_token,
16
+ :refresh_token,
17
+ :access,
18
+ :client_id,
19
+ :client_secret,
20
+ :code,
21
+ :redirect_uri
12
22
 
13
23
  def initialize(api_endpoint: Amorail.config.api_endpoint,
14
- api_key: Amorail.config.api_key,
15
- usermail: Amorail.config.usermail)
24
+ client_id: Amorail.config.client_id,
25
+ client_secret: Amorail.config.client_secret,
26
+ code: Amorail.config.code,
27
+ redirect_uri: Amorail.config.redirect_uri)
28
+ @store = Amorail.token_store
16
29
  @api_endpoint = api_endpoint
17
- @api_key = api_key
18
- @usermail = usermail
30
+ @client_id = client_id
31
+ @client_secret = client_secret
32
+ @code = code
33
+ @redirect_uri = redirect_uri
34
+ @access = AccessToken.find(@client_secret, store)
35
+ @access_token = @access.token
36
+ @refresh_token = @access.refresh_token
37
+
19
38
  @connect = Faraday.new(url: api_endpoint) do |faraday|
20
39
  faraday.response :json, content_type: /\bjson$/
21
40
  faraday.use :instrumentation
@@ -32,33 +51,53 @@ module Amorail
32
51
  end
33
52
 
34
53
  def authorize
35
- self.cookies = nil
36
- response = post(
37
- Amorail.config.auth_url,
38
- 'USER_LOGIN' => usermail,
39
- 'USER_HASH' => api_key
40
- )
41
- cookie_handler(response)
54
+ response = post(Amorail.config.auth_url, auth_params)
55
+ create_access_token(response)
56
+ response
57
+ end
58
+
59
+ def refresh_token!
60
+ response = post(Amorail.config.auth_url, refresh_params)
61
+ update_access_token(response)
42
62
  response
43
63
  end
44
64
 
65
+ def auth_params
66
+ {
67
+ client_id: client_id,
68
+ client_secret: client_secret,
69
+ grant_type: 'authorization_code',
70
+ code: @code,
71
+ redirect_uri: redirect_uri
72
+ }
73
+ end
74
+
75
+ def refresh_params
76
+ {
77
+ client_id: client_id,
78
+ client_secret: client_secret,
79
+ grant_type: 'refresh_token',
80
+ refresh_token: refresh_token,
81
+ redirect_uri: redirect_uri
82
+ }
83
+ end
84
+
45
85
  def safe_request(method, url, params = {})
46
- public_send(method, url, params)
47
- rescue ::Amorail::AmoUnauthorizedError
48
- authorize
86
+ authorize if access_token.blank?
87
+ refresh_token! if access.expired?
49
88
  public_send(method, url, params)
50
89
  end
51
90
 
52
91
  def get(url, params = {})
53
92
  response = connect.get(url, params) do |request|
54
- request.headers['Cookie'] = cookies if cookies.present?
93
+ request.headers['Authorization'] = "Bearer #{access_token}" if access_token.present?
55
94
  end
56
95
  handle_response(response)
57
96
  end
58
97
 
59
98
  def post(url, params = {})
60
99
  response = connect.post(url) do |request|
61
- request.headers['Cookie'] = cookies if cookies.present?
100
+ request.headers['Authorization'] = "Bearer #{access_token}" if access_token.present?
62
101
  request.headers['Content-Type'] = 'application/json'
63
102
  request.body = params.to_json
64
103
  end
@@ -67,12 +106,6 @@ module Amorail
67
106
 
68
107
  private
69
108
 
70
- attr_accessor :cookies
71
-
72
- def cookie_handler(response)
73
- self.cookies = response.headers['set-cookie'].split('; ')[0]
74
- end
75
-
76
109
  def handle_response(response) # rubocop:disable all
77
110
  return response if SUCCESS_STATUS_CODES.include?(response.status)
78
111
 
@@ -97,5 +130,33 @@ module Amorail
97
130
  fail ::Amorail::AmoUnknownError, response.body
98
131
  end
99
132
  end
133
+
134
+ def create_access_token(response)
135
+ _access = AccessToken.create(
136
+ client_secret,
137
+ response.body['access_token'],
138
+ response.body['refresh_token'],
139
+ expiration(response.body['expires_in']),
140
+ store
141
+ )
142
+ @access_token = _access.token
143
+ @refresh_token = _access.refresh_token
144
+ end
145
+
146
+ def update_access_token(response)
147
+ _access = AccessToken.refresh(
148
+ client_secret,
149
+ response.body['access_token'],
150
+ response.body['refresh_token'],
151
+ expiration(response.body['expires_in']),
152
+ store
153
+ )
154
+ @access_token = _access.token
155
+ @refresh_token = _access.refresh_token
156
+ end
157
+
158
+ def expiration(expires_in)
159
+ Time.now.to_i + expires_in.to_i
160
+ end
100
161
  end
101
162
  end
@@ -1,17 +1,23 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'anyway'
2
4
 
3
5
  module Amorail
4
6
  # Amorail config contains:
5
- # - usermail ("user@gmail.com")
6
- # - api_key ("13601bbac84727df")
7
7
  # - api_endpoint ("http://you_company.amocrm.com")
8
8
  # - api_path (default: "/private/api/v2/json/")
9
- # - auth_url (default: "/private/api/auth.php?type=json")
9
+ # - auth_url (default: "/oauth2/access_token")
10
10
  class Config < Anyway::Config
11
- attr_config :usermail,
12
- :api_key,
13
- :api_endpoint,
14
- api_path: "/private/api/v2/json/",
15
- auth_url: "/private/api/auth.php?type=json"
11
+ attr_config :api_endpoint,
12
+ :client_id,
13
+ :client_secret,
14
+ :code,
15
+ :redirect_uri,
16
+ :redis_url,
17
+ api_path: '/private/api/v2/json/',
18
+ auth_url: '/oauth2/access_token',
19
+ redis_host: '127.0.0.1',
20
+ redis_port: '6379',
21
+ redis_db_name: '0'
16
22
  end
17
23
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'amorail/entities/leadable'
2
4
 
3
5
  module Amorail
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'amorail/entities/leadable'
2
4
 
3
5
  module Amorail
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Amorail
2
4
  # AmoCRM contact-link join model
3
5
  class ContactLink < Amorail::Entity
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Amorail
2
4
  # Provides common functionallity for entities
3
5
  # that can be attached to another objects.
@@ -6,9 +8,9 @@ module Amorail
6
8
 
7
9
  ELEMENT_TYPES = {
8
10
  contact: 1,
9
- lead: 2,
11
+ lead: 2,
10
12
  company: 3,
11
- task: 4
13
+ task: 4
12
14
  }.freeze
13
15
 
14
16
  included do
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Amorail
2
4
  # AmoCRM lead entity
3
5
  class Lead < Amorail::Entity
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Amorail
2
4
  # Lead associations
3
5
  module Leadable
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'amorail/entities/elementable'
2
4
 
3
5
  module Amorail
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'amorail/entities/elementable'
2
4
 
3
5
  module Amorail
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Amorail
2
4
  # AmoCRM webhook entity
3
5
  class Webhook < Entity
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Amorail # :nodoc: all
2
4
  class Entity
3
5
  class << self
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "active_support/core_ext/hash/indifferent_access"
2
4
 
3
5
  module Amorail # :nodoc: all
@@ -19,11 +21,19 @@ module Amorail # :nodoc: all
19
21
  props = properties.send(self.class.amo_name)
20
22
 
21
23
  custom_fields = []
22
-
23
24
  self.class.properties.each do |k, v|
24
25
  prop_id = props.send(k).id
25
- prop_val = { value: send(k) }.merge(v)
26
- custom_fields << { id: prop_id, values: [prop_val] }
26
+ prop_values = send(v.fetch(:method_name, k))
27
+
28
+ if prop_values.is_a?(Array)
29
+ prop_val = prop_values.map do |value|
30
+ { value: value }.merge(v)
31
+ end
32
+ else
33
+ prop_val = [{ value: prop_values }.merge(v)]
34
+ end
35
+
36
+ custom_fields << { id: prop_id, values: prop_val }
27
37
  end
28
38
 
29
39
  custom_fields
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Amorail # :nodoc: all
2
4
  class Entity
3
5
  class InvalidRecord < ::Amorail::Error; end
@@ -1,4 +1,7 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_model'
4
+ require 'active_support/core_ext/array'
2
5
 
3
6
  module Amorail
4
7
  # Core class for all Amo entities (company, contact, etc)
@@ -32,7 +35,12 @@ module Amorail
32
35
 
33
36
  def amo_property(name, options = {})
34
37
  properties[name] = options
35
- attr_accessor(name)
38
+ var_name = options.fetch(:method_name, name)
39
+ define_method "#{var_name}=" do |value|
40
+ var_value = options[:multiple] ? Array.wrap(value) : Array.wrap(value).first
41
+ instance_variable_set("@#{var_name}", var_value)
42
+ end
43
+ attr_reader(var_name)
36
44
  end
37
45
 
38
46
  def attributes
@@ -87,15 +95,24 @@ module Amorail
87
95
  return if fields.nil?
88
96
 
89
97
  fields.each do |f|
90
- fname = f['code'] || f['name']
98
+ fname = custom_field_name(f)
91
99
  next if fname.nil?
92
100
 
93
101
  fname = "#{fname.downcase}="
94
- fval = f.fetch('values').first.fetch('value')
102
+ fval = f.fetch('values').pluck('value')
95
103
  send(fname, fval) if respond_to?(fname)
96
104
  end
97
105
  end
98
106
 
107
+ def custom_field_name(field)
108
+ fname = field['code'] || field['name']
109
+ return if fname.nil?
110
+
111
+ fname = self.class.properties
112
+ .fetch(fname.downcase, {})[:method_name] || fname
113
+ fname
114
+ end
115
+
99
116
  # call safe method <safe_request>. safe_request call authorize
100
117
  # if current session undefined or expires.
101
118
  def push(method)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  # Amorail Exceptions.
2
4
  # Every class is name of HTTP response error code(status)
3
5
  module Amorail
@@ -1,16 +1,18 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Amorail
2
4
  # Return hash key as method call
3
5
  module MethodMissing
4
6
  def method_missing(method_sym, *arguments, &block)
5
- if data.key?(method_sym.to_s)
6
- data.fetch(method_sym.to_s)
7
+ if data.key?(method_sym.to_s.downcase)
8
+ data.fetch(method_sym.to_s.downcase)
7
9
  else
8
10
  super
9
11
  end
10
12
  end
11
13
 
12
14
  def respond_to_missing?(method_sym, *args)
13
- args.size.zero? && data.key?(method_sym.to_s)
15
+ args.size.zero? && data.key?(method_sym.to_s.downcase)
14
16
  end
15
17
  end
16
18
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Amorail
2
4
  # Add amorail rake tasks
3
5
  class Railtie < Rails::Railtie