amorail 0.3.4 → 0.6.0

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 (66) hide show
  1. checksums.yaml +5 -5
  2. data/.rubocop.yml +19 -1
  3. data/README.md +7 -1
  4. data/Rakefile +7 -3
  5. data/amorail.gemspec +4 -3
  6. data/lib/amorail.rb +3 -0
  7. data/lib/amorail/client.rb +8 -4
  8. data/lib/amorail/config.rb +2 -0
  9. data/lib/amorail/entities/company.rb +2 -0
  10. data/lib/amorail/entities/contact.rb +3 -0
  11. data/lib/amorail/entities/contact_link.rb +2 -0
  12. data/lib/amorail/entities/elementable.rb +39 -0
  13. data/lib/amorail/entities/lead.rb +4 -1
  14. data/lib/amorail/entities/leadable.rb +3 -0
  15. data/lib/amorail/entities/note.rb +19 -0
  16. data/lib/amorail/entities/task.rb +11 -23
  17. data/lib/amorail/entities/webhook.rb +44 -0
  18. data/lib/amorail/entity.rb +17 -9
  19. data/lib/amorail/entity/finders.rb +19 -14
  20. data/lib/amorail/entity/params.rb +2 -0
  21. data/lib/amorail/entity/{persistance.rb → persistence.rb} +24 -0
  22. data/lib/amorail/exceptions.rb +2 -0
  23. data/lib/amorail/property.rb +8 -0
  24. data/lib/amorail/railtie.rb +4 -1
  25. data/lib/amorail/version.rb +3 -1
  26. data/lib/tasks/amorail.rake +2 -0
  27. data/spec/client_spec.rb +2 -0
  28. data/spec/company_spec.rb +2 -0
  29. data/spec/contact_link_spec.rb +2 -0
  30. data/spec/contact_spec.rb +17 -0
  31. data/spec/entity_spec.rb +2 -0
  32. data/spec/fixtures/{account_response.json → accounts/response_1.json} +5 -5
  33. data/spec/fixtures/{account2_response.json → accounts/response_2.json} +1 -1
  34. data/spec/fixtures/{contact_create.json → contacts/create.json} +1 -1
  35. data/spec/fixtures/{contact_find_query.json → contacts/find_many.json} +3 -5
  36. data/spec/fixtures/{contact_find.json → contacts/find_one.json} +5 -6
  37. data/spec/fixtures/contacts/links.json +16 -0
  38. data/spec/fixtures/{my_contact_find.json → contacts/my_contact_find.json} +2 -3
  39. data/spec/fixtures/contacts/update.json +13 -0
  40. data/spec/fixtures/leads/create.json +13 -0
  41. data/spec/fixtures/leads/find_many.json +73 -0
  42. data/spec/fixtures/leads/links.json +16 -0
  43. data/spec/fixtures/leads/update.json +13 -0
  44. data/spec/fixtures/leads/update_errors.json +12 -0
  45. data/spec/fixtures/webhooks/list.json +24 -0
  46. data/spec/fixtures/webhooks/subscribe.json +17 -0
  47. data/spec/fixtures/webhooks/unsubscribe.json +17 -0
  48. data/spec/helpers/webmock_helpers.rb +92 -13
  49. data/spec/lead_spec.rb +30 -0
  50. data/spec/my_contact_spec.rb +2 -0
  51. data/spec/note_spec.rb +28 -0
  52. data/spec/property_spec.rb +2 -0
  53. data/spec/spec_helper.rb +4 -2
  54. data/spec/support/elementable_example.rb +54 -0
  55. data/spec/support/entity_class_example.rb +2 -0
  56. data/spec/support/leadable_example.rb +2 -0
  57. data/spec/support/my_contact.rb +2 -0
  58. data/spec/support/my_entity.rb +2 -0
  59. data/spec/task_spec.rb +8 -28
  60. data/spec/webhook_spec.rb +61 -0
  61. metadata +60 -33
  62. data/.hound.yml +0 -12
  63. data/spec/fixtures/contact_update.json +0 -5
  64. data/spec/fixtures/contacts_links.json +0 -15
  65. data/spec/fixtures/leads.json +0 -69
  66. data/spec/fixtures/leads_links.json +0 -15
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
- SHA1:
3
- metadata.gz: 8db673fd4edfbf45eb53a5d0f975a89d7895fdba
4
- data.tar.gz: 356888cd7462e7c1ec8c0b427f422faf6b2da05e
2
+ SHA256:
3
+ metadata.gz: 6047ec19aa4839d30b29fc1cb9ee38079a04812b2ed6547d2e24e1e8ed16a57d
4
+ data.tar.gz: 64bfbcd2f8574b70c420a274bd8707533b4f4334020d180fda7acb85cdcee509
5
5
  SHA512:
