solve360 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
data/NOTES ADDED
@@ -0,0 +1,23 @@
1
+ :assignedto
2
+ :background
3
+ :businessaddress
4
+ :businessphonedirect
5
+ :businessemail
6
+ :businessfax
7
+ :businessphonemain
8
+ :cellularphone
9
+ :custom854308
10
+ :custom854310
11
+ :custom854311
12
+ :custom854307
13
+ :businessphoneextension
14
+ :firstname
15
+ :homephone
16
+ :homeaddress
17
+ :custom854309
18
+ :jobtitle
19
+ :lastname
20
+ :personalemail
21
+ :relatedto
22
+ :custom854312
23
+ :website
data/README.markdown ADDED
@@ -0,0 +1,75 @@
1
+ # Solve360
2
+
3
+ Library for interacting with Norada's Solve360 CRM
4
+
5
+ http://norada.com/
6
+
7
+ ## Usage
8
+
9
+ ### Installing
10
+
11
+ The gem is hosted on [Gem Cutter](http://gemcutter.org):
12
+
13
+ gem sources -a http://gemcutter.org
14
+ gem install solve360
15
+
16
+ ### Configuration
17
+
18
+ You can configure the API settings in a number of ways, but you must specify:
19
+
20
+ * url
21
+ * username
22
+ * token
23
+
24
+ The configuration uses [Configify](http://github.com/curve21/configify) so you can use a block or hash to define values:
25
+
26
+ Solve360::Config.configure do |config|
27
+ config.url = "https://secure.solve360.com"
28
+ config.username = "user@user.com"
29
+ config.token = "token"
30
+ end
31
+
32
+ Because configure accepts a hash, you can configure with YAML:
33
+
34
+ Solve360::Config.configure YAML.load(File.read("/path/to/file"))
35
+
36
+ And if you're using environments like Rails:
37
+
38
+ Solve360::Config.configure YAML.load(File.read("/path/to/file"))[RAILS_ENV]
39
+
40
+ ### Creating Records
41
+
42
+ Base attributes are set up for you. Creating is simple:
43
+
44
+ Solve360::Contact.create("First Name" => "Stephen", "Last Name" => "Bartholomew")
45
+
46
+ Custom attributes can be added:
47
+
48
+ Solve360::Contact.fields do
49
+ {"Description" => "custom20394", "Location" => "custom392434"}
50
+ end
51
+
52
+ and then used:
53
+
54
+ contact = Solve360::Contact.create("First Name" => "Steve", "Description" => "Web Developer", "Location" => "England")
55
+ contact.id
56
+ => The ID of the record created on the CRM
57
+
58
+ ### Finding
59
+
60
+ You can find by the ID of a record on the CRM:
61
+
62
+ contact = Solve360::Contact.find(12345)
63
+
64
+ ### Saving
65
+
66
+ Once you have set the attributes on a model you can simply save:
67
+
68
+ contact.attributes["First Name"] = "Steve"
69
+ contact.save
70
+
71
+ If the record does not have an ID it'll be created, otherwise the details will be saved.
72
+
73
+ ## Support/Bugs
74
+
75
+ [Lighthouse](http://c21.lighthouseapp.com/projects/38966-solve360/overview)
data/Rakefile ADDED
@@ -0,0 +1,40 @@
1
+ require 'spec/rake/spectask'
2
+
3
+ begin
4
+ require 'jeweler'
5
+ Jeweler::Tasks.new do |gem|
6
+ gem.name = "solve360"
7
+ gem.summary = "Libary for working with the Solve360 CRM API"
8
+ gem.email = "Stephen Bartholomew"
9
+ gem.homepage = "http://github.com/curve21/solve360"
10
+ gem.description = ""
11
+ gem.authors = ["Stephen Bartholomew"]
12
+ gem.files = FileList["[A-Z]*", "{lib,spec}/**/*"]
13
+ gem.add_dependency("configify", ">=0.0.1")
14
+ gem.add_dependency("activesupport")
15
+ gem.add_dependency("httparty", ">=0.4.5")
16
+ end
17
+ rescue LoadError
18
+ puts "Jeweler not available. Install it with: sudo gem install technicalpickles-jeweler -s http://gems.github.com"
19
+ end
20
+
21
+ Spec::Rake::SpecTask.new(:spec) do |spec|
22
+ spec.libs << 'lib' << 'spec'
23
+ spec.spec_files = FileList['spec/**/*_spec.rb']
24
+ spec.spec_opts = ['--options', 'spec/spec.opts']
25
+ end
26
+
27
+ Spec::Rake::SpecTask.new(:coverage) do |spec|
28
+ spec.spec_files = FileList['spec/**/*_spec.rb']
29
+ spec.rcov = true
30
+ spec.rcov_opts = ['--exclude', 'examples']
31
+ end
32
+
33
+ begin
34
+ require "yard"
35
+ YARD::Rake::YardocTask.new do |t|
36
+ t.files = ["lib/**/*.rb"]
37
+ end
38
+ rescue LoadError
39
+ puts "You'll need yard to generate documentation: gem install yard"
40
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.3
data/lib/solve360.rb ADDED
@@ -0,0 +1,9 @@
1
+ require "httparty"
2
+ require "configify"
3
+ require "active_support/inflector"
4
+ require "active_support/core_ext/hash"
5
+
6
+
7
+ ["model", "config", "contact", "company"].each do |lib|
8
+ require File.join(File.dirname(__FILE__), "solve360", lib)
9
+ end
@@ -0,0 +1,16 @@
1
+ module Solve360
2
+ class Company
3
+ include Solve360::Model
4
+
5
+ fields do
6
+ {"Billing Address" => "billingaddress",
7
+ "Company Address" => "mainaddress",
8
+ "Company Fax" => "fax",
9
+ "Company Name" => "name",
10
+ "Company Phone" => "phone",
11
+ "Related To" => "relatedto",
12
+ "Shipping Address" => "shippingaddress",
13
+ "Website" => "website"}
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ module Solve360
2
+ class Config
3
+ include Configify::Config
4
+ end
5
+ end
@@ -0,0 +1,23 @@
1
+ module Solve360
2
+ class Contact
3
+ include Solve360::Model
4
+
5
+ fields do
6
+ {"Business Address" => "businessaddress",
7
+ "Business Direct" => "businessphonedirect",
8
+ "Business Email" => "businessemail",
9
+ "Business Fax" => "businessfax",
10
+ "Business Main" => "businessphonemain",
11
+ "Cellular" => "cellularphone",
12
+ "Extension" => "businessphoneextension",
13
+ "First Name" => "firstname",
14
+ "Home" => "homephone",
15
+ "Home Address" => "homeaddress",
16
+ "Job Title" => "jobtitle",
17
+ "Last Name" => "lastname",
18
+ "Personal Email" => "personalemail",
19
+ "Related To" => "relatedto",
20
+ "Website" => "website"}
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,141 @@
1
+ module Solve360
2
+ module Model
3
+
4
+ def self.included(model)
5
+ model.extend ClassMethods
6
+ model.send(:include, HTTParty)
7
+ model.instance_variable_set(:@fields, {})
8
+ end
9
+
10
+ attr_accessor :attributes, :id, :relations
11
+
12
+ def initialize(attributes = {})
13
+ self.attributes = attributes
14
+ self.relations = []
15
+ self.id = nil
16
+ end
17
+
18
+ # @see Base::map_human_attributes
19
+ def map_human_attributes
20
+ self.class.map_human_attributes(self.attributes)
21
+ end
22
+
23
+ # Save the attributes for the current record to the CRM
24
+ #
25
+ # If the record is new it will be created on the CRM
26
+ #
27
+ # @return [Hash] response values from API
28
+ def save
29
+ if new_record?
30
+ new_record = self.class.create(attributes)
31
+ self.id = new_record.id
32
+ else
33
+ self.class.request(:put, "/#{self.class.resource_name}/#{id}", map_human_attributes.to_xml(:root => "request"))
34
+ end
35
+ end
36
+
37
+ def new_record?
38
+ self.id == nil
39
+ end
40
+
41
+ module ClassMethods
42
+
43
+ # Map human attributes to API attributes
44
+ #
45
+ # @param [Hash] human mapped attributes
46
+ # @example
47
+ # map_attributes("First Name" => "Steve", "Description" => "Web Developer")
48
+ # => {:firstname => "Steve", :custom12345 => "Web Developer"}
49
+ #
50
+ # @return [Hash] API mapped attributes
51
+ #
52
+ def map_human_attributes(attributes)
53
+ mapped_attributes = {}
54
+
55
+ fields.each do |human, api|
56
+ mapped_attributes[api] = attributes[human] if !attributes[human].blank?
57
+ end
58
+
59
+ mapped_attributes
60
+ end
61
+
62
+ # As ::map_human_attributes but API -> human
63
+ #
64
+ # @param [Hash] API mapped attributes
65
+ # @example
66
+ # map_attributes(:firstname => "Steve", :custom12345 => "Web Developer")
67
+ # => {"First Name" => "Steve", "Description" => "Web Developer"}
68
+ #
69
+ # @return [Hash] human mapped attributes
70
+ def map_api_attributes(attributes)
71
+ attributes.stringify_keys!
72
+
73
+ mapped_attributes = {}
74
+
75
+ fields.each do |human, api|
76
+ mapped_attributes[human] = attributes[api] if !attributes[api].blank?
77
+ end
78
+
79
+ mapped_attributes
80
+ end
81
+
82
+ # Create a record in the API
83
+ #
84
+ # @param [Hash] field => value as configured in Model::fields
85
+ def create(attributes, options = {})
86
+ response = request(:post, "/#{resource_name}", map_human_attributes(attributes).to_xml(:root => "request"))
87
+
88
+ construct_record_from_response(response)
89
+ end
90
+
91
+ # Find a record
92
+ #
93
+ # @param [Integer] id of the record on the CRM
94
+ def find(id)
95
+ response = request(:get, "/#{resource_name}/#{id}")
96
+
97
+ construct_record_from_response(response)
98
+ end
99
+
100
+ # Send an HTTP request
101
+ #
102
+ # @param [Symbol, String] :get, :post, :put or :delete
103
+ # @param [String] url of the resource
104
+ # @param [String, nil] optional string to send in request body
105
+ def request(verb, uri, body = "")
106
+ send(verb, HTTParty.normalize_base_uri(Solve360::Config.config.url) + uri,
107
+ :headers => {"Content-Type" => "application/xml", "Accepts" => "application/json"},
108
+ :body => body,
109
+ :basic_auth => {:username => Solve360::Config.config.username, :password => Solve360::Config.config.token})
110
+ end
111
+
112
+ def construct_record_from_response(response)
113
+ attributes = map_api_attributes(response["response"]["item"]["fields"])
114
+ record = new(attributes)
115
+
116
+ related_to = response["response"]["relateditems"]["relatedto"]
117
+
118
+ if related_to.kind_of?(Array)
119
+ record.relations.concat(related_to)
120
+ else
121
+ record.relations << related_to
122
+ end
123
+
124
+ record.id = response["response"]["item"]["id"].to_i
125
+ record
126
+ end
127
+
128
+ def resource_name
129
+ self.name.to_s.demodulize.underscore.pluralize
130
+ end
131
+
132
+ def fields(&block)
133
+ if block_given?
134
+ @fields.merge! yield
135
+ else
136
+ @fields
137
+ end
138
+ end
139
+ end
140
+ end
141
+ end
@@ -0,0 +1,3 @@
1
+ url: https://secure.solve360.com
2
+ username: steve@curve21.com
3
+ token: 46a1j9Z9n0seOaLaPa2bne7c563fgds0S0J990g5
@@ -0,0 +1,3 @@
1
+ url:
2
+ username:
3
+ token
@@ -0,0 +1 @@
1
+ {"response" : {"relateditems" : nil, "activities" : nil, "categories" : nil, "status" : "success", "item" : {"name" : "Test ", "typeid" : "1", "id" : "12345", "fields" : {"modificatorid" : "536663", "lastname" : "Bartholomew", "creatorname" : "Stephen Bartholomew", "businessemail" : nil, "modificatorname" : "Stephen Bartholomew", "firstname" : "Catherine", "creatorid" : "536663", "personalemail" : nil}, "viewed" : "2009-10-06T08:15:27+00:00", "ownership" : "536663", "flagged" : nil, "updated" : "2009-10-06T08:15:27+00:00", "created" : "2009-10-06T08:15:27+00:00"}}}
File without changes
@@ -0,0 +1 @@
1
+ {"response" : {"relateditems" : {"relatedto" : {"name" : "Curve21", "typeid" : "40", "id" : "960104", "note" : nil}}, "activities" : nil, "categories" : nil, "status" : "success", "item" : {"name" : "Henry Bartholomew", "typeid" : "1", "id" : "12345", "fields" : {"custom854307" : "A description of Henry", "modificatorid" : "536663", "lastname" : "Bartholomew", "creatorname" : "Stephen Bartholomew", "businessemail" : nil, "modificatorname" : "Stephen Bartholomew", "firstname" : "Henry", "creatorid" : "536663", "personalemail" : nil}, "viewed" : "2009-10-08T09:29:41+00:00", "ownership" : "536663", "flagged" : nil, "updated" : "2009-10-08T09:29:53+00:00", "created" : "2009-10-08T09:28:03+00:00"}}}
@@ -0,0 +1 @@
1
+ {"response" : {"id536666" : {"name" : "Josh Kedward", "id" : "536666", "flagged" : nil, "viewed" : "2009-10-02T14:21:18+00:00", "parentid" : "536663", "updated" : "2009-05-24T10:11:02+00:00", "created" : "2009-05-24T10:10:21+00:00"}, "count" : "2", "id536670" : {"name" : "Stephen Bartholomew", "id" : "536670", "flagged" : nil, "viewed" : "2009-10-02T14:21:18+00:00", "parentid" : "536663", "updated" : "2009-05-24T10:12:32+00:00", "created" : "2009-05-24T10:12:21+00:00"}, "status" : "success"}}
@@ -0,0 +1 @@
1
+ {"response" : {"relateditems" : nil, "activities" : nil, "categories" : nil, "status" : "success", "item" : {"name" : "Steve Bartholomew", "typeid" : "1", "id" : "12345", "fields" : {"modificatorid" : "536663", "lastname" : "Bartholomew", "creatorname" : "Stephen Bartholomew", "businessemail" : nil, "modificatorname" : "Stephen Bartholomew", "firstname" : "Henry", "creatorid" : "536663", "personalemail" : nil}, "viewed" : "2009-10-06T10:12:03+00:00", "ownership" : "536663", "flagged" : nil, "updated" : "2009-10-06T10:11:05+00:00", "created" : "2009-10-06T10:08:04+00:00"}}}
@@ -0,0 +1 @@
1
+ {"response" : {"errors" : {"incorrectfield" : "Incorrect field specified"}, "status" : "failed"}}
@@ -0,0 +1 @@
1
+ {"response" : {"status" : "success"}}
@@ -0,0 +1,128 @@
1
+ require File.join(File.dirname(__FILE__), "..", "spec_helper")
2
+
3
+ class Person
4
+ include Solve360::Model
5
+
6
+ fields do
7
+ { "Job Title" => "job_title" }
8
+ end
9
+ end
10
+
11
+ describe "A Solve360 model" do
12
+ it "should determine model name" do
13
+ Person.resource_name.should == "people"
14
+ end
15
+
16
+ context "more than one model" do
17
+ it "should not pollute map" do
18
+ class Car
19
+ include Solve360::Model
20
+ fields do
21
+ { "Doors" => "doors" }
22
+ end
23
+ end
24
+
25
+ Car.fields.keys.include?("Job Title").should be_false
26
+ Person.fields.keys.include?("Doors").should be_false
27
+ end
28
+ end
29
+ end
30
+
31
+ describe "Field mapping" do
32
+ before do
33
+ Person.fields do
34
+ {"Interests" => "custom_interests",
35
+ "Department Website" => "custom_deptwebsite",
36
+ "Description" => "custom_description"}
37
+ end
38
+
39
+ @person = Person.new
40
+ end
41
+
42
+ it "should set base map" do
43
+ Person.fields["Job Title"].should == "job_title"
44
+ end
45
+
46
+ it "should set custom map" do
47
+ Person.fields["Interests"].should == "custom_interests"
48
+ end
49
+
50
+ it "should allow setting of values on an instance via field maps" do
51
+ @person.attributes["Interests"] = "Coding"
52
+ @person.attributes["Interests"].should == "Coding"
53
+ end
54
+
55
+ it "should map human map to API map" do
56
+ attributes = {"Description" => "A description"}
57
+
58
+ Person.map_human_attributes(attributes)["custom_description"].should == "A description"
59
+ end
60
+
61
+ it "should map API map to human map" do
62
+ attributes = {:custom_description => "A description"}
63
+
64
+ Person.map_api_attributes(attributes)["Description"].should == "A description"
65
+ end
66
+ end
67
+
68
+ describe "Creating a record" do
69
+ context "directly from create" do
70
+ before do
71
+ stub_http_response_with("contacts/create-success.json")
72
+ @contact = Solve360::Contact.create("First Name" => "Catherine")
73
+ end
74
+
75
+ it "should be valid" do
76
+ @contact.attributes["First Name"].should == "Catherine"
77
+ @contact.id.should == 12345
78
+ end
79
+ end
80
+
81
+ context "creating a new object then saving" do
82
+ before do
83
+ stub_http_response_with("contacts/create-success.json")
84
+ @contact = Solve360::Contact.new("First Name" => "Catherine")
85
+ @contact.save
86
+ end
87
+
88
+ it "should be valid" do
89
+ @contact.id.should == 12345
90
+ end
91
+ end
92
+ end
93
+
94
+ describe "Finding a record" do
95
+ context "Successfully" do
96
+ before do
97
+ stub_http_response_with("contacts/find-success.json")
98
+ @contact = Solve360::Contact.find(12345)
99
+ end
100
+
101
+ it "should find existing user" do
102
+ @contact.attributes["First Name"].should == "Henry"
103
+ @contact.id.should == 12345
104
+ end
105
+
106
+ it "should have relations" do
107
+ @contact.relations.first["name"].should == "Curve21"
108
+ end
109
+ end
110
+ end
111
+
112
+ describe "Updating a record" do
113
+ before do
114
+ @contact = Solve360::Contact.new("First Name" => "Steve")
115
+
116
+ @contact.id = 12345
117
+
118
+ stub_http_response_with("contacts/update-success.json")
119
+
120
+ @contact.attributes["First Name"] = "Steve"
121
+
122
+ @response = @contact.save
123
+ end
124
+
125
+ it "should be valid" do
126
+ @response["response"]["status"].should == "success"
127
+ end
128
+ end
data/spec/spec.opts ADDED
@@ -0,0 +1,2 @@
1
+ --colour
2
+ --format specdoc
@@ -0,0 +1,22 @@
1
+ require 'rubygems'
2
+
3
+ require File.join(File.dirname(__FILE__), '..', 'lib', 'solve360')
4
+
5
+ Solve360::Config.configure YAML::load(File.read(File.join(File.dirname(__FILE__), 'api_settings.yml')))
6
+
7
+ def file_fixture(filename)
8
+ open(File.join(File.dirname(__FILE__), 'fixtures', "#{filename.to_s}")).read
9
+ end
10
+
11
+ def stub_http_response_with(filename)
12
+ format = filename.split('.').last.intern
13
+ data = file_fixture(filename)
14
+
15
+ response = Net::HTTPOK.new("1.1", 200, "Content for you")
16
+ response.stub!(:body).and_return(data)
17
+
18
+ http_request = HTTParty::Request.new(Net::HTTP::Get, 'http://localhost', :format => format)
19
+ http_request.stub!(:perform_actual_request).and_return(response)
20
+
21
+ HTTParty::Request.should_receive(:new).and_return(http_request)
22
+ end
metadata ADDED
@@ -0,0 +1,105 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: solve360
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.3
5
+ platform: ruby
6
+ authors:
7
+ - Stephen Bartholomew
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+
12
+ date: 2009-10-16 00:00:00 +01:00
13
+ default_executable:
14
+ dependencies:
15
+ - !ruby/object:Gem::Dependency
16
+ name: configify
17
+ type: :runtime
18
+ version_requirement:
19
+ version_requirements: !ruby/object:Gem::Requirement
20
+ requirements:
21
+ - - ">="
22
+ - !ruby/object:Gem::Version
23
+ version: 0.0.1
24
+ version:
25
+ - !ruby/object:Gem::Dependency
26
+ name: activesupport
27
+ type: :runtime
28
+ version_requirement:
29
+ version_requirements: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: "0"
34
+ version:
35
+ - !ruby/object:Gem::Dependency
36
+ name: httparty
37
+ type: :runtime
38
+ version_requirement:
39
+ version_requirements: !ruby/object:Gem::Requirement
40
+ requirements:
41
+ - - ">="
42
+ - !ruby/object:Gem::Version
43
+ version: 0.4.5
44
+ version:
45
+ description: ""
46
+ email: Stephen Bartholomew
47
+ executables: []
48
+
49
+ extensions: []
50
+
51
+ extra_rdoc_files:
52
+ - README.markdown
53
+ files:
54
+ - NOTES
55
+ - README.markdown
56
+ - Rakefile
57
+ - VERSION
58
+ - lib/solve360.rb
59
+ - lib/solve360/company.rb
60
+ - lib/solve360/config.rb
61
+ - lib/solve360/contact.rb
62
+ - lib/solve360/model.rb
63
+ - spec/api_settings.yml
64
+ - spec/api_settings.yml.sample
65
+ - spec/fixtures/contacts/create-success.json
66
+ - spec/fixtures/contacts/find-company-success.json
67
+ - spec/fixtures/contacts/find-success.json
68
+ - spec/fixtures/contacts/index.json
69
+ - spec/fixtures/contacts/show-success.json
70
+ - spec/fixtures/contacts/update-failed.json
71
+ - spec/fixtures/contacts/update-success.json
72
+ - spec/solve360/model_spec.rb
73
+ - spec/spec.opts
74
+ - spec/spec_helper.rb
75
+ has_rdoc: true
76
+ homepage: http://github.com/curve21/solve360
77
+ licenses: []
78
+
79
+ post_install_message:
80
+ rdoc_options:
81
+ - --charset=UTF-8
82
+ require_paths:
83
+ - lib
84
+ required_ruby_version: !ruby/object:Gem::Requirement
85
+ requirements:
86
+ - - ">="
87
+ - !ruby/object:Gem::Version
88
+ version: "0"
89
+ version:
90
+ required_rubygems_version: !ruby/object:Gem::Requirement
91
+ requirements:
92
+ - - ">="
93
+ - !ruby/object:Gem::Version
94
+ version: "0"
95
+ version:
96
+ requirements: []
97
+
98
+ rubyforge_project:
99
+ rubygems_version: 1.3.5
100
+ signing_key:
101
+ specification_version: 3
102
+ summary: Libary for working with the Solve360 CRM API
103
+ test_files:
104
+ - spec/solve360/model_spec.rb
105
+ - spec/spec_helper.rb