capsulecrm-b 0.0.6

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.
Files changed (45) hide show
  1. data/.gitignore +4 -0
  2. data/Gemfile +10 -0
  3. data/README.rdoc +33 -0
  4. data/Rakefile +9 -0
  5. data/capsulecrm.gemspec +23 -0
  6. data/examples.rb +82 -0
  7. data/lib/capsulecrm/address.rb +23 -0
  8. data/lib/capsulecrm/base.rb +176 -0
  9. data/lib/capsulecrm/child.rb +24 -0
  10. data/lib/capsulecrm/child_collection.rb +14 -0
  11. data/lib/capsulecrm/collection.rb +14 -0
  12. data/lib/capsulecrm/contact.rb +27 -0
  13. data/lib/capsulecrm/custom_field.rb +41 -0
  14. data/lib/capsulecrm/email.rb +64 -0
  15. data/lib/capsulecrm/history.rb +32 -0
  16. data/lib/capsulecrm/history_item.rb +20 -0
  17. data/lib/capsulecrm/opportunity.rb +60 -0
  18. data/lib/capsulecrm/organisation.rb +41 -0
  19. data/lib/capsulecrm/party.rb +114 -0
  20. data/lib/capsulecrm/person.rb +126 -0
  21. data/lib/capsulecrm/phone.rb +15 -0
  22. data/lib/capsulecrm/record_not_found.rb +1 -0
  23. data/lib/capsulecrm/recorn_not_recognised.rb +1 -0
  24. data/lib/capsulecrm/tag.rb +12 -0
  25. data/lib/capsulecrm/version.rb +3 -0
  26. data/lib/capsulecrm/website.rb +19 -0
  27. data/lib/capsulecrm.rb +46 -0
  28. data/test/create_person_test.rb +36 -0
  29. data/test/fixtures/responses/create_person.yml +59 -0
  30. data/test/fixtures/responses/party_history.yml +45 -0
  31. data/test/fixtures/responses/party_tags.yml +26 -0
  32. data/test/fixtures/responses/person_find_all.yml +28 -0
  33. data/test/fixtures/responses/person_find_all_with_limit.yml +26 -0
  34. data/test/fixtures/responses/person_find_all_with_offset.yml +28 -0
  35. data/test/fixtures/responses/person_find_by_id.yml +102 -0
  36. data/test/fixtures/responses/update_person.yml +85 -0
  37. data/test/fixtures/responses/update_person_without_changes.yml +28 -0
  38. data/test/party_dot_find_test.rb +40 -0
  39. data/test/party_dot_history_test.rb +29 -0
  40. data/test/party_dot_tags_test.rb +30 -0
  41. data/test/person_dot_find_all_test.rb +48 -0
  42. data/test/person_dot_find_by_id_test.rb +61 -0
  43. data/test/test_helper.rb +52 -0
  44. data/test/update_person_test.rb +46 -0
  45. metadata +158 -0