6
- metadata.gz: 97c770c6157118cb4613528c1b0f3ce57f958bb973426b3113bf5c5ee0f7da40d197dc516401568ca0fcc615764484de9936e3ff8f82df2d0c4f59dd406aacd2
7
- data.tar.gz: 965e56642519d357ba3eba530bfdd1a8b1f967bb0d8e11ab0dc54ccab032ef82ae40c97a91c0ed396f4cd3a19e72f8a796ea306afae7513616cc0023422fcb64
6
+ metadata.gz: 55fc29183cff4df2db947a76ceae748d289ebab1f1e88310e7c13234dd75886c87e9f68c41b782ba251be69f2226f386501974108ce2cc00fdc656e1ccdfe059
7
+ data.tar.gz: da64fc02f0bc506319bd543e7b35cb5dfdd3b112cc04458f25904c5d9ece1bc38df8d659ca1d705e17fcea8e7cd82e954c7f4642048fb2ed627a03c0f1802b8c
@@ -6,7 +6,9 @@ AllCops:
6
6
  - 'spec/**/*.rb'
7
7
  Exclude:
8
8
  - 'bin/**/*'
9
- RunRailsCops: true
9
+ - Rakefile
10
+ - Gemfile
11
+ - '*.gemspec'
10
12
  DisplayCopNames: true
11
13
  StyleGuideCopsOnly: false
12
14
 
@@ -39,5 +41,21 @@ Metrics/LineLength:
39
41
  Exclude:
40
42
  - 'spec/**/*.rb'
41
43
 
44
+ Metrics/BlockLength:
45
+ Exclude:
46
+ - 'spec/**/*.rb'
47
+
42
48
  Style/WordArray:
43
49
  Enabled: false
