pipeline_dealers 0.0.1
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.
- data/.gitignore +17 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/Guardfile +5 -0
- data/LICENSE.txt +22 -0
- data/README.md +64 -0
- data/Rakefile +1 -0
- data/examples/companies_create_and_destroy.rb +6 -0
- data/examples/companies_list_all.rb +9 -0
- data/examples/company_details.rb +19 -0
- data/examples/company_notes.rb +17 -0
- data/examples/company_people.rb +15 -0
- data/examples/config.rb +54 -0
- data/examples/find_person.rb +9 -0
- data/examples/list_custom_fields.rb +19 -0
- data/examples/people_create_and_destroy.rb +6 -0
- data/examples/people_list_all.rb +9 -0
- data/lib/pipeline_dealers/backend/base/backend.rb +13 -0
- data/lib/pipeline_dealers/backend/base/collection.rb +111 -0
- data/lib/pipeline_dealers/backend/base.rb +2 -0
- data/lib/pipeline_dealers/backend/http/backend.rb +64 -0
- data/lib/pipeline_dealers/backend/http/collection.rb +32 -0
- data/lib/pipeline_dealers/backend/http/connection.rb +69 -0
- data/lib/pipeline_dealers/backend/http/fetcher.rb +78 -0
- data/lib/pipeline_dealers/backend/http.rb +5 -0
- data/lib/pipeline_dealers/backend/test/backend.rb +34 -0
- data/lib/pipeline_dealers/backend/test/collection.rb +34 -0
- data/lib/pipeline_dealers/backend/test.rb +3 -0
- data/lib/pipeline_dealers/client.rb +23 -0
- data/lib/pipeline_dealers/delegator.rb +61 -0
- data/lib/pipeline_dealers/error/connection.rb +17 -0
- data/lib/pipeline_dealers/error/custom_field.rb +12 -0
- data/lib/pipeline_dealers/error/invalid_attribute.rb +29 -0
- data/lib/pipeline_dealers/error.rb +12 -0
- data/lib/pipeline_dealers/limits.rb +7 -0
- data/lib/pipeline_dealers/model/company/custom_field.rb +9 -0
- data/lib/pipeline_dealers/model/company.rb +46 -0
- data/lib/pipeline_dealers/model/custom_field.rb +94 -0
- data/lib/pipeline_dealers/model/has_custom_fields.rb +62 -0
- data/lib/pipeline_dealers/model/note.rb +37 -0
- data/lib/pipeline_dealers/model/person/custom_field.rb +9 -0
- data/lib/pipeline_dealers/model/person.rb +63 -0
- data/lib/pipeline_dealers/model.rb +158 -0
- data/lib/pipeline_dealers/test.rb +10 -0
- data/lib/pipeline_dealers/version.rb +3 -0
- data/lib/pipeline_dealers.rb +24 -0
- data/pipeline_dealers.gemspec +39 -0
- data/spec/acceptance/companies/creation_spec.rb +30 -0
- data/spec/acceptance/companies/custom_fields_spec.rb +8 -0
- data/spec/acceptance/companies/updating_spec.rb +36 -0
- data/spec/acceptance/people/creation_spec.rb +20 -0
- data/spec/pipeline_dealers/backend/http/backend_spec.rb +48 -0
- data/spec/pipeline_dealers/backend/http/collection_spec.rb +158 -0
- data/spec/pipeline_dealers/model/custom_field_spec.rb +129 -0
- data/spec/pipeline_dealers/model/has_custom_fields_spec.rb +115 -0
- data/spec/pipeline_dealers/model_spec.rb +33 -0
- data/spec/pipeline_dealers/test_client_spec.rb +80 -0
- data/spec/support/test_model.rb +6 -0
- data/todo.md +13 -0
- metadata +291 -0
@@ -0,0 +1,34 @@
|
|
1
|
+
require_relative "collection"
|
2
|
+
|
3
|
+
module PipelineDealers
|
4
|
+
module Backend
|
5
|
+
class Test
|
6
|
+
attr_reader :items
|
7
|
+
|
8
|
+
def cache key
|
9
|
+
yield
|
10
|
+
end
|
11
|
+
|
12
|
+
def initialize(client)
|
13
|
+
@client = client
|
14
|
+
@items = Hash.new([])
|
15
|
+
@last_id = 41
|
16
|
+
end
|
17
|
+
|
18
|
+
def collection(options)
|
19
|
+
Collection.new(self, options)
|
20
|
+
end
|
21
|
+
|
22
|
+
def save(collection, model)
|
23
|
+
@items[model.class] << model
|
24
|
+
if model.id.nil?
|
25
|
+
model.send(:instance_variable_set, :@id, @last_id += 1)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def destroy(collection, to_remove)
|
30
|
+
@items[to_remove.class].reject! { |model| model == to_remove }
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module PipelineDealers
|
2
|
+
module Backend
|
3
|
+
class Test
|
4
|
+
class Collection < Backend::Base::Collection
|
5
|
+
extend Delegator
|
6
|
+
|
7
|
+
delegate :count, :first, to: :items
|
8
|
+
|
9
|
+
def initialize(backend, options)
|
10
|
+
super(backend, options)
|
11
|
+
end
|
12
|
+
|
13
|
+
def find id
|
14
|
+
items.select { |model| model.id == id }.first
|
15
|
+
end
|
16
|
+
|
17
|
+
def each &operation
|
18
|
+
items.each &operation
|
19
|
+
end
|
20
|
+
|
21
|
+
def collect &operation
|
22
|
+
items.collect &operation
|
23
|
+
end
|
24
|
+
|
25
|
+
protected
|
26
|
+
|
27
|
+
def items
|
28
|
+
@backend.items[@model_klass]
|
29
|
+
@backend.items[@model_klass]
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module PipelineDealers
|
2
|
+
# This class models the API client.
|
3
|
+
class Client
|
4
|
+
attr_reader :api_key
|
5
|
+
attr_reader :backend
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
@backend = (options.delete(:backend) || Backend::Http).new(options)
|
9
|
+
end
|
10
|
+
|
11
|
+
def companies
|
12
|
+
@companies ||= @backend.collection(client: self, model_klass: Model::Company, custom_fields: { model_klass: Model::Company::CustomField, cached: true, cache_key: "custom_company_fields" })
|
13
|
+
end
|
14
|
+
|
15
|
+
def people
|
16
|
+
@people ||= @backend.collection(client: self, model_klass: Model::Person, custom_fields: {model_klass: Model::Person::CustomField, cached: true, cache_key: "custom_person_fields"})
|
17
|
+
end
|
18
|
+
|
19
|
+
def notes
|
20
|
+
@notes ||= @backend.collection(client: self, model_klass: Model::Note)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# Copied from activesupport/lib/active_support/core_ext/module/delegation.rb
|
2
|
+
|
3
|
+
module PipelineDealers
|
4
|
+
module Delegator
|
5
|
+
def delegate(*methods)
|
6
|
+
options = methods.pop
|
7
|
+
unless options.is_a?(Hash) && to = options[:to]
|
8
|
+
raise ArgumentError, 'Delegation needs a target. Supply an options hash with a :to key as the last argument (e.g. delegate :hello, to: :greeter).'
|
9
|
+
end
|
10
|
+
|
11
|
+
prefix, allow_nil = options.values_at(:prefix, :allow_nil)
|
12
|
+
|
13
|
+
if prefix == true && to =~ /^[^a-z_]/
|
14
|
+
raise ArgumentError, 'Can only automatically set the delegation prefix when delegating to a method.'
|
15
|
+
end
|
16
|
+
|
17
|
+
method_prefix = \
|
18
|
+
if prefix
|
19
|
+
"#{prefix == true ? to : prefix}_"
|
20
|
+
else
|
21
|
+
''
|
22
|
+
end
|
23
|
+
|
24
|
+
file, line = caller.first.split(':', 2)
|
25
|
+
line = line.to_i
|
26
|
+
|
27
|
+
to = to.to_s
|
28
|
+
to = 'self.class' if to == 'class'
|
29
|
+
|
30
|
+
methods.each do |method|
|
31
|
+
# Attribute writer methods only accept one argument. Makes sure []=
|
32
|
+
# methods still accept two arguments.
|
33
|
+
definition = (method =~ /[^\]]=$/) ? 'arg' : '*args, &block'
|
34
|
+
|
35
|
+
if allow_nil
|
36
|
+
module_eval(<<-EOS, file, line - 2)
|
37
|
+
def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
|
38
|
+
if #{to} || #{to}.respond_to?(:#{method}) # if client || client.respond_to?(:name)
|
39
|
+
#{to}.#{method}(#{definition}) # client.name(*args, &block)
|
40
|
+
end # end
|
41
|
+
end # end
|
42
|
+
EOS
|
43
|
+
else
|
44
|
+
exception = %(raise "#{self}##{method_prefix}#{method} delegated to #{to}.#{method}, but #{to} is nil: \#{self.inspect}")
|
45
|
+
|
46
|
+
module_eval(<<-EOS, file, line - 1)
|
47
|
+
def #{method_prefix}#{method}(#{definition}) # def customer_name(*args, &block)
|
48
|
+
#{to}.#{method}(#{definition}) # client.name(*args, &block)
|
49
|
+
rescue NoMethodError # rescue NoMethodError
|
50
|
+
if #{to}.nil? # if client.nil?
|
51
|
+
#{exception} # # add helpful message to the exception
|
52
|
+
else # else
|
53
|
+
raise # raise
|
54
|
+
end # end
|
55
|
+
end # end
|
56
|
+
EOS
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module PipelineDealers
|
2
|
+
class Error
|
3
|
+
class Connection < Error
|
4
|
+
end
|
5
|
+
|
6
|
+
class Connection
|
7
|
+
class Unprocessable < Error::Connection
|
8
|
+
attr_reader :errors
|
9
|
+
|
10
|
+
def initialize(errors)
|
11
|
+
@errors = errors
|
12
|
+
@message = "Couldn't save to the server: #{errors.inspect}"
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module PipelineDealers
|
2
|
+
class Error
|
3
|
+
class InvalidAttribute < Error
|
4
|
+
attr_reader :attribute_name
|
5
|
+
|
6
|
+
def initialize(klass, attribute_name)
|
7
|
+
@klass = klass
|
8
|
+
@attribute_name = attribute_name
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class InvalidAttributeName < InvalidAttribute
|
13
|
+
def initialize(klass, attribute_name)
|
14
|
+
super
|
15
|
+
@message = "The attribute #{attribute_name.inspect} is not known by #{klass.inspect}!"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
class InvalidAttributeValue < InvalidAttribute
|
20
|
+
attr_reader :value
|
21
|
+
|
22
|
+
def initialize(klass, attribute_name, value)
|
23
|
+
super(klass, attribute_name)
|
24
|
+
@value = value
|
25
|
+
@message = "The attribute #{attribute_name.inspect} does not accept the value #{value.inspect} (on model #{klass.inspect})"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
module PipelineDealers
|
2
|
+
class Error < Exception
|
3
|
+
attr_reader :message
|
4
|
+
def to_s
|
5
|
+
@message
|
6
|
+
end
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
require "pipeline_dealers/error/connection"
|
11
|
+
require "pipeline_dealers/error/custom_field"
|
12
|
+
require "pipeline_dealers/error/invalid_attribute"
|
@@ -0,0 +1,46 @@
|
|
1
|
+
module PipelineDealers
|
2
|
+
class Model
|
3
|
+
class Company < Model
|
4
|
+
include HasCustomFields
|
5
|
+
self.collection_url = "companies"
|
6
|
+
self.attribute_name = "company"
|
7
|
+
|
8
|
+
attrs :name,
|
9
|
+
:description,
|
10
|
+
:email,
|
11
|
+
:web,
|
12
|
+
:fax,
|
13
|
+
:address_1,
|
14
|
+
:address_2,
|
15
|
+
:city,
|
16
|
+
:postal_code,
|
17
|
+
:country,
|
18
|
+
:phone1,
|
19
|
+
:phone2,
|
20
|
+
:phone3,
|
21
|
+
:phone4,
|
22
|
+
:phone1_desc,
|
23
|
+
:phone2_desc, :phone3_desc,
|
24
|
+
:phone4_desc,
|
25
|
+
:created_at,
|
26
|
+
:import_id
|
27
|
+
|
28
|
+
|
29
|
+
# Read only
|
30
|
+
attrs :won_deals_total,
|
31
|
+
:image_thumb_url,
|
32
|
+
:updated_at,
|
33
|
+
:total_pipeline,
|
34
|
+
:state,
|
35
|
+
read_only: true
|
36
|
+
|
37
|
+
def people
|
38
|
+
@client.people.where(company_id: self.id)
|
39
|
+
end
|
40
|
+
|
41
|
+
def notes
|
42
|
+
@client.notes.where(company_id: self.id)
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,94 @@
|
|
1
|
+
module PipelineDealers
|
2
|
+
class Model
|
3
|
+
class CustomField < Model
|
4
|
+
attr_reader :dropdown_entries
|
5
|
+
|
6
|
+
attrs :name,
|
7
|
+
:field_type,
|
8
|
+
:is_required,
|
9
|
+
:custom_field_label_dropdown_entries,
|
10
|
+
readonly: true
|
11
|
+
|
12
|
+
def process_attributes
|
13
|
+
@dropdown_entries = @attributes.delete(:custom_field_label_dropdown_entries)
|
14
|
+
end
|
15
|
+
|
16
|
+
def decode(model, value)
|
17
|
+
coder_for(model).decode(value)
|
18
|
+
end
|
19
|
+
|
20
|
+
def encode(model, value)
|
21
|
+
coder_for(model).encode(value)
|
22
|
+
end
|
23
|
+
|
24
|
+
protected
|
25
|
+
|
26
|
+
def coder_for(model)
|
27
|
+
coder_klass = Coder::Types[field_type.to_sym]
|
28
|
+
if coder_klass.nil?
|
29
|
+
raise "Unkown custom field type '#{field_type.inspect}'"
|
30
|
+
else
|
31
|
+
return coder_klass.new(model, self)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
class Coder
|
36
|
+
def initialize(model, field)
|
37
|
+
@model, @field = model, field
|
38
|
+
end
|
39
|
+
|
40
|
+
class Identity < Coder
|
41
|
+
def encode(value) value end
|
42
|
+
def decode(value) value end
|
43
|
+
end
|
44
|
+
|
45
|
+
class Dropdown < Coder
|
46
|
+
def encode value
|
47
|
+
find :value, of: value, and_return: :id
|
48
|
+
end
|
49
|
+
|
50
|
+
def decode id
|
51
|
+
find :id, of: id, and_return: :value
|
52
|
+
end
|
53
|
+
|
54
|
+
protected
|
55
|
+
|
56
|
+
def find key, options
|
57
|
+
key = key.to_s
|
58
|
+
value = options[:of]
|
59
|
+
and_return = options[:and_return].to_s
|
60
|
+
|
61
|
+
entry = @field.dropdown_entries.find { |entry| entry[key] == value }
|
62
|
+
|
63
|
+
if entry
|
64
|
+
return entry[and_return]
|
65
|
+
else
|
66
|
+
raise Error::InvalidAttributeValue.new(@model, @field.name, value)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
class MultiSelect < Coder
|
72
|
+
def encode values
|
73
|
+
coder = Dropdown.new(@model, @field)
|
74
|
+
values.collect { |value| coder.encode(value) }
|
75
|
+
end
|
76
|
+
|
77
|
+
def decode ids
|
78
|
+
coder = Dropdown.new(@model, @field)
|
79
|
+
ids.collect { |value| coder.decode(value) }
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
Types = {
|
84
|
+
numeric: Identity,
|
85
|
+
text: Identity,
|
86
|
+
currency: Identity,
|
87
|
+
dropdown: Dropdown,
|
88
|
+
multi_select: MultiSelect,
|
89
|
+
multi_association: Identity # Does not work yet
|
90
|
+
}
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
require "active_support/concern"
|
2
|
+
|
3
|
+
module PipelineDealers
|
4
|
+
class Model
|
5
|
+
module HasCustomFields
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
attr_reader :custom_fields
|
10
|
+
end
|
11
|
+
|
12
|
+
CUSTOM_FIELD_PREFIX = "custom_label_"
|
13
|
+
CUSTOM_FIELD_REGEX = /#{CUSTOM_FIELD_PREFIX}(\d+)/
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
17
|
+
def custom_field_collection
|
18
|
+
if @collection.respond_to?(:custom_fields)
|
19
|
+
@collection.custom_fields.all
|
20
|
+
else
|
21
|
+
raise "Expected collection of model '#{@collection.model_klass}' to have the custom field collection (custom_fields method)!"
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def process_attributes
|
26
|
+
@custom_fields = pld2human_fields(@attributes.delete(:custom_fields))
|
27
|
+
end
|
28
|
+
|
29
|
+
def attributes_for_saving attributes
|
30
|
+
attributes.merge("custom_fields" => human2pld_fields(@custom_fields))
|
31
|
+
end
|
32
|
+
|
33
|
+
def pld2human_fields attributes
|
34
|
+
attributes ||= {}
|
35
|
+
result = {}
|
36
|
+
|
37
|
+
attributes.each do |key, value|
|
38
|
+
field_id = CUSTOM_FIELD_REGEX.match(key)[1].to_i
|
39
|
+
field = custom_field_collection.select { |field| field.id == field_id }.first
|
40
|
+
|
41
|
+
raise Error::InvalidAttributeName.new(self, key) if field.nil?
|
42
|
+
result[field.name] = field.decode(self, value)
|
43
|
+
end
|
44
|
+
|
45
|
+
result
|
46
|
+
end
|
47
|
+
|
48
|
+
def human2pld_fields attributes
|
49
|
+
attributes ||= {}
|
50
|
+
result = HashWithIndifferentAccess.new
|
51
|
+
|
52
|
+
attributes.each do |name, value|
|
53
|
+
field = custom_field_collection.select { |field| field.name == name}.first
|
54
|
+
raise Error::InvalidAttributeName.new(self, name) if field.nil?
|
55
|
+
result[CUSTOM_FIELD_PREFIX + field.id.to_s] = field.encode(self, value)
|
56
|
+
end
|
57
|
+
|
58
|
+
result
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module PipelineDealers
|
2
|
+
class Model
|
3
|
+
class Note < Model
|
4
|
+
self.collection_url = "notes"
|
5
|
+
self.attribute_name = "note"
|
6
|
+
|
7
|
+
attrs :deal_id,
|
8
|
+
:person_id,
|
9
|
+
:company_id,
|
10
|
+
:title,
|
11
|
+
:created_by_user_id,
|
12
|
+
:created_by_user,
|
13
|
+
:note_category_id,
|
14
|
+
:content,
|
15
|
+
:note_category
|
16
|
+
|
17
|
+
# Read only
|
18
|
+
attrs :created_at,
|
19
|
+
:updated_at,
|
20
|
+
:deal,
|
21
|
+
:person,
|
22
|
+
:company,
|
23
|
+
read_only: true
|
24
|
+
|
25
|
+
alias_method :person_cache, :person
|
26
|
+
alias_method :company_cache, :company
|
27
|
+
|
28
|
+
def person
|
29
|
+
@client.people.find(self.person_id)
|
30
|
+
end
|
31
|
+
|
32
|
+
def company
|
33
|
+
@client.companies.find(self.company_id)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
module PipelineDealers
|
2
|
+
class Model
|
3
|
+
class Person < Model
|
4
|
+
include HasCustomFields
|
5
|
+
self.collection_url = "people"
|
6
|
+
self.attribute_name = "person"
|
7
|
+
|
8
|
+
attrs :first_name,
|
9
|
+
:last_name,
|
10
|
+
:phone,
|
11
|
+
:home_phone,
|
12
|
+
:mobile,
|
13
|
+
:position,
|
14
|
+
:website,
|
15
|
+
:email,
|
16
|
+
:email2,
|
17
|
+
:home_email,
|
18
|
+
:company_id,
|
19
|
+
:user_id,
|
20
|
+
:type,
|
21
|
+
:lead_status_id,
|
22
|
+
:lead_source_id,
|
23
|
+
:predefined_contacts_tag_ids,
|
24
|
+
:deal_ids,
|
25
|
+
:work_address_1,
|
26
|
+
:work_address_2,
|
27
|
+
:work_city,
|
28
|
+
:work_state,
|
29
|
+
:work_country,
|
30
|
+
:work_postal_code,
|
31
|
+
:home_address_1,
|
32
|
+
:home_address_2,
|
33
|
+
:home_city,
|
34
|
+
:home_state,
|
35
|
+
:home_country,
|
36
|
+
:home_postal_code,
|
37
|
+
:fax,
|
38
|
+
:facebook_url,
|
39
|
+
:linked_in_url,
|
40
|
+
:twitter,
|
41
|
+
:instant_message,
|
42
|
+
:created_at
|
43
|
+
|
44
|
+
|
45
|
+
# Read only
|
46
|
+
attrs :predefined_contacts_tags,
|
47
|
+
:won_deals_total,
|
48
|
+
:full_name,
|
49
|
+
:company_name,
|
50
|
+
:user,
|
51
|
+
:lead_status,
|
52
|
+
:lead_source,
|
53
|
+
:image_thumb_url,
|
54
|
+
:predefined_contacts_tags,
|
55
|
+
:updated_at,
|
56
|
+
:deals,
|
57
|
+
:company,
|
58
|
+
:viewed_at,
|
59
|
+
:total_pipeline,
|
60
|
+
read_only: true
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|