solve360 0.0.3

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/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