50
+
51
+ Style/SymbolArray:
52
+ Enabled: false
53
+
54
+ Style/SignalException:
55
+ Enabled: false
56
+
57
+ Layout/MultilineMethodCallBraceLayout:
58
+ Enabled: false
59
+
60
+ Lint/MissingCopEnableDirective:
61
+ Enabled: false
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- [![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 Status](https://travis-ci.org/teachbase/amorail.svg?branch=master)](https://travis-ci.org/teachbase/amorail)
2
2
 
3
3
  # Amorail
4
4
 
@@ -128,6 +128,12 @@ Or using query:
128
128
  Amorail::Company.find_by_query("vip")
129
129
  ```
130
130
 
131
+ Or using arbitrary params:
132
+
133
+ ```ruby
134
+ Amorail::Company.where(query: "test", limit_rows: 10)
135
+ ```
136
+
131
137
  Also you can update objects, e.g:
132
138
 
133
139
  ```ruby
data/Rakefile CHANGED
@@ -1,11 +1,15 @@
1
1
  require "bundler/gem_tasks"
2
2
  require 'rspec/core/rake_task'
3
- Dir.glob('lib/tasks/*.rake').each {|r| import r}
3
+ require "rubocop/rake_task"
4
4
 
5
- RSpec::Core::RakeTask.new(:spec)
5
+ Dir.glob('lib/tasks/*.rake').each { |r| import r }
6
6
 
7
- task :default => :spec
7
+ RSpec::Core::RakeTask.new(:spec)
8
8
 
9
9
  task :console do
10
10
  sh 'pry -r ./lib/amorail.rb'
11
11
  end
12
+
13
+ RuboCop::RakeTask.new
14
+
15
+ task default: [:rubocop, :spec]
@@ -22,9 +22,10 @@ Gem::Specification.new do |spec|
22
22
  spec.add_development_dependency "rake", "~> 10.0"
23
23
  spec.add_development_dependency "rspec", "~> 3.4"
24
24
  spec.add_development_dependency "webmock"
25
- spec.add_development_dependency 'pry'
26
- spec.add_development_dependency 'shoulda-matchers', "~> 2.0"
27
- spec.add_dependency "anyway_config", "~> 0", ">= 0.3"
25
+ spec.add_development_dependency "pry"
26
+ spec.add_development_dependency "shoulda-matchers", "~> 2.0"
27
+ spec.add_development_dependency "rubocop", "~> 0.49"
28
+ spec.add_dependency "anyway_config", ">= 1.0"
28
29
  spec.add_dependency "faraday"
29
30
  spec.add_dependency "faraday_middleware"
30
31
  spec.add_dependency 'activemodel'
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'amorail/version'
2
4
  require 'amorail/config'
3
5
  require 'amorail/client'
@@ -35,6 +37,7 @@ module Amorail
35
37
  client = Client.new(client) unless client.is_a?(Client)
36
38
  ClientRegistry.client = client
37
39
  yield
40
+ ensure
38
41
  ClientRegistry.client = nil
39
42
  end
40
43
 
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'faraday'
2
4
  require 'faraday_middleware'
3
5
  require 'json'
@@ -6,6 +8,8 @@ require 'active_support'
6
8
  module Amorail
7
9
  # Amorail http client
8
10
  class Client
11
+ SUCCESS_STATUS_CODES = [200, 204].freeze
12
+
9
13
  attr_reader :usermail, :api_key, :api_endpoint
10
14
 
11
15
  def initialize(api_endpoint: Amorail.config.api_endpoint,
@@ -15,9 +19,9 @@ module Amorail
15
19
  @api_key = api_key
16
20
  @usermail = usermail
17
21
  @connect = Faraday.new(url: api_endpoint) do |faraday|
18
- faraday.adapter Faraday.default_adapter
19
22
  faraday.response :json, content_type: /\bjson$/
20
23
  faraday.use :instrumentation
24
+ faraday.adapter Faraday.default_adapter
21
25
  end
22
26
  end
23
27
 
@@ -41,10 +45,10 @@ module Amorail
41
45
  end
42
46
 
43
47
  def safe_request(method, url, params = {})
44
- send(method, url, params)
48
+ public_send(method, url, params)
45
49
  rescue ::Amorail::AmoUnauthorizedError
46
50
  authorize
47
- send(method, url, params)
51
+ public_send(method, url, params)
48
52
  end
49
53
 
50
54
  def get(url, params = {})
@@ -72,7 +76,7 @@ module Amorail
72
76
  end
73
77
 
74
78
  def handle_response(response) # rubocop:disable all
75
- return response if response.status == 200 || response.status == 204
79
+ return response if SUCCESS_STATUS_CODES.include?(response.status)
76
80
 
77
81
  case response.status
78
82
  when 301
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'anyway'
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
  require 'amorail/entities/leadable'
2
4
 
3
5
  module Amorail
@@ -22,6 +24,7 @@ module Amorail
22
24
 
23
25
  def company
24
26
  return if linked_company_id.nil?
27
+
25
28
  @company ||= Amorail::Company.find(linked_company_id)
26
29
  end
27
30
  end
@@ -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
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Amorail
4
+ # Provides common functionallity for entities
5
+ # that can be attached to another objects.
6
+ module Elementable
7
+ extend ActiveSupport::Concern
8
+
9
+ ELEMENT_TYPES = {
10
+ contact: 1,
11
+ lead: 2,
12
+ company: 3,
13
+ task: 4
14
+ }.freeze
15
+
16
+ included do
17
+ amo_field :element_id, :element_type
18
+
19
+ validates :element_id, :element_type,
20
+ presence: true
21
+ end
22
+
23
+ ELEMENT_TYPES.each do |type, value|
24
+ class_eval <<-CODE, __FILE__, __LINE__ + 1
25
+ def #{type}=(val) # def contact=(val)
26
+ #{type}! if val # contact! if val
27
+ end # end
28
+
29
+ def #{type}? # def contact?
30
+ self.element_type == #{value} # self.element_type == 1
31
+ end # end
32
+
33
+ def #{type}! # def contact!
34
+ self.element_type = #{value} # self.element_type = 1
35
+ end # end
36
+ CODE
37
+ end
38
+ end
39
+ end
@@ -1,9 +1,11 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Amorail
2
4
  # AmoCRM lead entity
3
5
  class Lead < Amorail::Entity
4
6
  amo_names "leads"
5
7
 
6
- amo_field :name, :price, :status_id, :tags
8
+ amo_field :name, :price, :status_id, :pipeline_id, :tags
7
9
 
8
10
  validates :name, :status_id, presence: true
9
11
 
@@ -15,6 +17,7 @@ module Amorail
15
17
  # Return list of associated contacts
16
18
  def contacts
17
19
  fail NotPersisted if id.nil?
20
+
18
21
  @contacts ||=
19
22
  begin
20
23
  links = Amorail::ContactLink.find_by_leads(id)
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Amorail
2
4
  # Lead associations
3
5
  module Leadable
@@ -22,6 +24,7 @@ module Amorail
22
24
  # Return all linked leads
23
25
  def leads
24
26
  return [] if linked_leads_id.empty?
27
+
25
28
  @leads ||= Amorail::Lead.find_all(linked_leads_id)
26
29
  end
27
30
  end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'amorail/entities/elementable'
4
+
5
+ module Amorail
6
+ # AmoCRM note entity
7
+ class Note < Amorail::Entity
8
+ include Elementable
9
+
10
+ amo_names 'notes'
11
+
12
+ amo_field :note_type, :text
13
+
14
+ validates :note_type, :text,
15
+ presence: true
16
+
17
+ validates :element_type, inclusion: ELEMENT_TYPES.values
18
+ end
19
+ end
@@ -1,32 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'amorail/entities/elementable'
4
+
1
5
  module Amorail
2
6
  # AmoCRM task entity
3
7
  class Task < Amorail::Entity
4
- amo_names "tasks"
5
-
6
- amo_field :element_id, :element_type, :text,
7
- :task_type, complete_till: :timestamp
8
+ include Elementable
8
9
 
9
- validates :text, :element_id,
10
- :element_type, :complete_till,
11
- :task_type,
12
- presence: true
13
-
14
- validates :element_type, inclusion: 1..2
10
+ amo_names 'tasks'
15
11
 
16
- [{ name: "contact", val: 1 }, { name: "lead", val: 2 }].each do |prop|
17
- class_eval <<-CODE, __FILE__, __LINE__ + 1
18
- def #{prop[:name]}=(val)
19
- #{prop[:name]}! if val
20
- end
12
+ amo_field :task_type, :text, complete_till: :timestamp
21
13
 
22
- def #{prop[:name]}?
23
- self.element_type == #{prop[:val]}
24
- end
14
+ validates :task_type, :text, :complete_till,
15
+ presence: true
25
16
 
26
- def #{prop[:name]}!
27
- self.element_type = #{prop[:val]}
28
- end
29
- CODE
30
- end
17
+ validates :element_type, inclusion:
18
+ ELEMENT_TYPES.reject { |type, _| type == :task }.values
31
19
  end
32
20
  end
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Amorail
4
+ # AmoCRM webhook entity
5
+ class Webhook < Entity
6
+ amo_names 'webhooks'
7
+
8
+ amo_field :id, :url, :events, :disabled
9
+
10
+ def self.list
11
+ response = client.safe_request(:get, remote_url('list'))
12
+
13
+ return [] if response.body.blank?
14
+
15
+ response.body['response'].fetch(amo_response_name, []).map do |attributes|
16
+ new.reload_model(attributes)
17
+ end
18
+ end
19
+
20
+ def self.subscribe(webhooks)
21
+ perform_webhooks_request('subscribe', webhooks) do |data|
22
+ data.map { |attrs| new.reload_model(attrs) }
23
+ end
24
+ end
25
+
26
+ def self.unsubscribe(webhooks)
27
+ perform_webhooks_request('unsubscribe', webhooks)
28
+ end
29
+
30
+ def self.perform_webhooks_request(action, webhooks, &block)
31
+ response = client.safe_request(
32
+ :post,
33
+ remote_url(action),
34
+ request: { webhooks: { action => webhooks } }
35
+ )
36
+
37
+ return response unless block
38
+
39
+ block.call(response.body['response'].dig(amo_response_name, 'subscribe'))
40
+ end
41
+
42
+ private_class_method :perform_webhooks_request
43
+ end
44
+ end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require 'active_model'
2
4
 
3
5
  module Amorail
@@ -62,7 +64,7 @@ module Amorail
62
64
  end
63
65
 
64
66
  require 'amorail/entity/params'
65
- require 'amorail/entity/persistance'
67
+ require 'amorail/entity/persistence'
66
68
  require 'amorail/entity/finders'
67
69
 
68
70
  def reload_model(info)
@@ -77,6 +79,7 @@ module Amorail
77
79
  attrs.each do |k, v|
78
80
  action = "#{k}="
79
81
  next unless respond_to?(action)
82
+
80
83
  send(action, v)
81
84
  end
82
85
  self
@@ -84,9 +87,11 @@ module Amorail
84
87
 
85
88
  def merge_custom_fields(fields)
86
89
  return if fields.nil?
90
+
87
91
  fields.each do |f|
88
92
  fname = f['code'] || f['name']
89
93
  next if fname.nil?
94
+
90
95
  fname = "#{fname.downcase}="
91
96
  fval = f.fetch('values').first.fetch('value')
92
97
  send(fname, fval) if respond_to?(fname)
@@ -108,15 +113,18 @@ module Amorail
108
113
  )
109
114
  end
110
115
 
116
+ # We can have response with 200 or 204 here.
117
+ # 204 response has no body, so we don't want to parse it.
111
118
  def handle_response(response, method)
112
- return false unless response.status == 200
113
- extract_method = "extract_data_#{method}"
114
- reload_model(
115
- send(extract_method,
116
- response.body['response'][self.class.amo_response_name]
117
- )
118
- ) if respond_to?(extract_method, true)
119
- self
119
+ return false if response.status == 204
120
+
121
+ data = send(
122
+ "extract_data_#{method}",
123
+ response.body['response'][self.class.amo_response_name]
124
+ )
125
+ reload_model(data)
126
+ rescue InvalidRecord
127
+ false
120
128
  end
121
129
  end
122
130
  end
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  module Amorail # :nodoc: all
2
4
  class Entity
3
5
  class << self
@@ -11,38 +13,41 @@ module Amorail # :nodoc: all
11
13
  def find!(id)
12
14
  rec = find(id)
13
15
  fail RecordNotFound unless rec
16
+
14
17
  rec
15
18
  end
16
19
 
17
- def find_all(*ids)
18
- ids = ids.first if ids.size == 1 && ids.first.is_a?(Array)
19
-
20
+ # General method to load many records by proving some filters
21
+ def where(options)
20
22
  response = client.safe_request(
21
23
  :get,
22
24
  remote_url('list'),
23
- id: ids
25
+ options
24
26
  )
25
27
  load_many(response)
26
28
  end
27
29
 
30
+ def find_all(*ids)
31
+ ids = ids.first if ids.size == 1 && ids.first.is_a?(Array)
32
+
33
+ where(id: ids)
34
+ end
35
+
28
36
  # Find AMO entities by query
29
37
  # Returns array of matching entities.
30
- def find_by_query(q)
31
- response = client.safe_request(
32
- :get,
33
- remote_url('list'),
34
- query: q
35
- )
36
- load_many(response)
38
+ def find_by_query(query)
39
+ where(query: query)
37
40
  end
38
41
 
39
42
  private
40
43
 
44
+ # We can have response with 200 or 204 here.
45
+ # 204 response has no body, so we don't want to parse it.
41
46
  def load_many(response)
42
- return [] unless response.status == 200
47
+ return [] if response.status == 204
43
48
 
44
- (response.body['response'][amo_response_name] || [])
45
- .map { |info| new.reload_model(info) }
49
+ response.body['response'].fetch(amo_response_name, [])
50
+ .map { |info| new.reload_model(info) }
46
51
  end
47
52
  end
48
53