justrelate_sdk 1.0.0.rc1

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