infopark_webcrm_sdk 1.0.0.rc3

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,61 @@
1
+ module Crm; module Core
2
+ # +ItemEnumerator+ provides methods for accessing items identified by their ID.
3
+ # It implements {#each} and includes the
4
+ # {http://ruby-doc.org/core/Enumerable.html Enumerable} mixin,
5
+ # which provides methods such as +#map+, +#select+ or +#take+.
6
+ # @api public
7
+ class ItemEnumerator
8
+ include Enumerable
9
+ include Core::Mixins::Inspectable
10
+ inspectable :length, :total
11
+
12
+ # Returns the IDs of the items to enumerate.
13
+ # @return [Array<String>]
14
+ # @api public
15
+ attr_reader :ids
16
+
17
+ # If the ItemEnumerator is the result of a search, it returns the total number of search hits.
18
+ # Otherwise, it returns {#length}.
19
+ # @return [Fixnum]
20
+ # @api public
21
+ attr_reader :total
22
+
23
+ def initialize(ids, total: nil)
24
+ @ids = ids
25
+ @total = total || ids.length
26
+ end
27
+
28
+ # Iterates over the {#ids} and fetches the corresponding items on demand.
29
+ # @overload each
30
+ # Calls the block once for each item, passing this item as a parameter.
31
+ # @yieldparam item [BasicResource]
32
+ # @return [void]
33
+ # @overload each
34
+ # If no block is given, an {http://ruby-doc.org/core/Enumerator.html enumerator}
35
+ # is returned instead.
36
+ # @return [Enumerator<BasicResource>]
37
+ # @raise [Errors::ResourceNotFound] if at least one of the IDs could not be found.
38
+ # @api public
39
+ def each(&block)
40
+ return enum_for(:each) unless block_given?
41
+
42
+ server_limit = 100
43
+ @ids.each_slice(server_limit) do |sliced_ids|
44
+ RestApi.instance.get('mget', {'ids' => sliced_ids}).map do |item|
45
+ block.call "Crm::#{item['base_type']}".constantize.new(item)
46
+ end
47
+ end
48
+ end
49
+
50
+ # Returns the number of items.
51
+ # Prefer this method over +Enumerable#count+
52
+ # because +#length+ doesn't fetch the items and therefore is faster than +Enumerable#count+.
53
+ # @return [Fixnum]
54
+ # @api public
55
+ def length
56
+ @ids.length
57
+ end
58
+
59
+ alias_method :size, :length
60
+ end
61
+ end; end
@@ -0,0 +1,41 @@
1
+ require 'action_dispatch'
2
+
3
+ module Crm; module Core
4
+ class LogSubscriber < ActiveSupport::LogSubscriber
5
+ def logger
6
+ self.class.logger.presence or super
7
+ end
8
+
9
+ def request(event)
10
+ info { "#{event.payload[:method].to_s.upcase} #{event.payload[:resource_path]}" }
11
+ request_payload = event.payload[:request_payload]
12
+ if request_payload.present?
13
+ debug { " request body: #{parameter_filter.filter(request_payload)}" }
14
+ end
15
+ end
16
+
17
+ def response(event)
18
+ r = event.payload[:response]
19
+ info {
20
+ " #{r.code} #{r.message} #{r.body.to_s.length} (total: #{event.duration.round(1)}ms)"
21
+ }
22
+ debug {
23
+ response_payload = MultiJson.load(r.body)
24
+ " response body: #{parameter_filter.filter(response_payload)}"
25
+ }
26
+ end
27
+
28
+ def establish_connection(event)
29
+ debug {
30
+ attempt = event.payload[:attempt]
31
+ " Establishing connection on attempt #{attempt} (#{event.duration.round(1)}ms)"
32
+ }
33
+ end
34
+
35
+ private
36
+
37
+ def parameter_filter
38
+ @parameter_filter ||= ::ActionDispatch::Http::ParameterFilter.new(['password'])
39
+ end
40
+ end
41
+ end; end
@@ -0,0 +1,135 @@
1
+ module Crm; module Core; module Mixins
2
+ # +AttributeProvider+ provides multiple ways to access the attributes of an item.
3
+ # All attributes are available using {#[]}, {#attributes}
4
+ # or a method named like the attribute.
5
+ # @example
6
+ # contact
7
+ # # => Crm::Contact
8
+ #
9
+ # contact.first_name
10
+ # # => "John"
11
+ #
12
+ # contact['first_name']
13
+ # # => "John"
14
+ #
15
+ # contact[:first_name]
16
+ # # => "John"
17
+ #
18
+ # contact.unknown_attribute
19
+ # # => raises NoMethodError
20
+ #
21
+ # contact['unknown_attribute']
22
+ # # => nil
23
+ #
24
+ # contact.attributes
25
+ # # => {
26
+ # # ...
27
+ # # 'first_name' => 'John',
28
+ # # ...
29
+ # # }
30
+ # @api public
31
+ module AttributeProvider
32
+ def initialize(attributes = nil)
33
+ load_attributes(attributes || {})
34
+
35
+ super()
36
+ end
37
+
38
+ # Makes all attributes accessible as methods.
39
+ def method_missing(method_name, *args)
40
+ return self[method_name] if has_attribute?(method_name)
41
+ [
42
+ "#{method_name}_id",
43
+ "#{method_name.to_s.singularize}_ids",
44
+ ].each do |id_name|
45
+ return Crm.find(self[id_name]) if has_attribute?(id_name)
46
+ end
47
+
48
+ super
49
+ end
50
+
51
+ # Motivation see http://blog.marc-andre.ca/2010/11/15/methodmissing-politely/
52
+ def respond_to_missing?(method_name, *)
53
+ return true if has_attribute?(method_name)
54
+ return true if has_attribute?("#{method_name}_id")
55
+ return true if has_attribute?("#{method_name.to_s.singularize}_ids")
56
+
57
+ super
58
+ end
59
+
60
+ def methods(*args)
61
+ super | @extra_methods
62
+ end
63
+
64
+ # Returns the value associated with +attribute_name+.
65
+ # Returns +nil+ if not found.
66
+ # @example
67
+ # contact['first_name']
68
+ # # => "John"
69
+ #
70
+ # contact[:first_name]
71
+ # # => "John"
72
+ #
73
+ # contact['nonexistent']
74
+ # # => nil
75
+ # @param attribute_name [String, Symbol]
76
+ # @return [Object, nil]
77
+ # @api public
78
+ def [](attribute_name)
79
+ @attrs[attribute_name.to_s]
80
+ end
81
+
82
+ # Returns the hash of all attribute names and their values.
83
+ # @return [HashWithIndifferentAccess]
84
+ # @api public
85
+ def attributes
86
+ @attrs
87
+ end
88
+
89
+ # Returns the value before type cast.
90
+ # Returns +nil+ if attribute_name not found.
91
+ # @example
92
+ # contact[:created_at]
93
+ # # => 2012-05-07 17:15:00 +0200
94
+ #
95
+ # contact.raw(:created_at)
96
+ # # => "2012-05-07T15:15:00+00:00"
97
+ # @return [Object, nil]
98
+ # @api public
99
+ def raw(attribute_name)
100
+ @raw_attrs[attribute_name] || @attrs[attribute_name]
101
+ end
102
+
103
+ protected
104
+
105
+ def load_attributes(attributes)
106
+ @raw_attrs = HashWithIndifferentAccess.new
107
+ @attrs = attributes.each_with_object(HashWithIndifferentAccess.new) do |(key, value), hash|
108
+ if key.ends_with?('_at')
109
+ @raw_attrs[key] = value
110
+ value = begin
111
+ Time.parse(value.to_s).in_time_zone
112
+ rescue ArgumentError
113
+ nil
114
+ end
115
+ end
116
+ hash[key] = value
117
+ end
118
+ @extra_methods = []
119
+ @attrs.keys.each do |key|
120
+ key = key.to_s
121
+ @extra_methods << key.to_sym
122
+ @extra_methods << key.gsub(/_id$/, '').to_sym if key.ends_with?('_id')
123
+ @extra_methods << key.gsub(/_ids$/, '').pluralize.to_sym if key.ends_with?('_ids')
124
+ end
125
+ @attrs.freeze
126
+ self
127
+ end
128
+
129
+ private
130
+
131
+ def has_attribute?(attribute_name)
132
+ @attrs.has_key?(attribute_name.to_s)
133
+ end
134
+ end
135
+ end; end; end
@@ -0,0 +1,98 @@
1
+ module Crm; module Core; module Mixins
2
+ # +ChangeLoggable+ provides access to the change log of a resource.
3
+ # A {ChangeLoggable::Change change log entry} contains the +before+ and +after+ values
4
+ # of all attributes that were changed by an update.
5
+ # It also includes the author (+changed_by+) and the timestamp (+changed_at+) of the change.
6
+ # @example
7
+ # contact
8
+ # # => Crm::Contact
9
+ #
10
+ # changes = contact.changes
11
+ # # => Array<ChangeLoggable::Change>
12
+ #
13
+ # change = changes.first
14
+ # # => ChangeLoggable::Change
15
+ #
16
+ # change.changed_by
17
+ # # => 'john_smith'
18
+ #
19
+ # change.changed_at
20
+ # # => Time
21
+ #
22
+ # change.details.keys
23
+ # # => ['email', 'locality']
24
+ #
25
+ # detail = change.details[:email]
26
+ # # => ChangeLoggable::Change::Detail
27
+ #
28
+ # detail.before
29
+ # # => old@example.com
30
+ #
31
+ # detail.after
32
+ # # => new@example.com
33
+ # @api public
34
+ module ChangeLoggable
35
+ # +Change+ represents a single change log entry contained in
36
+ # {ChangeLoggable#changes item.changes}.
37
+ # See {Crm::Core::Mixins::ChangeLoggable ChangeLoggable} for details.
38
+ # @api public
39
+ class Change
40
+ include Core::Mixins::AttributeProvider
41
+
42
+ def initialize(raw_change)
43
+ change = raw_change.dup
44
+ change['details'] = change['details'].each_with_object({}) do |(attr_name, detail), hash|
45
+ hash[attr_name] = Detail.new(detail)
46
+ end
47
+ super(change)
48
+ end
49
+
50
+ # @!attribute [r] changed_at
51
+ # Returns the timestamp of the change to the item.
52
+ # @return [Time]
53
+ # @api public
54
+
55
+ # @!attribute [r] changed_by
56
+ # Returns the login of the API user who made the change.
57
+ # @return [String]
58
+ # @api public
59
+
60
+ # @!attribute [r] details
61
+ # Returns the details of the change
62
+ # (i.e. +before+ and +after+ of every changed attribute)
63
+ # @return [Array<Detail>]
64
+ # @api public
65
+
66
+ # +Detail+ represents a single detail of a
67
+ # {Crm::Core::Mixins::ChangeLoggable::Change change},
68
+ # which can be accessed by means of {Change#details change.details}.
69
+ # See {Crm::Core::Mixins::ChangeLoggable ChangeLoggable} for details.
70
+ # @api public
71
+ class Detail
72
+ # @!attribute [r] before
73
+ # Returns the value before the change.
74
+ # @return [Object]
75
+ # @api public
76
+
77
+ # @!attribute [r] after
78
+ # Returns the value after the change.
79
+ # @return [Object]
80
+ # @api public
81
+
82
+ # flush yardoc!!!!
83
+ include Core::Mixins::AttributeProvider
84
+ end
85
+ end
86
+
87
+ # Returns the most recent changes made to this item.
88
+ # @param limit [Fixnum] the number of changes to return at most.
89
+ # Maximum: +100+. Default: +10+.
90
+ # @return [Array<Change>]
91
+ # @api public
92
+ def changes(limit: 10)
93
+ RestApi.instance.get("#{path}/changes", {"limit" => limit})['results'].map do |change|
94
+ Change.new(change)
95
+ end
96
+ end
97
+ end
98
+ end; end; end
@@ -0,0 +1,28 @@
1
+ module Crm; module Core; module Mixins
2
+ # +Findable+ lets you fetch an item using {ClassMethods#find .find}.
3
+ # @example
4
+ # Crm::Contact.find('e70a7123f499c5e0e9972ab4dbfb8fe3')
5
+ # # => Crm::Contact
6
+ # @api public
7
+ module Findable
8
+ extend ActiveSupport::Concern
9
+
10
+ # @api public
11
+ module ClassMethods
12
+ # Returns the requested item.
13
+ # @param id [String] the ID of the item.
14
+ # @return [BasicResource]
15
+ # @raise [Errors::ResourceNotFound]
16
+ # if the ID could not be found or the base type did not match.
17
+ # @api public
18
+ def find(id)
19
+ if id.blank?
20
+ raise Crm::Errors::ResourceNotFound.new(
21
+ "Items could not be found.", [id])
22
+ end
23
+ new({'id' => id}).reload
24
+ end
25
+ end
26
+ # @!parse extend ClassMethods
27
+ end
28
+ end; end; end
@@ -0,0 +1,27 @@
1
+ module Crm; module Core; module Mixins
2
+ module Inspectable
3
+ extend ActiveSupport::Concern
4
+
5
+ def inspect
6
+ field_values = self.class.inspectable_fields.map do |field|
7
+ value = self.send(field).inspect
8
+
9
+ "#{field}=#{value}"
10
+ end
11
+
12
+ "#<#{self.class.name} #{field_values.join(', ')}>"
13
+ end
14
+
15
+ included do
16
+ cattr_accessor :inspectable_fields
17
+
18
+ self.inspectable_fields = []
19
+ end
20
+
21
+ module ClassMethods
22
+ def inspectable(*fields)
23
+ self.inspectable_fields = *fields
24
+ end
25
+ end
26
+ end
27
+ end; end; end
@@ -0,0 +1,17 @@
1
+ module Crm; module Core; module Mixins
2
+ # +MergeAndDeletable+ provides the common {#merge_and_delete} method
3
+ # for {Account accounts} and {Contact contacts}.
4
+ # @api public
5
+ module MergeAndDeletable
6
+ # Assigns the items associated with this item to the account or contact
7
+ # whose ID is +merge_into_id+. Afterwards, the current item is deleted.
8
+ # @param merge_into_id [String]
9
+ # the ID of the account or contact to which the associated items are assigned.
10
+ # @return [self] the deleted item.
11
+ # @api public
12
+ def merge_and_delete(merge_into_id)
13
+ load_attributes(
14
+ RestApi.instance.post("#{path}/merge_and_delete", {"merge_into_id" => merge_into_id}))
15
+ end
16
+ end
17
+ end; end; end
@@ -0,0 +1,102 @@
1
+ require "active_support/concern"
2
+
3
+ module Crm; module Core; module Mixins
4
+ # +Modifiable+ is a collection of methods that are used to {ClassMethods#create .create},
5
+ # {#update}, {#delete}, and {#undelete} an Infopark WebCRM item.
6
+ # @api public
7
+ module Modifiable
8
+ extend ActiveSupport::Concern
9
+
10
+ # @api public
11
+ module ClassMethods
12
+ # Creates a new item using the given +attributes+.
13
+ # @example
14
+ # Crm::Contact.create({
15
+ # language: 'en',
16
+ # last_name: 'Smith',
17
+ # })
18
+ # # => Crm::Contact
19
+ # @param attributes [Hash{String, Symbol => String}] the attributes of the new item.
20
+ # @return [BasicResource] the created item.
21
+ # @raise [Errors::InvalidKeys] if +attributes+ contains unknown attribute names.
22
+ # @raise [Errors::InvalidValues] if +attributes+ contains incorrect values.
23
+ # @api public
24
+ def create(attributes = {})
25
+ new(RestApi.instance.post(path, attributes))
26
+ end
27
+ end
28
+ # @!parse extend ClassMethods
29
+
30
+ # Updates the attributes of this item.
31
+ # @example
32
+ # contact.last_name
33
+ # # => 'Smith'
34
+ #
35
+ # contact.locality
36
+ # # => 'New York'
37
+ #
38
+ # contact.update({locality: 'Boston'})
39
+ # # => Crm::Contact
40
+ #
41
+ # contact.last_name
42
+ # # => 'Smith'
43
+ #
44
+ # contact.locality
45
+ # # => 'Boston'
46
+ # @param attributes [Hash{String, Symbol => String}] the new attributes.
47
+ # @return [self] the updated item.
48
+ # @raise [Errors::InvalidKeys] if +attributes+ contains unknown attribute names.
49
+ # @raise [Errors::InvalidValues] if +attributes+ contains incorrect values.
50
+ # @raise [Errors::ResourceConflict] if the item has been changed concurrently.
51
+ # {Core::BasicResource#reload Reload} it, review the changes and retry.
52
+ # @api public
53
+ def update(attributes = {})
54
+ load_attributes(RestApi.instance.put(path, attributes, if_match_header))
55
+ end
56
+
57
+ # Soft-deletes this item (i.e. marks it as deleted).
58
+ #
59
+ # The deleted item can be {#undelete undeleted}.
60
+ # @example
61
+ # contact.deleted?
62
+ # # => false
63
+ #
64
+ # contact.delete
65
+ # # => Crm::Contact
66
+ #
67
+ # contact.deleted?
68
+ # # => true
69
+ # @return [self] the deleted item.
70
+ # @raise [Errors::ResourceConflict] if the item has been changed concurrently.
71
+ # {Core::BasicResource#reload Reload} it, review the changes and retry.
72
+ # @api public
73
+ def delete
74
+ load_attributes(RestApi.instance.delete(path, nil, if_match_header))
75
+ end
76
+
77
+ # Undeletes this deleted item.
78
+ # @example
79
+ # contact.deleted?
80
+ # # => true
81
+ #
82
+ # contact.undelete
83
+ # # => Crm::Contact
84
+ #
85
+ # contact.deleted?
86
+ # # => false
87
+ # @return [self] the undeleted item.
88
+ # @api public
89
+ def undelete
90
+ load_attributes(RestApi.instance.put("#{path}/undelete", {}))
91
+ end
92
+
93
+ # Returns +true+ if the item was deleted (i.e. +item.deleted_at+ is not empty).
94
+ # @return [Boolean]
95
+ # @api public
96
+ def deleted?
97
+ self['deleted_at'].present?
98
+ end
99
+
100
+ alias_method :destroy, :delete
101
+ end
102
+ end; end; end
@@ -0,0 +1,88 @@
1
+ module Crm; module Core; module Mixins
2
+ # +Searchable+ provides several methods related to searching.
3
+ # @api public
4
+ module Searchable
5
+ extend ActiveSupport::Concern
6
+
7
+ # @api public
8
+ module ClassMethods
9
+ # Returns the item of this base type that was created first.
10
+ # @return [BasicResource]
11
+ # @api public
12
+ def first
13
+ search_configurator.
14
+ sort_by('created_at').
15
+ limit(1).
16
+ perform_search.
17
+ first
18
+ end
19
+
20
+ # Returns an {Crm::Core::ItemEnumerator enumerator} for iterating over all items
21
+ # of this base type. The items are sorted by +created_at+.
22
+ # @param include_deleted [Boolean] whether to include deleted items. Default: +false+.
23
+ # @return [ItemEnumerator]
24
+ # @api public
25
+ def all(include_deleted: false)
26
+ search_configurator.
27
+ sort_by('created_at').
28
+ unlimited.
29
+ include_deleted(include_deleted).
30
+ perform_search
31
+ end
32
+
33
+ # Returns a new {Crm::Core::SearchConfigurator SearchConfigurator} set to the given
34
+ # filter (+field+, +condition+, +value+). Additionally, it is limited
35
+ # to this base type and can be further refined using chainable methods.
36
+ # This method is equivalent to +search_configurator.and(field, condition, value)+.
37
+ # See {SearchConfigurator#and} for parameters and examples.
38
+ # @return [SearchConfigurator]
39
+ # @api public
40
+ def where(field, condition, value = nil)
41
+ search_configurator.and(field, condition, value)
42
+ end
43
+
44
+ # Returns a new {Crm::Core::SearchConfigurator SearchConfigurator} set to the given
45
+ # negated filter (+field+, +condition+, +value+). Additionally, it is limited
46
+ # to this base type and can be further refined using chainable methods.
47
+ # This method is equivalent to +search_configurator.and_not(field, condition, value)+.
48
+ # See {SearchConfigurator#and_not} for parameters and examples.
49
+ # @return [SearchConfigurator]
50
+ # @api public
51
+ def where_not(field, condition, value = nil)
52
+ search_configurator.and_not(field, condition, value)
53
+ end
54
+
55
+ # Returns a new {Crm::Core::SearchConfigurator SearchConfigurator} set to the given
56
+ # +query+. Additionally, it is limited
57
+ # to this base type and can be further refined using chainable methods.
58
+ # This method is equivalent to +search_configurator.query(query)+.
59
+ # See {SearchConfigurator#query} for examples.
60
+ # @return [SearchConfigurator]
61
+ # @api public
62
+ def query(query)
63
+ search_configurator.query(query)
64
+ end
65
+
66
+ # Returns a new {Crm::Core::SearchConfigurator SearchConfigurator} limited
67
+ # to this base type. It can be further refined using chainable methods.
68
+ # @return [SearchConfigurator]
69
+ # @api public
70
+ def search_configurator
71
+ SearchConfigurator.new({
72
+ filters: filters_for_base_type,
73
+ })
74
+ end
75
+
76
+ private
77
+
78
+ def filters_for_base_type
79
+ [{
80
+ field: 'base_type',
81
+ condition: 'equals',
82
+ value: base_type,
83
+ }]
84
+ end
85
+ end
86
+ # @!parse extend ClassMethods
87
+ end
88
+ end; end; end
@@ -0,0 +1,6 @@
1
+ module Crm; module Core
2
+ # @api public
3
+ module Mixins
4
+ Crm.autoload_module(self, File.expand_path(__FILE__))
5
+ end
6
+ end; end