capsulecrm-b 0.0.6

Sign up to get free protection for your applications and to get access to all the features.
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