sugarcrm 0.5.3 → 0.6.0
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/README.rdoc +15 -10
- data/Rakefile +4 -1
- data/VERSION +1 -1
- data/lib/sugarcrm.rb +107 -119
- data/lib/sugarcrm/api/get_available_modules.rb +17 -0
- data/lib/sugarcrm/api/get_document_revision.rb +14 -0
- data/lib/sugarcrm/{get_entries.rb → api/get_entries.rb} +2 -2
- data/lib/sugarcrm/api/get_entries_count.rb +20 -0
- data/lib/sugarcrm/{get_entry.rb → api/get_entry.rb} +5 -8
- data/lib/sugarcrm/{get_entry_list.rb → api/get_entry_list.rb} +2 -2
- data/lib/sugarcrm/{get_module_fields.rb → api/get_module_fields.rb} +6 -3
- data/lib/sugarcrm/api/get_note_attachment.rb +15 -0
- data/lib/sugarcrm/{get_relationships.rb → api/get_relationship.rb} +9 -4
- data/lib/sugarcrm/api/get_report_entries.rb +19 -0
- data/lib/sugarcrm/api/get_server_info.rb +7 -0
- data/lib/sugarcrm/api/get_user_id.rb +13 -0
- data/lib/sugarcrm/api/get_user_team_id.rb +14 -0
- data/lib/sugarcrm/api/login.rb +18 -0
- data/lib/sugarcrm/api/logout.rb +15 -0
- data/lib/sugarcrm/api/seamless_login.rb +13 -0
- data/lib/sugarcrm/api/search_by_module.rb +24 -0
- data/lib/sugarcrm/api/set_campaign_merge.rb +15 -0
- data/lib/sugarcrm/api/set_document_revision.rb +15 -0
- data/lib/sugarcrm/api/set_entries.rb +15 -0
- data/lib/sugarcrm/api/set_entry.rb +15 -0
- data/lib/sugarcrm/api/set_note_attachment.rb +3 -0
- data/lib/sugarcrm/api/set_relationship.rb +18 -0
- data/lib/sugarcrm/api/set_relationships.rb +22 -0
- data/lib/sugarcrm/connection.rb +112 -0
- data/lib/sugarcrm/exceptions.rb +16 -0
- data/lib/sugarcrm/module.rb +11 -0
- data/lib/sugarcrm/request.rb +28 -0
- data/lib/sugarcrm/response.rb +29 -0
- data/test/helper.rb +5 -0
- data/test/test_connection.rb +60 -0
- data/test/test_response.rb +36 -0
- data/test/test_sugarcrm.rb +31 -45
- metadata +56 -17
- data/lib/net/http_digest_auth.rb +0 -44
- data/lib/stdlib/array.rb +0 -14
- data/lib/stdlib/hash.rb +0 -17
- data/lib/stdlib/object.rb +0 -5
- data/test/test_json_to_obj.rb +0 -48
data/README.rdoc
CHANGED
@@ -6,18 +6,19 @@ REST Bindings for SugarCRM!
|
|
6
6
|
|
7
7
|
== SUMMARY:
|
8
8
|
|
9
|
-
Ruby Gem for interacting with SugarCRM via REST
|
9
|
+
Ruby Gem for interacting with SugarCRM via REST.
|
10
10
|
|
11
|
-
==
|
11
|
+
== Description:
|
12
|
+
|
13
|
+
I've implemented all of the basic API calls that SugarCRM supports, and am actively building an abstraction layer
|
14
|
+
on top of the basic API methods. The end result will be to provide ActiveRecord style finders and first class
|
15
|
+
objects. Some of this functionality is included today.
|
12
16
|
|
13
|
-
|
14
|
-
* Only the following methods are currently implemented:
|
17
|
+
== FEATURES/PROBLEMS:
|
15
18
|
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
* get_module_fields
|
20
|
-
* get_relationships
|
19
|
+
* Auto-generation of Module specific objects. When a connection is established, get_available_modules is called
|
20
|
+
and the resultant modules are turned into SugarCRM::Module classes.
|
21
|
+
* If you just want to use the vanilla API, you can access the methods directly on the connection object.
|
21
22
|
|
22
23
|
== SYNOPSIS:
|
23
24
|
|
@@ -25,7 +26,7 @@ Ruby Gem for interacting with SugarCRM via REST
|
|
25
26
|
sugarcrm = SugarCRM::Base.new("http://localhost/sugarcrm", 'user', 'password', {:debug => false})
|
26
27
|
|
27
28
|
# Lookup a user by name. Find any associated accounts
|
28
|
-
sugarcrm.get_entry_list(
|
29
|
+
sugarcrm.connection.get_entry_list(
|
29
30
|
"Users",
|
30
31
|
"users.user_name = \'#{USER}\'",
|
31
32
|
{
|
@@ -39,8 +40,12 @@ Ruby Gem for interacting with SugarCRM via REST
|
|
39
40
|
}
|
40
41
|
)
|
41
42
|
|
43
|
+
# Retrieve a user by ID, using the SugarCRM::User Proxy object
|
44
|
+
user = SugarCRM::User.find(id)
|
45
|
+
|
42
46
|
== REQUIREMENTS:
|
43
47
|
|
48
|
+
* activesupport gem
|
44
49
|
* json gem
|
45
50
|
|
46
51
|
== INSTALL:
|
data/Rakefile
CHANGED
@@ -6,12 +6,15 @@ begin
|
|
6
6
|
Jeweler::Tasks.new do |gem|
|
7
7
|
gem.name = "sugarcrm"
|
8
8
|
gem.summary = %Q{Ruby based REST client for SugarCRM}
|
9
|
-
gem.description = %Q{
|
9
|
+
gem.description = %Q{I've implemented all of the basic API calls that SugarCRM supports, and am actively building an abstraction layer
|
10
|
+
on top of the basic API methods. The end result will be to provide ActiveRecord style finders and first class
|
11
|
+
objects. Some of this functionality is included today.}
|
10
12
|
gem.email = "carl.hicks@gmail.com"
|
11
13
|
gem.homepage = "http://github.com/chicks/sugarcrm"
|
12
14
|
gem.authors = ["Carl Hicks"]
|
13
15
|
gem.add_development_dependency "shoulda", ">= 0"
|
14
16
|
gem.add_dependency "json", ">= 0"
|
17
|
+
gem.add_dependency "activesupport", ">= 0"
|
15
18
|
# gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
|
16
19
|
end
|
17
20
|
Jeweler::GemcutterTasks.new
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.6.0
|
data/lib/sugarcrm.rb
CHANGED
@@ -3,153 +3,141 @@
|
|
3
3
|
module SugarCRM
|
4
4
|
|
5
5
|
Dir["#{File.dirname(__FILE__)}/sugarcrm/**/*.rb"].each { |f| load(f) }
|
6
|
-
Dir["#{File.dirname(__FILE__)}/stdlib/**/*.rb"].each { |f| load(f) }
|
7
6
|
|
8
7
|
require 'pp'
|
9
|
-
require 'ostruct'
|
10
8
|
require 'uri'
|
11
9
|
require 'net/https'
|
12
|
-
require 'openssl'
|
13
10
|
require 'digest/md5'
|
14
11
|
|
15
12
|
require 'rubygems'
|
13
|
+
require 'active_support/core_ext'
|
16
14
|
require 'json'
|
17
|
-
|
18
|
-
class LoginError < RuntimeError
|
19
|
-
end
|
20
|
-
|
21
|
-
class EmptyResponse < RuntimeError
|
22
|
-
end
|
23
|
-
|
24
|
-
class UnhandledResponse < RuntimeError
|
25
|
-
end
|
26
|
-
|
27
|
-
class InvalidSugarCRMUrl < RuntimeError
|
28
|
-
end
|
15
|
+
require 'json/add/rails'
|
29
16
|
|
30
17
|
class Base
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
18
|
+
# Unset all of the instance methods we don't need.
|
19
|
+
instance_methods.each { |m| undef_method m unless m =~ /(^__|^send$|^object_id$|^define_method$|^class$|^instance_of.$)/ }
|
20
|
+
|
21
|
+
# This holds our connection
|
22
|
+
cattr_accessor :connection, :instance_writer => false
|
23
|
+
|
24
|
+
# Contains the name of the module in SugarCRM
|
25
|
+
class_attribute :module_name
|
26
|
+
self.module_name = self.name.split(/::/)[-1]
|
27
|
+
|
28
|
+
# Contains the fields found on the current module
|
29
|
+
class_attribute :module_fields
|
30
|
+
self.module_fields = {}
|
31
|
+
|
32
|
+
# Tracks if we have extended our class with attribute methods yet.
|
33
|
+
class_attribute :attribute_methods_generated
|
34
|
+
self.attribute_methods_generated = false
|
35
|
+
|
36
|
+
# Contains a list of attributes
|
37
|
+
attr :attributes, true
|
39
38
|
attr :debug, true
|
40
|
-
attr :to_obj, true
|
41
39
|
|
42
|
-
def
|
40
|
+
def self.establish_connection(url, user, pass, opts={})
|
43
41
|
options = {
|
44
42
|
:debug => false,
|
45
|
-
:to_obj => true
|
46
43
|
}.merge(opts)
|
47
|
-
|
48
44
|
@debug = options[:debug]
|
49
|
-
|
50
|
-
|
51
|
-
@url = URI.parse(url)
|
52
|
-
@user = user
|
53
|
-
@pass = pass
|
54
|
-
|
55
|
-
# Handles http/https in url string
|
56
|
-
@ssl = false
|
57
|
-
@ssl = true if @url.scheme == "https"
|
58
|
-
|
59
|
-
# Appends the rest.php path onto the end of the URL if it's not included
|
60
|
-
if @url.path !~ /rest.php$/
|
61
|
-
@url.path += URL
|
62
|
-
end
|
63
|
-
|
64
|
-
login!
|
65
|
-
raise LoginError, "Invalid Login" unless logged_in?
|
45
|
+
@@connection = SugarCRM::Connection.new(url, user, pass, @debug)
|
66
46
|
end
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
47
|
+
|
48
|
+
# Registers the module fields on the class
|
49
|
+
def self.register_module_fields
|
50
|
+
self.module_fields = connection.get_fields(self.module_name)["module_fields"] if self.module_fields.length == 0
|
51
|
+
end
|
52
|
+
|
53
|
+
def initialize(attributes={})
|
54
|
+
@attributes = attributes_from_module_fields.merge(attributes)
|
55
|
+
define_attribute_methods
|
56
|
+
end
|
57
|
+
|
58
|
+
def inspect
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
62
|
+
def to_s
|
63
|
+
attrs = []
|
64
|
+
@attributes.each_key do |k|
|
65
|
+
attrs << "#{k}: #{attribute_for_inspect(k)}"
|
66
|
+
end
|
67
|
+
"#<#{self.class} #{attrs.join(", ")}>"
|
72
68
|
end
|
73
69
|
|
74
|
-
|
75
|
-
|
70
|
+
# Returns an <tt>#inspect</tt>-like string for the value of the
|
71
|
+
# attribute +attr_name+. String attributes are elided after 50
|
72
|
+
# characters, and Date and Time attributes are returned in the
|
73
|
+
# <tt>:db</tt> format. Other attributes return the value of
|
74
|
+
# <tt>#inspect</tt> without modification.
|
75
|
+
#
|
76
|
+
# person = Person.create!(:name => "David Heinemeier Hansson " * 3)
|
77
|
+
#
|
78
|
+
# person.attribute_for_inspect(:name)
|
79
|
+
# # => '"David Heinemeier Hansson David Heinemeier Hansson D..."'
|
80
|
+
#
|
81
|
+
# person.attribute_for_inspect(:created_at)
|
82
|
+
# # => '"2009-01-12 04:48:57"'
|
83
|
+
def attribute_for_inspect(attr_name)
|
84
|
+
value = read_attribute(attr_name)
|
85
|
+
|
86
|
+
if value.is_a?(String) && value.length > 50
|
87
|
+
"#{value[0..50]}...".inspect
|
88
|
+
elsif value.is_a?(Date) || value.is_a?(Time)
|
89
|
+
%("#{value.to_s(:db)}")
|
90
|
+
else
|
91
|
+
value.inspect
|
92
|
+
end
|
76
93
|
end
|
77
94
|
|
78
95
|
protected
|
79
96
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
97
|
+
# Generates get/set methods for keys in the attributes hash
|
98
|
+
def define_attribute_methods
|
99
|
+
return if attribute_methods_generated?
|
100
|
+
@attributes.each_pair do |k,v|
|
101
|
+
self.class.module_eval %Q?
|
102
|
+
def #{k}
|
103
|
+
read_attribute :#{k}
|
85
104
|
end
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
def login!
|
90
|
-
connect! unless connected?
|
91
|
-
json = <<-EOF
|
92
|
-
{
|
93
|
-
\"user_auth\": {
|
94
|
-
\"user_name\": \"#{@user}\"\,
|
95
|
-
\"password\": \"#{OpenSSL::Digest::MD5.new(@pass)}\"\,
|
96
|
-
\"version\": \"2\"\,
|
97
|
-
},
|
98
|
-
\"application\": \"\"
|
99
|
-
}
|
100
|
-
EOF
|
101
|
-
json.gsub!(/^\s{8}/,'')
|
102
|
-
response = get(:login, json)
|
103
|
-
|
104
|
-
if @to_obj
|
105
|
-
@session = response.id
|
106
|
-
else
|
107
|
-
@session = response["id"]
|
105
|
+
def #{k}=(value)
|
106
|
+
write_attribute :#{k},value
|
108
107
|
end
|
108
|
+
?
|
109
109
|
end
|
110
|
+
self.class.attribute_methods_generated = true
|
111
|
+
end
|
110
112
|
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
when Net::HTTPOK
|
127
|
-
raise EmptyResponse unless response.body
|
128
|
-
response_json = JSON.parse response.body
|
129
|
-
return false if response_json["result_count"] == 0
|
130
|
-
if @debug
|
131
|
-
puts "#{method}: JSON Response:"
|
132
|
-
pp response_json
|
133
|
-
puts "\n"
|
134
|
-
end
|
135
|
-
response_obj = response_json.to_obj
|
136
|
-
|
137
|
-
if @to_obj
|
138
|
-
return response_obj
|
139
|
-
else
|
140
|
-
return response_json
|
141
|
-
end
|
142
|
-
when Net::HTTPNotFound
|
143
|
-
raise InvalidSugarCRMUrl, "#{@url} is invalid"
|
144
|
-
else
|
145
|
-
if @debug
|
146
|
-
puts "#{method}: Raw Response:"
|
147
|
-
puts response.body
|
148
|
-
puts "\n"
|
149
|
-
end
|
150
|
-
raise UnhandledResponse, "Can't handle response #{response}"
|
151
|
-
end
|
113
|
+
# Wrapper around class attribute
|
114
|
+
def attribute_methods_generated?
|
115
|
+
self.class.attribute_methods_generated
|
116
|
+
end
|
117
|
+
|
118
|
+
def module_fields_registered?
|
119
|
+
self.class.module_fields.length > 0
|
120
|
+
end
|
121
|
+
|
122
|
+
# Returns a hash of the module fields from the
|
123
|
+
def attributes_from_module_fields
|
124
|
+
self.class.register_module_fields unless module_fields_registered?
|
125
|
+
fields = {}
|
126
|
+
self.class.module_fields.keys.sort.each do |k|
|
127
|
+
fields[k.to_s] = nil
|
152
128
|
end
|
129
|
+
fields
|
130
|
+
end
|
131
|
+
|
132
|
+
# Wrapper around attributes hash
|
133
|
+
def read_attribute(key)
|
134
|
+
@attributes[key]
|
135
|
+
end
|
136
|
+
|
137
|
+
# Wrapper around attributes hash
|
138
|
+
def write_attribute(key, value)
|
139
|
+
@attributes[key] = value
|
140
|
+
end
|
153
141
|
|
154
142
|
end
|
155
143
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module SugarCRM; class Connection
|
2
|
+
# Retrieves the list of modules available to the current user logged into the system.
|
3
|
+
def get_available_modules
|
4
|
+
login! unless logged_in?
|
5
|
+
json = <<-EOF
|
6
|
+
{
|
7
|
+
\"session\": \"#{@session}\"
|
8
|
+
}
|
9
|
+
EOF
|
10
|
+
|
11
|
+
json.gsub!(/^\s{6}/,'')
|
12
|
+
get(:get_available_modules, json)["modules"]
|
13
|
+
end
|
14
|
+
|
15
|
+
alias :get_modules :get_available_modules
|
16
|
+
|
17
|
+
end; end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module SugarCRM; class Connection
|
2
|
+
# Downloads a particular revision of a document.
|
3
|
+
def get_document_revision(id)
|
4
|
+
login! unless logged_in?
|
5
|
+
json = <<-EOF
|
6
|
+
{
|
7
|
+
\"session\": \"#{@session}\"\,
|
8
|
+
\"id\": #{id}
|
9
|
+
}
|
10
|
+
EOF
|
11
|
+
json.gsub!(/^\s{6}/,'')
|
12
|
+
get(:get_document_revision, json)
|
13
|
+
end
|
14
|
+
end; end
|
@@ -1,4 +1,4 @@
|
|
1
|
-
module SugarCRM; class
|
1
|
+
module SugarCRM; class Connection
|
2
2
|
# Retrieve a list of SugarBeans by ID. This method will not
|
3
3
|
# work with the report module.
|
4
4
|
def get_entries(module_name, ids, options={})
|
@@ -12,7 +12,7 @@ def get_entries(module_name, ids, options={})
|
|
12
12
|
{
|
13
13
|
\"session\": \"#{@session}\"\,
|
14
14
|
\"module_name\": \"#{module_name}\"\,
|
15
|
-
\"ids\":
|
15
|
+
\"ids\": #{ids.to_json}\,
|
16
16
|
\"select_fields\": #{options[:fields].to_json}\,
|
17
17
|
\"link_name_to_fields_array\": #{options[:link_fields].to_json}\,
|
18
18
|
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module SugarCRM; class Connection
|
2
|
+
# Retrieves the specified number of records in a module.
|
3
|
+
def get_entries_count(module_name, query, options={})
|
4
|
+
login! unless logged_in?
|
5
|
+
{
|
6
|
+
:deleted => 0,
|
7
|
+
}.merge! options
|
8
|
+
|
9
|
+
json = <<-EOF
|
10
|
+
{
|
11
|
+
\"session\": \"#{@session}\"\,
|
12
|
+
\"module_name\": \"#{module_name}\"\,
|
13
|
+
\"query\": \"#{query}\"\,
|
14
|
+
\"deleted\": #{options[:deleted]}
|
15
|
+
}
|
16
|
+
EOF
|
17
|
+
json.gsub!(/^\s{6}/,'')
|
18
|
+
get(:get_entries_count, json)
|
19
|
+
end
|
20
|
+
end; end
|
@@ -1,4 +1,5 @@
|
|
1
|
-
module SugarCRM; class
|
1
|
+
module SugarCRM; class Connection
|
2
|
+
# Retrieves a single SugarBean based on the ID.
|
2
3
|
def get_entry(module_name, id, options={})
|
3
4
|
login! unless logged_in?
|
4
5
|
{ :fields => [],
|
@@ -11,15 +12,11 @@ module SugarCRM; class Base
|
|
11
12
|
\"module_name\": \"#{module_name}\"\,
|
12
13
|
\"id\": \"#{id}\"\,
|
13
14
|
\"select_fields\": #{options[:fields].to_json}\,
|
15
|
+
\"link_name_to_fields_array\": #{options[:link_fields]}\,
|
14
16
|
}
|
15
17
|
EOF
|
16
|
-
|
17
|
-
placeholder = <<-EOF
|
18
|
-
\"select_fields\": [\"name\"]
|
19
|
-
\"link_name_to_fields_array\": \"#{options[:link_fields]}\"\,
|
20
|
-
EOF
|
21
|
-
|
18
|
+
|
22
19
|
json.gsub!(/^\s{6}/,'')
|
23
|
-
get(:get_entry, json)
|
20
|
+
SugarCRM::Response.new(get(:get_entry, json))
|
24
21
|
end
|
25
22
|
end; end
|
@@ -1,8 +1,8 @@
|
|
1
|
-
module SugarCRM; class
|
1
|
+
module SugarCRM; class Connection
|
2
2
|
# Retrieve a list of SugarBeans. This is the primary method for getting
|
3
3
|
# a list of SugarBeans using the REST API.
|
4
4
|
def get_entry_list(module_name, query, options={})
|
5
|
-
login! unless logged_in?
|
5
|
+
login! unless logged_in?
|
6
6
|
{
|
7
7
|
:order_by => '',
|
8
8
|
:offset => '',
|
@@ -1,7 +1,8 @@
|
|
1
|
-
module SugarCRM; class
|
2
|
-
|
1
|
+
module SugarCRM; class Connection
|
2
|
+
|
3
|
+
# Retrieves the vardef information on fields of the specified bean.
|
3
4
|
def get_module_fields(module_name)
|
4
|
-
login! unless logged_in?
|
5
|
+
login! unless logged_in?
|
5
6
|
json = <<-"EOF"
|
6
7
|
{
|
7
8
|
\"session\": \"#{@session}\"\,
|
@@ -12,4 +13,6 @@ def get_module_fields(module_name)
|
|
12
13
|
get(:get_module_fields, json)
|
13
14
|
end
|
14
15
|
|
16
|
+
alias :get_fields :get_module_fields
|
17
|
+
|
15
18
|
end; end
|