amorail 0.1.4

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.
@@ -0,0 +1,263 @@
1
+ require 'active_model'
2
+
3
+ module Amorail
4
+ class AmoEntity
5
+ include ActiveModel::Model
6
+ include ActiveModel::AttributeMethods
7
+ include ActiveModel::Validations
8
+
9
+ class InvalidRecord < ::Amorail::Error; end
10
+ class NotPersisted < ::Amorail::Error; end
11
+ class RecordNotFound < ::Amorail::Error; end
12
+
13
+ class << self
14
+ attr_reader :amo_name, :amo_response_name, :attributes, :properties
15
+
16
+ def amo_names(name, response_name = nil)
17
+ @amo_name = @amo_response_name = name
18
+ @amo_response_name = response_name unless response_name.nil?
19
+ end
20
+
21
+ def amo_field(*vars, **hargs)
22
+ @attributes ||= {}
23
+ vars.each { |v| @attributes[v] = :default }
24
+ hargs.each { |k, v| @attributes[k] = v }
25
+ attr_accessor(*@attributes.keys)
26
+ end
27
+
28
+ def amo_property(name, options = {})
29
+ @properties ||= {}
30
+ @properties[name] = options
31
+ attr_accessor(name)
32
+ end
33
+
34
+ def find(id)
35
+ new.load_record(id)
36
+ end
37
+
38
+ def find!(id)
39
+ rec = find(id)
40
+ fail RecordNotFound unless rec
41
+ rec
42
+ end
43
+ end
44
+
45
+ amo_names 'entity'
46
+
47
+ amo_field :id, :request_id, :responsible_user_id,
48
+ date_create: :timestamp, last_modified: :timestamp
49
+
50
+ delegate :client, :properties, to: Amorail
51
+
52
+ def initialize(attributes = {})
53
+ super(attributes)
54
+ self.last_modified = Time.now.to_i if last_modified.nil?
55
+ end
56
+
57
+ def new_record?
58
+ id.blank?
59
+ end
60
+
61
+ def persisted?
62
+ !new_record?
63
+ end
64
+
65
+ def load_record(id)
66
+ response = client.safe_request(
67
+ :get,
68
+ remote_url('list'),
69
+ id: id
70
+ )
71
+ handle_response(response, 'load')
72
+ end
73
+
74
+ def save
75
+ return false unless valid?
76
+ new_record? ? push('add') : push('update')
77
+ end
78
+
79
+ def save!
80
+ if save
81
+ true
82
+ else
83
+ fail InvalidRecord
84
+ end
85
+ end
86
+
87
+ def update(attrs = {})
88
+ return false if new_record?
89
+ merge_params(attrs)
90
+ push('update')
91
+ end
92
+
93
+ def update!(attrs = {})
94
+ if update(attrs)
95
+ true
96
+ else
97
+ fail NotPersisted
98
+ end
99
+ end
100
+
101
+ def params
102
+ data = {}
103
+ self.class.attributes.each do |k, v|
104
+ data[k] = send("to_#{v}", send(k))
105
+ end
106
+
107
+ data[:custom_fields] = custom_fields
108
+
109
+ normalize_params(data)
110
+ end
111
+
112
+ protected
113
+
114
+ def custom_fields
115
+ return unless properties.respond_to?(self.class.amo_name)
116
+
117
+ return if self.class.properties.nil?
118
+
119
+ props = properties.send(self.class.amo_name)
120
+
121
+ custom_fields = []
122
+
123
+ self.class.properties.each do |k, v|
124
+ prop_id = props.send(k).id
125
+ prop_val = { value: send(k) }.merge(v)
126
+ custom_fields << { id: prop_id, values: [prop_val] }
127
+ end
128
+
129
+ custom_fields
130
+ end
131
+
132
+ def create_params(method)
133
+ {
134
+ request: {
135
+ self.class.amo_response_name => {
136
+ method => [
137
+ params
138
+ ]
139
+ }
140
+ }
141
+ }
142
+ end
143
+
144
+ def normalize_custom_fields(val)
145
+ val.reject do |field|
146
+ field[:values].all? { |item| !item[:value] }
147
+ end
148
+ end
149
+
150
+ # this method removes nil values and empty arrays from params hash (deep)
151
+ def normalize_params(data)
152
+ return data unless data.is_a?(Hash)
153
+
154
+ compacted = {}
155
+ data.each do |key, val|
156
+ case val
157
+ when Numeric, String
158
+ compacted[key] = val
159
+ when Array
160
+ val.compact!
161
+ # handle custom keys
162
+ val = normalize_custom_fields(val) if key == :custom_fields
163
+ unless val.empty?
164
+ compacted[key] = val.map { |el| normalize_params(el) }
165
+ end
166
+ else
167
+ params = normalize_params(val)
168
+ compacted[key] = params unless params.nil?
169
+ end
170
+ end
171
+ compacted.with_indifferent_access
172
+ end
173
+
174
+ def to_timestamp(val)
175
+ return if val.nil?
176
+
177
+ case val
178
+ when String
179
+ (date = Time.parse(val)) && date.to_i
180
+ when Date
181
+ val.to_time.to_i
182
+ else
183
+ val.to_i
184
+ end
185
+ end
186
+
187
+ def to_default(val)
188
+ val
189
+ end
190
+
191
+ def reload_model(info)
192
+ merge_params(info)
193
+ merge_custom_fields(info['custom_fields'])
194
+ self
195
+ end
196
+
197
+ private
198
+
199
+ def merge_params(attrs)
200
+ attrs.each do |k, v|
201
+ action = "#{k}="
202
+ next unless respond_to?(action)
203
+ send(action, v)
204
+ end
205
+ self
206
+ end
207
+
208
+ def merge_custom_fields(fields)
209
+ return if fields.nil?
210
+ fields.each do |f|
211
+ fname = "#{f.fetch('code').downcase}="
212
+ fval = f.fetch('values').first.fetch('value')
213
+ send(fname, fval) if respond_to?(fname)
214
+ end
215
+ end
216
+
217
+ def attributes_list
218
+ self.class.attributes
219
+ end
220
+
221
+ def remote_url(action)
222
+ File.join(Amorail.config.api_path, self.class.amo_name, action)
223
+ end
224
+
225
+ # call safe method <safe_request>. safe_request call authorize
226
+ # if current session undefined or expires.
227
+ def push(method)
228
+ response = commit_request(create_params(method))
229
+ handle_response(response, method)
230
+ end
231
+
232
+ def commit_request(attrs)
233
+ client.safe_request(
234
+ :post,
235
+ remote_url('set'),
236
+ normalize_params(attrs)
237
+ )
238
+ end
239
+
240
+ def handle_response(response, method)
241
+ if response.status == 200
242
+ extract_method = "extract_data_#{method}"
243
+ reload_model(
244
+ send(
245
+ extract_method,
246
+ response.body['response'][self.class.amo_response_name]
247
+ )
248
+ ) if respond_to?(extract_method, true)
249
+ self
250
+ else
251
+ false
252
+ end
253
+ end
254
+
255
+ def extract_data_load(response)
256
+ response.first
257
+ end
258
+
259
+ def extract_data_add(response)
260
+ response.fetch('add').first
261
+ end
262
+ end
263
+ end
@@ -0,0 +1,26 @@
1
+ # Classes for Amorail Exceptions
2
+ # Every class is name of HTTP response error code(status)
3
+
4
+ module Amorail
5
+ class Error < ::StandardError; end
6
+
7
+ class APIError < Error; end
8
+
9
+ class AmoBadRequestError < APIError; end
10
+
11
+ class AmoMovedPermanentlyError < APIError; end
12
+
13
+ class AmoUnauthorizedError < APIError; end
14
+
15
+ class AmoForbiddenError < APIError; end
16
+
17
+ class AmoNotFoudError < APIError; end
18
+
19
+ class AmoInternalError < APIError; end
20
+
21
+ class AmoBadGatewayError < APIError; end
22
+
23
+ class AmoServiceUnaviableError < APIError; end
24
+
25
+ class AmoUnknownError < APIError; end
26
+ end
@@ -0,0 +1,3 @@
1
+ module Amorail
2
+ VERSION = "0.1.4"
3
+ end
data/lib/amorail.rb ADDED
@@ -0,0 +1,35 @@
1
+ require 'amorail/version'
2
+ require 'amorail/config'
3
+ require 'amorail/client'
4
+ require 'amorail/exceptions'
5
+ require 'amorail/entity'
6
+ require 'amorail/custom_fields'
7
+
8
+ require 'amorail/entities/leadable'
9
+
10
+ Gem.find_files('amorail/entities/*.rb').each { |path| require path }
11
+
12
+ module Amorail
13
+ def self.config
14
+ @config ||= Config.new
15
+ end
16
+
17
+ def self.properties
18
+ @properties ||= Property.new(client)
19
+ end
20
+
21
+ def self.configure
22
+ yield(config) if block_given?
23
+ end
24
+
25
+ def self.client
26
+ @client ||= Client.new
27
+ end
28
+
29
+ def self.reset
30
+ @config = nil
31
+ @client = nil
32
+ end
33
+
34
+ require 'amorail/engine' if defined?(Rails)
35
+ end
@@ -0,0 +1,8 @@
1
+ require 'amorail'
2
+
3
+ namespace :amorail do
4
+ desc "Check Amorails configuration. Authorize and get custom fields information"
5
+ task :check do
6
+ p Amorail.properties
7
+ end
8
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+
3
+ describe Amorail::Client do
4
+ let(:client) { Amorail.client }
5
+
6
+ before(:each) { mock_api }
7
+
8
+ it "it should create client" do
9
+ expect(Amorail.config.usermail).to eq "amorail@test.com"
10
+ expect(Amorail.config.api_key).to eq "75742b166417fe32ae132282ce178cf6"
11
+ expect(Amorail.config.api_endpoint).to eq "https://test.amocrm.ru"
12
+ end
13
+
14
+ it "should #authorize method call" do
15
+ res = client.authorize
16
+ expect(res.status).to eq 200
17
+ end
18
+
19
+ it "should #authorize and set cookie" do
20
+ res = client.get("/private/api/v2/json/accounts/current")
21
+ expect(res.status).to eq 200
22
+ end
23
+ end
@@ -0,0 +1,63 @@
1
+ require "spec_helper"
2
+
3
+ describe Amorail::AmoCompany do
4
+ before { mock_api }
5
+
6
+ describe "validations" do
7
+ it { should validate_presence_of(:name) }
8
+ end
9
+
10
+ describe "#params" do
11
+ let(:company) do
12
+ Amorail::AmoCompany.new(
13
+ name: 'Test inc',
14
+ phone: '12345678',
15
+ email: 'test@mala.ru',
16
+ address: '10, State st',
17
+ web: 'hoohle.com'
18
+ )
19
+ end
20
+
21
+ subject { company.params }
22
+
23
+ specify { is_expected.to include(name: 'Test inc') }
24
+ specify { is_expected.to include(type: 'contact') }
25
+
26
+ it "contains email property" do
27
+ prop = subject[:custom_fields].detect { |p| p[:id] == "1460591" }
28
+ expect(prop).not_to be_nil
29
+ expect(prop[:values].first[:value]).to eq 'test@mala.ru'
30
+ expect(prop[:values].first[:enum]).to eq 'WORK'
31
+ end
32
+
33
+ it "contains phone property" do
34
+ prop = subject[:custom_fields].detect { |p| p[:id] == "1460589" }
35
+ expect(prop).not_to be_nil
36
+ expect(prop[:values].first[:value]).to eq '12345678'
37
+ expect(prop[:values].first[:enum]).to eq 'WORK'
38
+ end
39
+
40
+ it "contains address property" do
41
+ prop = subject[:custom_fields].detect { |p| p[:id] == "1460597" }
42
+ expect(prop).not_to be_nil
43
+ expect(prop[:values].first[:value]).to eq '10, State st'
44
+ end
45
+
46
+ it "contains web property" do
47
+ prop = subject[:custom_fields].detect { |p| p[:id] == "1460593" }
48
+ expect(prop).not_to be_nil
49
+ expect(prop[:values].first[:value]).to eq 'hoohle.com'
50
+ end
51
+ end
52
+
53
+ describe "#save" do
54
+ let(:company) { Amorail::AmoCompany.new(name: "test") }
55
+
56
+ before { company_create_stub(Amorail.config.api_endpoint) }
57
+
58
+ it "should set id after create" do
59
+ company.save!
60
+ expect(company.id).to eq 101
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,104 @@
1
+ require "spec_helper"
2
+
3
+ describe Amorail::AmoContact do
4
+ before { mock_api }
5
+
6
+ let(:contact) { Amorail::AmoContact.new(name: "test") }
7
+
8
+ describe "validations" do
9
+ it { should validate_presence_of(:name)}
10
+ end
11
+
12
+ describe "#params" do
13
+ let(:contact) do
14
+ Amorail::AmoContact.new(
15
+ name: 'Tester',
16
+ company_name: 'Test inc',
17
+ phone: '12345678',
18
+ email: 'test@mala.ru',
19
+ position: 'CEO'
20
+ )
21
+ end
22
+
23
+ subject { contact.params }
24
+
25
+ specify { is_expected.to include(name: 'Tester') }
26
+ specify { is_expected.to include(company_name: 'Test inc') }
27
+
28
+ it "contains email property" do
29
+ prop = subject[:custom_fields].detect { |p| p[:id] == "1460591" }
30
+ expect(prop).not_to be_nil
31
+ expect(prop[:values].first[:value]).to eq 'test@mala.ru'
32
+ expect(prop[:values].first[:enum]).to eq 'MOB'
33
+ end
34
+
35
+ it "contains phone property" do
36
+ prop = subject[:custom_fields].detect { |p| p[:id] == "1460589" }
37
+ expect(prop).not_to be_nil
38
+ expect(prop[:values].first[:value]).to eq '12345678'
39
+ expect(prop[:values].first[:enum]).to eq 'WORK'
40
+ end
41
+
42
+ it "contains position property" do
43
+ prop = subject[:custom_fields].detect { |p| p[:id] == "1460587" }
44
+ expect(prop).not_to be_nil
45
+ expect(prop[:values].first[:value]).to eq 'CEO'
46
+ end
47
+ end
48
+
49
+ describe ".find" do
50
+ before { contact_find_stub(Amorail.config.api_endpoint, 101) }
51
+ before { contact_find_stub(Amorail.config.api_endpoint, 102, false) }
52
+
53
+ it "loads entity" do
54
+ obj = Amorail::AmoContact.find(101)
55
+ expect(obj.id).to eq 101
56
+ expect(obj.company_name).to eq "Foo Inc."
57
+ expect(obj.email).to eq "foo@tb.com"
58
+ expect(obj.phone).to eq "1111 111 111"
59
+ end
60
+
61
+ it "returns nil" do
62
+ obj = Amorail::AmoContact.find(102)
63
+ expect(obj).to be_falsey
64
+ end
65
+
66
+ it "raise error" do
67
+ expect { Amorail::AmoContact.find!(102) }
68
+ .to raise_error(Amorail::AmoEntity::RecordNotFound)
69
+ end
70
+ end
71
+
72
+ describe "#save" do
73
+ before { contact_create_stub(Amorail.config.api_endpoint) }
74
+
75
+ it "set id after create" do
76
+ contact.save!
77
+ expect(contact.id).to eq 101
78
+ end
79
+ end
80
+
81
+ describe "#update" do
82
+ before { contact_create_stub(Amorail.config.api_endpoint) }
83
+
84
+ it "update params" do
85
+ contact.save!
86
+ contact.name = "foo"
87
+
88
+ contact_update_stub(Amorail.config.api_endpoint)
89
+ expect(contact.save!).to be_truthy
90
+ expect(contact.name).to eq "foo"
91
+ end
92
+
93
+ it "raise error if id is blank?" do
94
+ obj = Amorail::AmoContact.new
95
+ expect { obj.update!(name: 'Igor') }.to raise_error
96
+ end
97
+
98
+ it "raise error" do
99
+ obj = Amorail::AmoContact.new
100
+ expect { obj.update!(id: 101, name: "Igor") }
101
+ .to(raise_error)
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,42 @@
1
+ require "spec_helper"
2
+ require "webmock/rspec"
3
+
4
+ describe "spec for Amorail Properties" do
5
+ before(:each) { mock_api }
6
+
7
+ let(:prop) {Amorail.properties}
8
+
9
+ it "should parse companies hash" do
10
+ expect(prop.company.phone.present?).to be_truthy
11
+ expect(prop.company.phone.is_a?(Amorail::Property::PropertyItem)).to be_truthy
12
+ expect(prop.company.phone.id.present?).to be_truthy
13
+ expect(prop.company.data["phone"].data["id"]).to eq prop.company.phone.id
14
+
15
+ expect(prop.company.phone.id).to eq "1460589"
16
+ expect(prop.company.address.id).to eq "1460597"
17
+ expect(prop.company.email.id).to eq "1460591"
18
+ expect(prop.company.web.id).to eq "1460593"
19
+ end
20
+
21
+ it "should parse contacts hash" do
22
+ expect(prop.contacts.email.present?).to be_truthy
23
+ expect(prop.contacts.im.is_a?(Amorail::Property::PropertyItem)).to be_truthy
24
+ expect(prop.contacts.im.id.present?).to be_truthy
25
+ expect(prop.contacts.data["im"].data["id"]).to eq prop.contacts.im.id
26
+
27
+ expect(prop.contacts.im.id).to eq "1460595"
28
+ expect(prop.contacts.position.id).to eq "1460587"
29
+ expect(prop.contacts.phone.id).to eq "1460589"
30
+ expect(prop.contacts.email.id).to eq "1460591"
31
+ end
32
+
33
+ it "should parse leads hash" do
34
+ expect(prop.leads.statuses["Первичный контакт"].id).to eq "8195972"
35
+ expect(prop.leads.statuses["Успешно реализовано"].id).to eq "142"
36
+ end
37
+
38
+ it "should parse task types" do
39
+ expect(prop.tasks.follow_up.id).to eq 1
40
+ expect(prop.tasks["CALL"].id).to eq 1
41
+ end
42
+ end
@@ -0,0 +1,51 @@
1
+ require "spec_helper"
2
+
3
+ describe Amorail::AmoEntity do
4
+ before { mock_api }
5
+
6
+ let(:entity) { Amorail::AmoEntity.new }
7
+
8
+ describe "#params" do
9
+ let(:now) { Time.now }
10
+
11
+ subject { entity.params }
12
+
13
+ specify { is_expected.to include(:last_modified) }
14
+ specify { is_expected.not_to include(
15
+ :id, :request_id, :responsible_user_id, :date_create)
16
+ }
17
+
18
+ context "with some values" do
19
+ let(:entity) do
20
+ Amorail::AmoEntity.new(
21
+ responsible_user_id: 2,
22
+ last_modified: now
23
+ )
24
+ end
25
+
26
+ specify { is_expected.to include(responsible_user_id: 2) }
27
+ specify { is_expected.to include(last_modified: now.to_i) }
28
+ specify { is_expected.not_to include(
29
+ :id, :request_id, :date_create)
30
+ }
31
+ end
32
+
33
+ context "with all values" do
34
+ let(:entity) do
35
+ Amorail::AmoEntity.new(
36
+ id: 100,
37
+ request_id: 1,
38
+ responsible_user_id: 2,
39
+ date_create: now,
40
+ last_modified: now
41
+ )
42
+ end
43
+
44
+ specify { is_expected.to include(id: 100) }
45
+ specify { is_expected.to include(request_id: 1) }
46
+ specify { is_expected.to include(responsible_user_id: 2) }
47
+ specify { is_expected.to include(date_create: now.to_i) }
48
+ specify { is_expected.to include(last_modified: now.to_i) }
49
+ end
50
+ end
51
+ end