infopark_webcrm_sdk 1.0.0.rc3

Sign up to get free protection for your applications and to get access to all the features.
data/README.md ADDED
@@ -0,0 +1,113 @@
1
+ # Infopark WebCRM SDK
2
+
3
+ [![Build Status](https://magnum.travis-ci.com/infopark/webcrm_sdk.svg?token=iixzKo9dcgfko6CA7rmm&branch=dev)](https://magnum.travis-ci.com/infopark/webcrm_sdk)
4
+
5
+ [Infopark WebCRM](https://infopark.com/) is a cloud-based CRM.
6
+ The Infopark WebCRM SDK makes CRM content available to your Ruby application.
7
+ It is a client of the Infopark WebCRM REST API v2.
8
+
9
+ This SDK lets you access and manipulate accounts and contacts, for example, perform searches, etc.
10
+
11
+ ## Installation
12
+
13
+ Add `infopark_webcrm_sdk` to your `Gemfile`:
14
+
15
+ gem 'infopark_webcrm_sdk'
16
+
17
+ Install the gem with [Bundler](http://bundler.io/):
18
+
19
+ bundle install
20
+
21
+ ## Configuration
22
+
23
+ You can obtain your API credentials from the [Infopark Console](https://console.infopark.net/).
24
+
25
+ ```ruby
26
+ require 'infopark_webcrm_sdk'
27
+
28
+ Crm.configure do |config|
29
+ config.tenant = 'my_tenant'
30
+ config.login = 'my_login'
31
+ config.api_key = 'my_api_key'
32
+ end
33
+ ```
34
+
35
+ ## Example Usage
36
+
37
+ The Infopark WebCRM SDK provides the following Infopark WebCRM resources to your Ruby application:
38
+
39
+ * {Crm::Account}
40
+ * {Crm::Activity}
41
+ * {Crm::Collection}
42
+ * {Crm::Contact}
43
+ * {Crm::EventContact}
44
+ * {Crm::Event}
45
+ * {Crm::Mailing}
46
+ * {Crm::TemplateSet}
47
+ * {Crm::Type}
48
+
49
+ Most of these classes have methods such as {Crm::Core::Mixins::Findable::ClassMethods#find find}, {Crm::Core::Mixins::Modifiable::ClassMethods#create create}, {Crm::Core::Mixins::Modifiable#update update}, {Crm::Core::Mixins::Searchable::ClassMethods#query query}, and {Crm::Core::Mixins::Searchable::ClassMethods#where where}.
50
+
51
+ ### Creating a Contact
52
+
53
+ ```ruby
54
+ contact = Crm::Contact.create({
55
+ first_name: 'John',
56
+ last_name: 'Smith',
57
+ language: 'en',
58
+ locality: 'New York',
59
+ })
60
+ # => Crm::Contact
61
+
62
+ contact.first_name
63
+ # => 'John'
64
+
65
+ contact.id
66
+ # => 'e70a7123f499c5e0e9972ab4dbfb8fe3'
67
+ ```
68
+
69
+ ### Fetching and Updating a Contact
70
+
71
+ ```ruby
72
+ # Retrieve the contact by ID
73
+ contact = Crm::Contact.find('e70a7123f499c5e0e9972ab4dbfb8fe3')
74
+ # => Crm::Contact
75
+
76
+ contact.last_name
77
+ # => 'Smith'
78
+
79
+ contact.locality
80
+ # => 'New York'
81
+
82
+ # Change this contact's locality
83
+ contact.update({locality: 'Boston'})
84
+ # => Crm::Contact
85
+
86
+ contact.last_name
87
+ # => 'Smith'
88
+
89
+ contact.locality
90
+ # => 'Boston'
91
+ ```
92
+
93
+ ### Searching for Contacts
94
+
95
+ ```ruby
96
+ Crm::Contact.where(:login, :is_term, 'root').first
97
+ # => Crm::Contact
98
+
99
+ Crm::Contact.where(:locality, :is_term, 'Boston').
100
+ and(:last_name, :is_prefix, 'S').
101
+ sort_by(:last_name).
102
+ sort_order(:desc).
103
+ limit(2).
104
+ map(&:last_name)
105
+ # => ['Smith', 'Simpson']
106
+ ```
107
+
108
+ ## License
109
+
110
+ Copyright (c) 2015 [Infopark AG](https://infopark.com).
111
+
112
+ This software can be used and modified in accordance with the GNU Lesser General Public License
113
+ (LGPL-3.0). Please refer to LICENSE for details.
data/UPGRADE.md ADDED
@@ -0,0 +1,507 @@
1
+ # Upgrade Guide
2
+
3
+ This guide assists you in upgrading from the **Infopark WebCRM Connector** (which uses API 1) to **Infopark WebCRM SDK** (which uses API 2).
4
+
5
+ You can upgrade incrementally to the Infopark WebCRM SDK.
6
+ This means that you don't need to upgrade your whole project at once.
7
+ Instead, include both the Infopark WebCRM Connector and the Infopark WebCRM SDK in your project and replace the code use case by use case.
8
+ Finally, remove the Infopark WebCRM Connector from the project.
9
+
10
+ To begin with, put both gems into the `Gemfile` of your project:
11
+
12
+ ```ruby
13
+ gem "infopark_crm_connector"
14
+ gem "infopark_webcrm_sdk"
15
+ ```
16
+
17
+ ## Configuring the Infopark WebCRM SDK
18
+
19
+ Then, configure the Infopark WebCRM SDK to use your personal credentials when connecting to the Infopark WebCRM REST API.
20
+ You probably have a `webcrm.rb` or `crm_connector.rb` initializer file in your project which already contains the Infopark WebCRM Connector configuration.
21
+ Add the following lines to this file.
22
+
23
+ ```ruby
24
+ require 'infopark_webcrm_sdk'
25
+
26
+ Crm.configure do |config|
27
+ config.tenant = ENV["CRM_TENANT"]
28
+ config.login = ENV["CRM_LOGIN"]
29
+ config.api_key = ENV["CRM_API_KEY"]
30
+ end
31
+ ```
32
+
33
+ Instead of reading the configuration values from the `ENV` you may also want to read them from a configuration file.
34
+
35
+ ## General remarks
36
+
37
+ ### The Crm namespace
38
+
39
+ All Infopark WebCRM SDK classes live under the `Crm` namespace whereas Infopark WebCRM Connector classes live under the `Infopark::Crm` namespace.
40
+
41
+ ```ruby
42
+ # old:
43
+ Infopark::Crm::Contact.find(contact_id)
44
+ # new:
45
+ Crm::Contact.find(contact_id)
46
+ ```
47
+
48
+ In case the resource could not be found, a `Crm::Errors::ResourceNotFound` error is raised.
49
+ Infopark WebCRM SDK will never raise an `ActiveResource::ResourceNotFound` error.
50
+ More on errors in the next section.
51
+
52
+ ### Errors tell exactly what went wrong
53
+
54
+ For example, when trying to fetch a resource that could not be found, the `Crm::Errors::ResourceNotFound` error has a `missing_ids` property telling you the list of IDs that could not be found.
55
+
56
+ ```ruby
57
+ begin
58
+ Crm::Contact.find("foo")
59
+ rescue Crm::Errors::ResourceNotFound => e
60
+ e.message # => "Items could not be found. Missing IDs: foo"
61
+ e.missing_ids # => ["foo"]
62
+ end
63
+ ```
64
+
65
+ Or, when the Infopark WebCRM backend refuses to save a resource due to unsatisfied validation rules, the error contains the details.
66
+
67
+ ```ruby
68
+ begin
69
+ Crm::Contact.create({
70
+ first_name: "John",
71
+ gender: "i don't know",
72
+ })
73
+ rescue Crm::Errors::InvalidValues => e
74
+ e.message # => "Validate the parameters and try again. gender is not included in the list: N, M, F, language is not included in the list, and last_name can't be blank."
75
+ e.validation_errors
76
+ # => [
77
+ # {
78
+ # "attribute" => "gender",
79
+ # "code" => "inclusion",
80
+ # "message" => "gender is not included in the list: N, M, F"
81
+ # },
82
+ # {
83
+ # "attribute" => "language",
84
+ # "code" => "inclusion",
85
+ # "message" => "language is not included in the list"
86
+ # },
87
+ # {
88
+ # "attribute" => "last_name",
89
+ # "code" => "blank",
90
+ # "message" => "last_name can't be blank"
91
+ # }
92
+ # ]
93
+ end
94
+ ```
95
+
96
+ Validation errors contain the names of the attributes whose values are invalid, symbolic codes that you can use to look up custom translations of the error messages and English messages for convenience.
97
+ The symbolic codes are the Rails validation error codes where possible, e.g. `blank` and `inclusion`.
98
+ Infopark WebCRM adds its own codes such as `invalid_comment_contact_id` and `liquid_syntax_error`.
99
+
100
+ Furthermore, when updating a resource, Infopark WebCRM complains about attributes not defined in the resource type.
101
+
102
+ ```ruby
103
+ begin
104
+ Crm::Contact.create({
105
+ foo: "bar",
106
+ })
107
+ rescue Crm::Errors::InvalidKeys => e
108
+ e.message # => "Unknown keys specified. foo is unknown."
109
+ e.validation_errors
110
+ # => [
111
+ # {
112
+ # "attribute" => "foo",
113
+ # "code" => "unknown",
114
+ # "message" => "foo is unknown"
115
+ # }
116
+ # ]
117
+ end
118
+ ```
119
+
120
+ However, trying to set an internal read-only attribute such as `created_at` is ignored.
121
+
122
+ A list of errors and their properties can be found in the Infopark WebCRM SDK YARD docs.
123
+
124
+ ### Write form models including their custom logic for every use case
125
+
126
+ One big change in the Infopark WebCRM SDK is that model classes are no longer based on `ActiveResource`.
127
+ They are much simpler now, and they don't behave like `ActiveModel` objects any more.
128
+ Instead of modifying a resource locally and then asking it to `save` itself, the Infopark WebCRM SDK requires the resource to be changed by passing the new attribute values to the `update` method.
129
+ Analogously, for creating a new resource, pass all of its attributes to `create`.
130
+
131
+ ```ruby
132
+ contact = Crm::Contact.create({
133
+ first_name: "John",
134
+ last_name: "Smith",
135
+ language: "en",
136
+ locality: "New York",
137
+ gender: "M",
138
+ })
139
+ contact.locality # => "New York"
140
+
141
+ contact.update({
142
+ locality: "Hamburg",
143
+ language: "de",
144
+ })
145
+ contact.locality # => "Hamburg"
146
+ ```
147
+
148
+ This means that you can no longer use them as form objects in your controllers and views.
149
+ You'd rather write a plain Ruby class for every use case, implement the `ActiveModel` interface and your custom logic in this class, and delegate to Infopark WebCRM SDK models for communicating with the Infopark WebCRM REST API.
150
+ For further details, see the API docs.
151
+
152
+ We recommend using the `active_attr` gem to simplify implementing the `ActiveModel` interface.
153
+
154
+ ### Accessing attributes
155
+
156
+ You can access the attributes of an Infopark WebCRM SDK resource by means of method calls or the `[]` operator.
157
+
158
+ ```ruby
159
+ contact = Crm::Contact.find(contact_id)
160
+ contact.last_name # => "Smith"
161
+ contact[:last_name] # => "Smith"
162
+ contact["last_name"] # => "Smith"
163
+ ```
164
+
165
+ ### Every attribute has a sane value
166
+
167
+ Attributes not set have a default value according to their attribute type.
168
+ Assuming that an Infopark WebCRM contact has no value for the `first_name` string attribute, reading it results in `""`, the empty string, not `nil`.
169
+ The same rule applies to numbers, boolean, arrays and hashes.
170
+
171
+ The only exception to this rule are date attributes whose values are in fact `nil` if they are not set.
172
+ If a date attribute has a value, it is automatically parsed and returned as a `Time` instance in the local timezone.
173
+
174
+ The consequence of this is that you don't need to write a lot of `nil` checks and no longer need to parse dates yourself.
175
+
176
+ ### Renamed attributes
177
+
178
+ A couple of attributes were renamed in API 2.
179
+ You can access attributes using the Infopark WebCRM Connector and the old attribute name or the Infopark WebCRM SDK and the new attribute name.
180
+
181
+ The following attributes were renamed for all resources.
182
+
183
+ * `kind` => `type_id` (e.g. `"contact-form"`)
184
+ * `type` => `base_type` (e.g. `"Activity"`)
185
+
186
+ Further attribute name changes are documented below.
187
+
188
+ ### Authentication
189
+
190
+ Infopark WebCRM SDK offers two methods for authenticating a contact.
191
+
192
+ 1. `Crm::Contact.authenticate` returns the authenticated contact or `nil`.
193
+ 2. `Crm::Contact.authenticate!` returns the authenticated contact or raises a `Crm::Errors::AuthenticationFailed` error.
194
+
195
+ ### All resources have a changelog
196
+
197
+ The changes history known from the WebCRM GUI is now also available in the new API.
198
+ You can retrieve up to 100 changelog entries of any resource.
199
+ They are sorted in reverse chronological order.
200
+
201
+ ```ruby
202
+ contact = Crm::Contact.find(contact_id)
203
+ contact.changes.each do |change|
204
+ change.changed_at # => 2014-11-26 15:37:27 +0100
205
+ change.changed_by # => "root"
206
+ change.details(limit: 1).each do |attr_name, detail|
207
+ attr_name # => "email"
208
+ detail.before # => "john.smith@example.org"
209
+ detail.after # => "johann.schmidt@example.org"
210
+ end
211
+ end
212
+ ```
213
+
214
+ ### All resources can be undeleted
215
+
216
+ Previously, there was no way to undelete a deleted resource by means of the API.
217
+ With the API 2, all resources have an `undelete` method.
218
+
219
+ ```ruby
220
+ contact = Crm::Contact.find(contact_id)
221
+ contact.deleted? # => true
222
+ contact.undelete
223
+ contact.deleted? # => false
224
+ ```
225
+
226
+ ## The individual resources: changes & features
227
+
228
+ ### Search
229
+
230
+ Infopark WebCRM comes with a global search.
231
+ All resource types are searched simultaneously.
232
+ The following example finds both accounts and contacts located in Rome.
233
+ Both accounts and contacts have a locality attribute.
234
+
235
+ ```ruby
236
+ Crm.search(
237
+ filters: [
238
+ {field: 'locality', condition: 'equals', value: 'Rome'},
239
+ ],
240
+ sort_by: 'updated_at'
241
+ )
242
+ ```
243
+
244
+ In order to limit the search hits to contacts, add another filter.
245
+
246
+ ```ruby
247
+ Crm.search(
248
+ filters: [
249
+ {field: 'locality', condition: 'equals', value: 'Rome'},
250
+ {field: 'base_type', condition: 'equals', value: 'Contact'},
251
+ ],
252
+ sort_by: 'updated_at'
253
+ )
254
+ ```
255
+
256
+ The power of the new search API comes into play when building more complex queries.
257
+ Based on this low-level `search` method, the SDK lets you compose search queries by means of method chaining.
258
+ Start with the `where` method, then append methods such as `and`, `limit`, and `sort_by` to further refine the search.
259
+ Finally, run the search by iterating over the results:
260
+
261
+ ```ruby
262
+ Crm::Activity.
263
+ where("type_id", :equals, "support-case").
264
+ and("contact_id", :equals, contact_id).
265
+ and_not("state", :equals, "closed").
266
+ query("I have got a problem").
267
+ limit(10).
268
+ sort_by("updated_at").desc.
269
+ each do |activity|
270
+ puts activity.title
271
+ end
272
+ ```
273
+
274
+ Further examples to illustrate the new search features:
275
+
276
+ ```ruby
277
+ # old:
278
+ Infopark::Crm::Contact.search(params: {q: "something"}).take(10)
279
+ Infopark::Crm::Contact.search(params: {login: login}).first
280
+ # new:
281
+ Crm::Contact.query("something").limit(10).to_a
282
+ Crm::Contact.where(:login, :equals, login).limit(1).first
283
+ ```
284
+
285
+ ### Accounts
286
+
287
+ The `merge_and_delete` method merges two accounts and deletes the one for which the method was called.
288
+ This feature is already known from the WebCRM GUI.
289
+ All items associated with the account to be deleted, e.g. activities, are transfered to the target account.
290
+
291
+ ```ruby
292
+ account_to_delete = Crm::Account.find(account_id)
293
+ account_to_delete.merge_and_delete(target_account_id)
294
+ account_to_delete.deleted? # => true
295
+ ```
296
+
297
+ ### Activities
298
+
299
+ When associating an attachment with an activity comment, you can pass an open file instead of an attachment ID.
300
+ In this case, the Infopark WebCRM SDK automatically uploads the file content using the attachments API.
301
+ It then references this attachment ID in the `comment_attachments` field.
302
+
303
+ ```ruby
304
+ activity = Crm::Activity.create({
305
+ type_id: 'support-case',
306
+ state: 'created',
307
+ title: 'I have a question',
308
+ comment_notes: 'Please see the attached screenshot.',
309
+ comment_attachments: [File.new('screenshot.jpg')],
310
+ })
311
+ activity.comments.last.attachments.first.download_url
312
+ # => "https://.../screenshot.jpg"
313
+ ```
314
+
315
+ The following activity attributes were renamed in API 2:
316
+
317
+ * `appointment_dtend_at` => `dtend_at`
318
+ * `appointment_dtstart_at` => `dtstart_at`
319
+ * `appointment_location` => `location`
320
+ * `contact_id` => `contact_ids`
321
+
322
+ ### AttachmentStore
323
+
324
+ Actions related to attachments were streamlined.
325
+ The Ruby class responsible for attachments is now `AttachmentStore`.
326
+ The main class methods of the attachment store are `generate_upload_permission` and `generate_download_url`.
327
+
328
+ When uploading attachments using Ruby, we recommend utilizing the implicit uploading facility of activity comments as a shortcut.
329
+
330
+ ### Collections
331
+
332
+ The `Collection` API is new.
333
+ An Infopark WebCRM collection is a saved search.
334
+ The search filters as well as the search results are part of the collection.
335
+
336
+ ```ruby
337
+ collection = Crm::Collection.create({
338
+ title: 'My Collection',
339
+ collection_type: 'contact',
340
+ filters: [
341
+ [
342
+ {field: 'contact.last_name', condition: 'equals', value: 'Smith'}
343
+ ]
344
+ ]
345
+ })
346
+ ```
347
+
348
+ To execute such a saved search, call `compute`.
349
+
350
+ ```ruby
351
+ collection.compute
352
+ ```
353
+
354
+ The results are persisted and can be accessed via `output_items`.
355
+
356
+ ```ruby
357
+ collection.output_items.each do |contact|
358
+ contact.last_name # => "Smith"
359
+ end
360
+ ```
361
+
362
+ For details, see the Infopark WebCRM SDK docs.
363
+
364
+ ### Contacts
365
+
366
+ `Contact#merge_and_delete` works analogously to `Account#merge_and_delete`.
367
+ Refer to the "Accounts" section for details.
368
+
369
+ The following contact attributes were renamed in API 2:
370
+
371
+ * `password_request_at` => `password_requested_at`
372
+
373
+ ### EventContacts
374
+
375
+ With the exception of the renamed common attributes (see above), nothing has changed.
376
+
377
+ ### Events
378
+
379
+ The following event attributes were renamed in API 2:
380
+
381
+ * `custom_attributes` (array) => `attribute_definitions` (hash) + `attribute_order` (array)
382
+
383
+ ### Mailings
384
+
385
+ You can render the plain-text and HTML templates of a mailing as they would look for a given contact ID by means of `Mailing#render_preview`.
386
+
387
+ ```ruby
388
+ mailing = Crm::Mailing.create({
389
+ email_from: "Marketing <marketing@example.com>",
390
+ email_reply_to: "marketing-replyto@example.com",
391
+ email_subject: "Invitation to our exhibition",
392
+ html_body: '<h1>Welcome {{contact.first_name}} {{contact.last_name}}</h1>',
393
+ text_body: 'Welcome {{contact.first_name}} {{contact.last_name}}',
394
+ title: 'Our Annual Exhibition',
395
+ type_id: 'newsletter',
396
+ })
397
+ mailing.render_preview(contact_id)
398
+ # => {
399
+ # "email_from" => "Marketing <marketing@example.com>",
400
+ # "email_reply_to" => "marketing-replyto@example.com",
401
+ # "email_subject" => "Invitation to our exhibition",
402
+ # "email_to" => "john.smith@example.org",
403
+ # "html_body" => "<h1>Welcome John Smith</h1>",
404
+ # "text_body" => "Welcome John Smith",
405
+ # }
406
+ ```
407
+
408
+ `Mailing#send_single_email` sends an e-mail to an individual contact after the mailing has already been released.
409
+
410
+ `Mailing#send_me_a_proof_email` sends an e-mail to the authenticated API user, i.e. to the user you configured in the `Crm.configure` block.
411
+
412
+ `Mailing#release` releases a mailing and sends e-mails to all recipients.
413
+
414
+ ```ruby
415
+ recipients = Crm::Collection.create({
416
+ title: 'Newsletter Recipients',
417
+ collection_type: 'contact',
418
+ filters: [
419
+ [
420
+ {field: 'contact.locality', condition: 'equals', value: "Berlin"},
421
+ ]
422
+ ]
423
+ })
424
+ recipients.compute
425
+
426
+ newsletter_mailing = Crm::Mailing.create({
427
+ title: 'Newsletter',
428
+ type_id: 'newsletter',
429
+ collection_id: recipients.id,
430
+ email_from: "Marketing <marketing@example.com>",
431
+ })
432
+ newsletter_mailing.release
433
+ ```
434
+
435
+ The following mailing attributes were renamed in API 2:
436
+
437
+ * `contact_collection_id` => `collection_id`
438
+ * `dtstart_at` => `planned_release_at`
439
+ * `body` => `text_body`
440
+
441
+
442
+ ### TemplateSet
443
+
444
+ Templates can no longer be accessed using the `Infopark::Crm::System.templates` hash.
445
+ Instead, they are now a normal attribute, `templates`, of the `Crm::TemplateSet.singleton` resource.
446
+
447
+ ```ruby
448
+ ts = Crm::TemplateSet.singleton
449
+ ts.templates['password_request_email_from'] # => "bounces@example.com"
450
+ ts.updated_at # => 2015-02-16 11:19:28 +0100
451
+ ts.updated_by # => "root"
452
+ ```
453
+
454
+ Compared to the Infopark WebCRM Connector, setting a subset of templates is sufficient.
455
+ Templates that are not specified are no longer deleted.
456
+ Set templates explicitly to `nil` to remove them from the template set.
457
+
458
+ ```ruby
459
+ ts = Crm::TemplateSet.singleton
460
+ ts.update(templates: {
461
+ 'password_request_email_from' => nil,
462
+ 'foo' => 'bar',
463
+ })
464
+ ts.templates['password_request_email_from'] # => nil
465
+ ts.templates['foo'] # => "bar"
466
+ ```
467
+
468
+ ### Types
469
+
470
+ Types were formerly known as custom types.
471
+
472
+ `Crm::Type.all` retrieves a list of all types, i.e. custom types and built-in types.
473
+
474
+ Every type has an `id`, e.g. `contact` or `support-case`, which is referenced as `type_id` by instances of these classes.
475
+
476
+ `Crm::Type.find` loads the type by its ID.
477
+
478
+ ```ruby
479
+ contact = Crm::Contact.find(contact_id)
480
+ contact.type_id # => "contact"
481
+
482
+ contact_type = Crm::Type.find("contact")
483
+ contact_type.base_type # => "Type"
484
+ contact_type.id # => "contact"
485
+ contact_type.item_base_type # => "Contact"
486
+ contact_type.attribute_definitions
487
+ # => {
488
+ # "custom_my_foo" => {
489
+ # "title" => "my foo attribute",
490
+ # "mandatory" => false,
491
+ # "max_length" => 80,
492
+ # "attribute_type" => "string",
493
+ # "create" => true,
494
+ # "update" => true,
495
+ # "read" => true,
496
+ # }
497
+ # }
498
+
499
+ support_case = Crm::Activity.find(support_case_id)
500
+ support_case.type_id # => "support-case"
501
+
502
+ support_case_type = Crm::Type.find('support-case')
503
+ support_case_type.base_type # => "Type"
504
+ support_case_type.id # => "support-case"
505
+ support_case_type.item_base_type # => "Activity"
506
+ support_case_type.attribute_definitions # => {}
507
+ ```