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