data/.gitignore ADDED
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in capsulecrm.gemspec
4
+ gemspec
5
+
6
+ group :development do
7
+ gem 'webmock'
8
+ gem 'vcr'
9
+ end
10
+
data/README.rdoc ADDED
@@ -0,0 +1,33 @@
1
+ = CapsuleCRM
2
+
3
+ This is a ruby wrapper for the CapsuleCRM API. It is alpha software and should be considered a work in progress. It currently supports People & Organisations. There is support for CustomFields & Contacts but they are read-only.
4
+
5
+ == Installation
6
+
7
+ [sudo] gem install capsulecrm
8
+
9
+ gem 'capsulecrm' # rails
10
+ require 'capsulecrm' # non-rails
11
+
12
+ == Getting started
13
+
14
+ You will need to supply the gem with your CapsuleCRM API token and account name. The account name is the first part of your CapsuleCRM url:
15
+
16
+ CapsuleCRM.account_name = "test-account" # http://test-account.capsulecrm.com
17
+ CapsuleCRM.api_token = 'MY_API_TOKEN'
18
+ CapsuleCRM.initialize!
19
+
20
+ If you're using rails, you can put that in config/initializers/capsulecrm.rb
21
+
22
+
23
+ == Usage
24
+
25
+ Please see examples.rb for usage examples
26
+
27
+ == Feedback
28
+
29
+ Bug reports, feature requests and patches are welcome. Please create an issue on the github issue tracker.
30
+
31
+ == License
32
+
33
+ MIT License. Copyright 2011 Ahmed Adam
data/Rakefile ADDED
@@ -0,0 +1,9 @@
1
+ require 'bundler'
2
+ require 'rake/testtask'
3
+ Bundler::GemHelper.install_tasks
4
+
5
+ Rake::TestTask.new do |t|
6
+ t.libs << "test"
7
+ t.test_files = FileList['test/*_test.rb']
8
+ t.verbose = true
9
+ end
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "capsulecrm/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "capsulecrm-b"
7
+ s.version = CapsuleCRM::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Ahmed Adam", "dsimard", "FWMatt"]
10
+ s.email = ["ahmed.msgs@gmail.com", "dsimard@azanka.ca", "matt@futureworkshops.com"]
11
+ s.homepage = "https://github.com/dsimard/capsulecrm"
12
+ s.summary = %q{CapsuleCRM API Gem}
13
+ s.description = %q{CapsuleCRM API Gem}
14
+
15
+ s.add_dependency 'httparty', '~> 0.7'
16
+ s.add_dependency 'activemodel', '~> 3.0'
17
+ s.add_dependency 'activesupport', '~> 3.0'
18
+
19
+ s.files = `git ls-files`.split("\n")
20
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
21
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
22
+ s.require_paths = ["lib"]
23
+ end
data/examples.rb ADDED
@@ -0,0 +1,82 @@
1
+ # NOTE: CapsuleCRM::Person and CapsuleCRM::Organisation have virtually identically methods.
2
+ require 'capsulecrm'
3
+
4
+ # find by id
5
+ person = CapsuleCRM::Person.find 123
6
+
7
+ # or if you don't know what you are searching for Party
8
+ something = CapsuleCRM::Party.find 123
9
+ something.is?(:person)
10
+ something.is?(:organisation)
11
+
12
+ # find by email
13
+ person = CapsuleCRM::Person.find_by_email 'foo@example.com'
14
+
15
+ # find all
16
+ person = CapsuleCRM::Person.find :all, :offset => 10, :limit => 5
17
+
18
+ # find by search term (searches name, telephone number and searchable custom fields)
19
+ person = CapsuleCRM::Person.search 'fred', :offset => 10, :limit => 5
20
+
21
+ # CapsuleCRM::Person attributes
22
+ person.id
23
+ person.title
24
+ person.first_name
25
+ person.last_name
26
+ person.job_title
27
+ person.about
28
+
29
+ # Add a note to the history
30
+ person.add_history "This is a note"
31
+
32
+ # update a person object
33
+ person.first_name = "Homer"
34
+ person.last_name = "Simpson"
35
+ person.save # returns true/false
36
+
37
+ # create a person object
38
+ person = CapsuleCRM::Person.new
39
+ person.first_name = "Marge"
40
+ person.last_name = "Simpson"
41
+ person.save # returns true/false
42
+
43
+
44
+ # get the person's organisation
45
+ person.organisation # CapsuleCRM::Organisation
46
+
47
+ # CapsuleCRM::Organisation attributes:
48
+ organisation.about
49
+ organisation.name
50
+
51
+
52
+ # Contacts: CapsuleCRM::Phone (read-only)
53
+ person.phone_numbers # CapsuleCRM::Collection
54
+ person.phone_numbers.first.number # 01234 56789
55
+ person.phone_numbers.first.type # work
56
+
57
+ # Contacts: CapsuleCRM::Website (read-only)
58
+ party.websites # CapsuleCRM::Collection
59
+ party.websites.first.url # http://google.com
60
+ party.websites.first.web_address # http://google.com
61
+
62
+ # Contacts: CapsuleCRM::Email (read-only)
63
+ person.emails # CapsuleCRM::Collection
64
+ person.emails.first.address # 'foo@example.com'
65
+ person.emails.first.type # 'home'
66
+
67
+ # Contacts: CapsuleCRM::Address (read-only)
68
+ person.addresses # CapsuleCRM::Collection
69
+ person.addresses.first.street # 10 Somestreet
70
+ person.addresses.first.city # Manchester
71
+ person.addresses.first.state # Greater Manchester
72
+ person.addresses.first.zip # ME10 7TR
73
+ person.addresses.first.country # United Kingdom
74
+
75
+ # CapsuleCRM::CustomFields (read-only)
76
+ person.custom_fields # CapsuleCRM::Collection
77
+ person.custom_fields.first.label # 'Favourite colour'
78
+ person.custom_fields.first.value # 'Blue'
79
+
80
+ # CapsuleCRM::Tag (read-only)
81
+ person.tags # CapsuleCRM::Collection
82
+ person.tag_names # ["array", "of all", "tags", "as strings"]
@@ -0,0 +1,23 @@
1
+ class CapsuleCRM::Address < CapsuleCRM::Contact
2
+
3
+ attr_accessor :street
4
+ attr_accessor :city
5
+ attr_accessor :state
6
+ attr_accessor :zip
7
+ attr_accessor :country
8
+
9
+
10
+ # nodoc
11
+ def self.xml_map
12
+ map = {
13
+ 'street' => 'street',
14
+ 'city' => 'city',
15
+ 'state' => 'state',
16
+ 'zip' => 'zip',
17
+ 'country' => 'country'
18
+ }
19
+ super.merge map
20
+ end
21
+
22
+
23
+ end
@@ -0,0 +1,176 @@
1
+ module CapsuleCRM
2
+ class Base
3
+ include HTTParty
4
+ include ActiveModel::Dirty
5
+
6
+
7
+ # This is needed for PUT and POST requests because the capsule API returns html
8
+ # if the request is unsuccessful. HTTParty's default xlm parser crashes if fed
9
+ # html so this parser is used for PUT/POST
10
+ class Parser::Simple < HTTParty::Parser
11
+ def parse; body; end
12
+ end
13
+
14
+
15
+ # -- Attributes --
16
+ attr_accessor :id
17
+ attr_accessor :raw_data
18
+ @@last_response = nil
19
+
20
+
21
+ # -- HttpParty --
22
+ format :xml
23
+ headers 'User-Agent' => 'CapsuleCRM ruby gem'
24
+
25
+
26
+ # nodoc
27
+ def initialize(attributes={})
28
+ attributes.each do |name, value|
29
+ send("#{name}=", value)
30
+ end
31
+ changed_attributes.clear
32
+ end
33
+
34
+
35
+ # nodoc
36
+ def ==(other)
37
+ return false if other.nil?
38
+ id == other.id
39
+ end
40
+
41
+
42
+ # nodoc
43
+ def id
44
+ return nil if @id.nil?
45
+ @id.to_i
46
+ end
47
+
48
+
49
+ # nodoc
50
+ def errors
51
+ @errors ||= []
52
+ end
53
+
54
+
55
+ # nodoc
56
+ def new_record?
57
+ id.nil?
58
+ end
59
+
60
+
61
+ # -- Class Methods --
62
+
63
+
64
+ # nodoc
65
+ def self.find(what, options={})
66
+ return find_all(options) if what == :all
67
+ find_one(what)
68
+ end
69
+
70
+
71
+ # for debugging
72
+ def self.last_response
73
+ @@last_response
74
+ end
75
+
76
+
77
+ private
78
+
79
+
80
+ # uses xml_map() to convert the xml hash from response.data into an attributes hash
81
+ def self.attributes_from_xml_hash(hash)
82
+ attributes = {:raw_data => hash}
83
+ xml_map.each { |k,v| attributes[v] = hash[k] }
84
+ attributes
85
+ end
86
+
87
+
88
+ # uses xml_map() to convert :attributes into an xml string
89
+ def self.attributes_to_xml(attributes, root=nil)
90
+ xml = {}
91
+ map = xml_map.invert
92
+ attributes.each do |k,v|
93
+ key = map[k.to_s]
94
+ xml[key] = v
95
+ end
96
+ xml.to_xml :root => root
97
+ end
98
+
99
+
100
+ # nodoc
101
+ def self.find_one(id)
102
+ path = get_path + '/' + id.to_s
103
+ @@last_response = get(path)
104
+ raise_404(id) if last_response.code == 404
105
+ init_one(last_response)
106
+ end
107
+
108
+
109
+ # nodoc
110
+ def self.find_all(options={}, path=nil)
111
+ path ||= get_path
112
+ params = query_params(options)
113
+ @@last_response = get(path, :query => params)
114
+ init_many(last_response)
115
+ end
116
+
117
+
118
+ # over-ride in sub-classes
119
+ def self.get_path
120
+ end
121
+
122
+
123
+ # capsule API uses :start. :offset is prob more familiar to ruby/active_record users
124
+ def self.query_params(options)
125
+ params = options.dup
126
+ params[:start] = params.delete(:offset) if params.has_key?(:offset)
127
+ params
128
+ end
129
+
130
+
131
+ # nodoc
132
+ def self.raise_404(id)
133
+ err = "Could not find #{name} with id #{id}"
134
+ raise CapsuleCRM::RecordNotFound, err
135
+ end
136
+
137
+
138
+ # creates a new object, and returns the ID
139
+ # returns false if something went wrong (use last_response() to debug)
140
+ def self.create(attributes, options={})
141
+ return false if attributes.empty?
142
+ xml = attributes_to_xml(attributes, options.delete(:root))
143
+ @@last_response = post options[:path], xml_request_options(xml)
144
+ return false unless last_response.code == 201
145
+ last_response.headers['location'].split('/').last
146
+ end
147
+
148
+
149
+ # updates an object with the given id, returns true on success, false
150
+ # on failure.
151
+ def self.update(id, attributes, options={})
152
+ return true if attributes.empty?
153
+ xml = attributes_to_xml(attributes, options.delete(:root))
154
+ @@last_response = put options[:path], xml_request_options(xml)
155
+ last_response.code == 200
156
+ end
157
+
158
+
159
+ # nodoc
160
+ def self.xml_map
161
+ {'id' => 'id'}
162
+ end
163
+
164
+
165
+ # needed for PUT and POST operations
166
+ def self.xml_request_options(xml)
167
+ options = {
168
+ :body => xml,
169
+ :headers => {'Content-Type' => 'text/xml'},
170
+ :parser => Parser::Simple
171
+ }
172
+ end
173
+
174
+
175
+ end
176
+ end
@@ -0,0 +1,24 @@
1
+ class CapsuleCRM::Child < CapsuleCRM::Base
2
+ attr_accessor :parent
3
+
4
+
5
+ # nodoc
6
+ def initialize(parent, attributes={})
7
+ @parent = parent
8
+ super(attributes)
9
+ end
10
+
11
+
12
+ # nodoc
13
+ def self.init_many(parent, data)
14
+ CapsuleCRM::ChildCollection.new(parent, self, data)
15
+ end
16
+
17
+
18
+ # nodoc
19
+ def self.init_one(parent, data)
20
+ new(parent, attributes_from_xml_map(data))
21
+ end
22
+
23
+
24
+ end
@@ -0,0 +1,14 @@
1
+ class CapsuleCRM::ChildCollection < CapsuleCRM::Collection
2
+
3
+
4
+ # nodoc
5
+ def initialize(parent, klass, data)
6
+ return if data.nil?
7
+ [data].flatten.each do |attributes|
8
+ attributes = klass.attributes_from_xml_hash(attributes)
9
+ self.push klass.new(parent, attributes)
10
+ end
11
+ end
12
+
13
+
14
+ end
@@ -0,0 +1,14 @@
1
+ class CapsuleCRM::Collection < Array
2
+
3
+
4
+ # nodoc
5
+ def initialize(klass, data)
6
+ return if data.nil?
7
+ [data].flatten.each do |attributes|
8
+ attributes = klass.attributes_from_xml_hash(attributes)
9
+ self.push klass.new(attributes)
10
+ end
11
+ end
12
+
13
+
14
+ end
@@ -0,0 +1,27 @@
1
+ class CapsuleCRM::Contact < CapsuleCRM::Child
2
+
3
+ attr_accessor :type
4
+ define_attribute_methods [:type]
5
+
6
+ # nodoc
7
+ def attributes
8
+ attrs = {}
9
+ arr = [:type]
10
+ arr.each do |key|
11
+ attrs[key] = self.send(key)
12
+ end
13
+ attrs
14
+ end
15
+
16
+ # nodoc
17
+ def type=(value)
18
+ type_will_change! unless value == type
19
+ @type = value
20
+ end
21
+
22
+ # nodoc
23
+ def self.xml_map
24
+ map = {'type' => 'type'}
25
+ super.merge map
26
+ end
27
+ end
@@ -0,0 +1,41 @@
1
+ class CapsuleCRM::CustomField < CapsuleCRM::Child
2
+
3
+ attr_accessor :boolean
4
+ attr_accessor :date
5
+ attr_accessor :label
6
+ attr_accessor :text
7
+
8
+
9
+ # nodoc
10
+ def boolean=(value)
11
+ return @boolean = true if value.to_s == 'true'
12
+ @boolean = false
13
+ end
14
+
15
+
16
+ # nodoc
17
+ def date=(value)
18
+ value = Time.parse(value) if value.is_a?(String)
19
+ @date = value
20
+ end
21
+
22
+
23
+ # nodoc
24
+ def value
25
+ date || text || boolean
26
+ end
27
+
28
+
29
+ # nodoc
30
+ def self.xml_map
31
+ map = {
32
+ 'label' => 'label',
33
+ 'text' => 'text',
34
+ 'date' => 'date',
35
+ 'boolean' => 'boolean'
36
+ }
37
+ super.merge map
38
+ end
39
+
40
+
41
+ end
@@ -0,0 +1,64 @@
1
+ class CapsuleCRM::Email < CapsuleCRM::Contact
2
+
3
+ attr_accessor :address
4
+ define_attribute_methods [:address]
5
+
6
+
7
+ # nodoc
8
+ def attributes
9
+ attrs = {}
10
+ arr = [:address, :type]
11
+ arr.each do |key|
12
+ attrs[key] = self.send(key)
13
+ end
14
+ attrs
15
+ end
16
+
17
+ # nodoc
18
+ def address=(value)
19
+ address_will_change! unless value == address
20
+ @address = value
21
+ end
22
+
23
+ # nodoc
24
+ def dirty_attributes
25
+ Hash[attributes.select { |k,v| changed.include? k.to_s }]
26
+ end
27
+
28
+ def parent_type
29
+ return "person" if self.parent.class == CapsuleCRM::Person
30
+ return "organisation" if self.parent.class == CapsuleCRM::Organisation
31
+ raise "Unknown Parent Type"
32
+ end
33
+
34
+ # nodoc
35
+ def save
36
+ path = ["", "api", parent_type, self.parent.id].join("/")
37
+ options = {:root => 'person', :path => path}
38
+ attrs = new_record?? attributes : {:id => id}.merge(dirty_attributes)
39
+ success = self.class.update id, attrs, options
40
+ changed_attributes.clear if success
41
+ success
42
+ end
43
+
44
+ # uses xml_map() to convert :attributes into an xml string
45
+ def self.attributes_to_xml(attributes, root=nil)
46
+ xml = {"contacts" => {"email" => {}}}
47
+ map = xml_map.invert
48
+ attributes.each do |k,v|
49
+ key = map[k.to_s]
50
+ xml["contacts"]["email"][key] = v
51
+ end
52
+ xml.to_xml :root => root
53
+ end
54
+
55
+ # nodoc
56
+ def self.xml_map
57
+ map = {
58
+ 'emailAddress' => 'address'
59
+ }
60
+ super.merge map
61
+ end
62
+
63
+
64
+ end
@@ -0,0 +1,32 @@
1
+ module CapsuleCRM::History
2
+ # Reload history
3
+ def history!
4
+ @history = nil
5
+ history
6
+ end
7
+
8
+ # Load history if not loaded
9
+ def history
10
+ return @history if @history
11
+
12
+ path = self.class.get_path
13
+ path = [path, id, 'history'].join '/'
14
+ last_response = self.class.get(path)
15
+ data = last_response['history'].try(:[], 'historyItem')
16
+ @history = CapsuleCRM::HistoryItem.init_many(self, data)
17
+ end
18
+
19
+
20
+ def add_history(note)
21
+ if note
22
+ path = [self.class.get_path, self.id, 'history'].join '/'
23
+ self.class.create(Hash[{:note => note}], {:root => 'historyItem', :path => path})
24
+
25
+ # TODO : Should be optimized so it doesn't reload history each time
26
+ @history = nil
27
+ end
28
+ end
29
+
30
+ alias :add_note :add_history
31
+ end
32
+
@@ -0,0 +1,20 @@
1
+ class CapsuleCRM::HistoryItem < CapsuleCRM::Child
2
+ attr_reader :id
3
+ attr_accessor :type
4
+ attr_accessor :entry_date
5
+ attr_accessor :creator
6
+ attr_accessor :subject
7
+ attr_accessor :note
8
+
9
+ def self.xml_map
10
+ map = {
11
+ "id" => 'id',
12
+ "type" => 'type',
13
+ "entryDate" => 'entry_date',
14
+ "creator" => 'creator',
15
+ "subject" => 'subject',
16
+ "note" => 'note'
17
+ }
18
+ super.merge map
19
+ end
20
+ end
@@ -0,0 +1,60 @@
1
+ class CapsuleCRM::Opportunity < CapsuleCRM::Base
2
+
3
+ attr_accessor :name
4
+ attr_accessor :party_id
5
+ attr_accessor :currency
6
+ attr_accessor :value
7
+ attr_accessor :duration_basis
8
+ attr_accessor :duration
9
+ attr_accessor :expected_close_date
10
+ attr_accessor :actual_close_date
11
+ attr_accessor :milestone
12
+ attr_accessor :updated_on
13
+ attr_accessor :created_on
14
+
15
+ # nodoc
16
+ def self.xml_map
17
+ map = {
18
+ 'name' => 'name',
19
+ 'partyId' => 'party_id',
20
+ 'currency' => 'currency',
21
+ 'value' => 'value',
22
+ 'durationBasis' => 'duration_basis',
23
+ 'expectedCloseDate' => 'expected_close_date',
24
+ 'actualCloseDate' => 'actual_close_date',
25
+ 'milestone' => 'milestone',
26
+ 'updatedOn' => 'updated_on',
27
+ 'createdOn' => 'created_on',
28
+ }
29
+ super.merge map
30
+ end
31
+
32
+ def all
33
+ return @opportunities if @opportunities
34
+ path = self.class.get_path
35
+ last_response = self.class.get(path)
36
+ @opportunities = CapsuleCRM::Opportunity.init_many(last_response)
37
+ end
38
+
39
+ # nodoc
40
+ def self.get_path
41
+ '/api/opportunity'
42
+ end
43
+
44
+
45
+ # nodoc
46
+ def self.init_many(response)
47
+ data = response['opportunities']['opportunity']
48
+ CapsuleCRM::Collection.new(self, data)
49
+ end
50
+
51
+
52
+ # nodoc
53
+ def self.init_one(response)
54
+ data = response['opportunity']
55
+ new(attributes_from_xml_hash(data))
56
+ end
57
+
58
+
59
+
60
+ end