amorail 0.3.4 → 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
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