sugarcrm 0.8.2 → 0.9.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 +58 -19
- data/Rakefile +1 -4
- data/VERSION +1 -1
- data/lib/sugarcrm.rb +7 -4
- data/lib/sugarcrm/associations.rb +2 -0
- data/lib/sugarcrm/associations/association_collection.rb +143 -0
- data/lib/sugarcrm/associations/association_methods.rb +92 -0
- data/lib/sugarcrm/attributes.rb +4 -0
- data/lib/sugarcrm/attributes/attribute_methods.rb +131 -0
- data/lib/sugarcrm/attributes/attribute_serializers.rb +55 -0
- data/lib/sugarcrm/attributes/attribute_typecast.rb +39 -0
- data/lib/sugarcrm/attributes/attribute_validations.rb +37 -0
- data/lib/sugarcrm/base.rb +77 -21
- data/lib/sugarcrm/connection.rb +3 -137
- data/lib/sugarcrm/connection/api/get_entry_list.rb +1 -1
- data/lib/sugarcrm/connection/api/get_note_attachment.rb +0 -1
- data/lib/sugarcrm/connection/api/set_relationship.rb +3 -0
- data/lib/sugarcrm/connection/connection.rb +144 -0
- data/lib/sugarcrm/{request.rb → connection/request.rb} +2 -10
- data/lib/sugarcrm/{response.rb → connection/response.rb} +10 -4
- data/lib/sugarcrm/exceptions.rb +14 -26
- data/lib/sugarcrm/module.rb +19 -18
- data/test/connection/test_get_entry.rb +5 -5
- data/test/connection/test_get_module_fields.rb +1 -1
- data/test/connection/test_set_relationship.rb +13 -21
- data/test/helper.rb +1 -1
- data/test/test_association_collection.rb +12 -0
- data/test/test_associations.rb +33 -0
- data/test/test_connection.rb +0 -7
- data/test/test_module.rb +1 -1
- data/test/test_sugarcrm.rb +16 -8
- metadata +22 -28
- data/lib/sugarcrm/association_methods.rb +0 -46
- data/lib/sugarcrm/attribute_methods.rb +0 -170
@@ -19,6 +19,9 @@ module SugarCRM; class Connection
|
|
19
19
|
}
|
20
20
|
EOF
|
21
21
|
json.gsub!(/^\s{6}/,'')
|
22
|
+
# TODO: Add a handler for the response. By default it returns a hash like:
|
23
|
+
# {failed => 0, deleted => 0, created => 1}. We should add this to the
|
24
|
+
# Response.handle method and return true/false depending on the outcome.
|
22
25
|
send!(:set_relationship, json)
|
23
26
|
end
|
24
27
|
end; end
|
@@ -0,0 +1,144 @@
|
|
1
|
+
module SugarCRM; class Connection
|
2
|
+
|
3
|
+
URL = "/service/v2/rest.php"
|
4
|
+
# Set this to filter out debug output on a certain method (i.e. get_modules, or get_fields)
|
5
|
+
DONT_SHOW_DEBUG_FOR = []
|
6
|
+
RESPONSE_IS_NOT_JSON = [:get_user_id, :get_user_team_id]
|
7
|
+
|
8
|
+
attr :url, true
|
9
|
+
attr :user, false
|
10
|
+
attr :pass, false
|
11
|
+
attr :session, true
|
12
|
+
attr :connection, true
|
13
|
+
attr :options, true
|
14
|
+
attr :request, true
|
15
|
+
attr :response, true
|
16
|
+
|
17
|
+
# This is the singleton connection class.
|
18
|
+
def initialize(url, user, pass, options={})
|
19
|
+
@options = {
|
20
|
+
:debug => false,
|
21
|
+
:register_modules => true
|
22
|
+
}.merge(options)
|
23
|
+
@url = URI.parse(url)
|
24
|
+
@user = user
|
25
|
+
@pass = pass
|
26
|
+
@request = ""
|
27
|
+
@response = ""
|
28
|
+
resolve_url
|
29
|
+
login!
|
30
|
+
self
|
31
|
+
end
|
32
|
+
|
33
|
+
# Check to see if we are logged in
|
34
|
+
def logged_in?
|
35
|
+
connect! unless connected?
|
36
|
+
@session ? true : false
|
37
|
+
end
|
38
|
+
|
39
|
+
# Login
|
40
|
+
def login!
|
41
|
+
@session = login["id"]
|
42
|
+
raise SugarCRM::LoginError, "Invalid Login" unless logged_in?
|
43
|
+
SugarCRM.connection = self
|
44
|
+
SugarCRM::Base.connection = self
|
45
|
+
Module.register_all if @options[:register_modules]
|
46
|
+
end
|
47
|
+
|
48
|
+
# Check to see if we are connected
|
49
|
+
def connected?
|
50
|
+
return false unless @connection
|
51
|
+
return false unless @connection.started?
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
# Connect
|
56
|
+
def connect!
|
57
|
+
@connection = Net::HTTP.new(@url.host, @url.port)
|
58
|
+
if @url.scheme == "https"
|
59
|
+
@connection.use_ssl = true
|
60
|
+
@connection.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
61
|
+
end
|
62
|
+
@connection.start
|
63
|
+
end
|
64
|
+
|
65
|
+
# Send a request to the Sugar Instance
|
66
|
+
def send!(method, json)
|
67
|
+
@request = SugarCRM::Request.new(@url, method, json, @options[:debug])
|
68
|
+
begin
|
69
|
+
if @request.length > 3900
|
70
|
+
@response = @connection.post(@url.path, @request)
|
71
|
+
else
|
72
|
+
@response = @connection.get(@url.path.dup + "?" + @request.to_s)
|
73
|
+
end
|
74
|
+
rescue Errno::ECONNRESET
|
75
|
+
retry!(method, json)
|
76
|
+
rescue EOFError
|
77
|
+
retry!(method, json)
|
78
|
+
end
|
79
|
+
handle_response
|
80
|
+
end
|
81
|
+
|
82
|
+
# Sometimes our connection just disappears but we still have a session.
|
83
|
+
# This method forces a reconnect and relogin to update the session and resend
|
84
|
+
# the request.
|
85
|
+
def retry!(method, json)
|
86
|
+
connect!
|
87
|
+
login!
|
88
|
+
send!(method,json)
|
89
|
+
end
|
90
|
+
|
91
|
+
def debug=(debug)
|
92
|
+
options[:debug] = debug
|
93
|
+
end
|
94
|
+
|
95
|
+
def debug?
|
96
|
+
options[:debug]
|
97
|
+
end
|
98
|
+
|
99
|
+
private
|
100
|
+
|
101
|
+
def handle_response
|
102
|
+
case @response
|
103
|
+
when Net::HTTPOK
|
104
|
+
return process_response
|
105
|
+
when Net::HTTPNotFound
|
106
|
+
raise SugarCRM::InvalidSugarCRMUrl, "#{@url} is invalid"
|
107
|
+
when Net::HTTPInternalServerError
|
108
|
+
raise SugarCRM::InvalidRequest, "#{@request} is invalid"
|
109
|
+
else
|
110
|
+
if @options[:debug]
|
111
|
+
puts "#{@request.method}: Raw Response:"
|
112
|
+
puts @response.body
|
113
|
+
puts "\n"
|
114
|
+
end
|
115
|
+
raise SugarCRM::UnhandledResponse, "Can't handle response #{@response}"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def process_response
|
120
|
+
# Complain if our body is empty.
|
121
|
+
raise SugarCRM::EmptyResponse unless @response.body
|
122
|
+
# Some methods are dumb and don't return a JSON Response
|
123
|
+
return @response.body if RESPONSE_IS_NOT_JSON.include? @request.method
|
124
|
+
# Push it through the old meat grinder.
|
125
|
+
response_json = JSON.parse @response.body
|
126
|
+
# Empty result. Is this wise?
|
127
|
+
return false if response_json["result_count"] == 0
|
128
|
+
# Filter debugging on REALLY BIG responses
|
129
|
+
if @options[:debug] && !(DONT_SHOW_DEBUG_FOR.include? @request.method)
|
130
|
+
puts "#{@request.method}: JSON Response:"
|
131
|
+
pp response_json
|
132
|
+
puts "\n"
|
133
|
+
end
|
134
|
+
return response_json
|
135
|
+
end
|
136
|
+
|
137
|
+
def resolve_url
|
138
|
+
# Appends the rest.php path onto the end of the URL if it's not included
|
139
|
+
if @url.path !~ /rest.php$/
|
140
|
+
@url.path += URL
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
end; end
|
@@ -1,7 +1,4 @@
|
|
1
|
-
module SugarCRM
|
2
|
-
|
3
|
-
class Request
|
4
|
-
|
1
|
+
module SugarCRM; class Request
|
5
2
|
attr :request, true
|
6
3
|
attr :url, true
|
7
4
|
attr :method, true
|
@@ -12,12 +9,10 @@ class Request
|
|
12
9
|
@url = url
|
13
10
|
@method = method
|
14
11
|
@json = json
|
15
|
-
|
16
12
|
@request = 'method=' << @method.to_s
|
17
13
|
@request << '&input_type=JSON'
|
18
14
|
@request << '&response_type=JSON'
|
19
15
|
@request << '&rest_data=' << @json
|
20
|
-
|
21
16
|
if debug
|
22
17
|
puts "#{method}: Request:"
|
23
18
|
pp @request
|
@@ -33,7 +28,4 @@ class Request
|
|
33
28
|
def to_s
|
34
29
|
URI.escape(@request)
|
35
30
|
end
|
36
|
-
|
37
|
-
end
|
38
|
-
|
39
|
-
end
|
31
|
+
end; end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
module SugarCRM; class Response
|
2
|
-
|
3
2
|
class << self
|
4
3
|
# This class handles the response from the server.
|
5
4
|
# It tries to convert the response into an object such as User
|
@@ -8,11 +7,17 @@ module SugarCRM; class Response
|
|
8
7
|
r = new(json)
|
9
8
|
begin
|
10
9
|
return r.to_obj
|
10
|
+
rescue UninitializedModule => e
|
11
|
+
raise e
|
12
|
+
rescue InvalidAttribute => e
|
13
|
+
raise e
|
14
|
+
rescue InvalidAttributeType => e
|
15
|
+
raise e
|
11
16
|
rescue => e
|
12
17
|
if SugarCRM.connection.debug?
|
13
18
|
puts "Failed to process JSON:"
|
14
19
|
pp json
|
15
|
-
|
20
|
+
raise e
|
16
21
|
end
|
17
22
|
return json
|
18
23
|
end
|
@@ -23,6 +28,7 @@ module SugarCRM; class Response
|
|
23
28
|
|
24
29
|
def initialize(json)
|
25
30
|
@response = json
|
31
|
+
@response = json.with_indifferent_access if json.is_a? Hash
|
26
32
|
end
|
27
33
|
|
28
34
|
# Tries to instantiate and return an object with the values
|
@@ -35,13 +41,13 @@ module SugarCRM; class Response
|
|
35
41
|
@response["entry_list"].each do |object|
|
36
42
|
attributes = []
|
37
43
|
_module = resolve_module(object)
|
38
|
-
id = object["id"]
|
44
|
+
#id = object["id"]
|
39
45
|
attributes = flatten_name_value_list(object)
|
40
46
|
if SugarCRM.const_get(_module)
|
41
47
|
if attributes.length == 0
|
42
48
|
raise AttributeParsingError, "response contains objects without attributes!"
|
43
49
|
end
|
44
|
-
objects << SugarCRM.const_get(_module).new(
|
50
|
+
objects << SugarCRM.const_get(_module).new(attributes)
|
45
51
|
else
|
46
52
|
raise InvalidModule, "#{_module} does not exist, or is not accessible"
|
47
53
|
end
|
data/lib/sugarcrm/exceptions.rb
CHANGED
@@ -1,28 +1,16 @@
|
|
1
1
|
module SugarCRM
|
2
|
-
class LoginError < RuntimeError
|
3
|
-
end
|
4
|
-
|
5
|
-
class
|
6
|
-
end
|
7
|
-
|
8
|
-
class
|
9
|
-
end
|
10
|
-
|
11
|
-
class
|
12
|
-
end
|
13
|
-
|
14
|
-
class
|
15
|
-
end
|
16
|
-
|
17
|
-
class InvalidModule <RuntimeError
|
18
|
-
end
|
19
|
-
|
20
|
-
class AttributeParsingError < RuntimeError
|
21
|
-
end
|
22
|
-
|
23
|
-
class RecordNotFound < RuntimeError
|
24
|
-
end
|
25
|
-
|
26
|
-
class InvalidRecord < RuntimeError
|
27
|
-
end
|
2
|
+
class LoginError < RuntimeError; end
|
3
|
+
class EmptyResponse < RuntimeError; end
|
4
|
+
class UnhandledResponse < RuntimeError; end
|
5
|
+
class InvalidSugarCRMUrl < RuntimeError; end
|
6
|
+
class InvalidRequest < RuntimeError; end
|
7
|
+
class InvalidModule <RuntimeError; end
|
8
|
+
class AttributeParsingError < RuntimeError; end
|
9
|
+
class RecordNotFound < RuntimeError; end
|
10
|
+
class InvalidRecord < RuntimeError; end
|
11
|
+
class RecordSaveFailed < RuntimeError; end
|
12
|
+
class AssociationFailed < RuntimeError; end
|
13
|
+
class UninitializedModule < RuntimeError; end
|
14
|
+
class InvalidAttribute < RuntimeError; end
|
15
|
+
class InvalidAttributeType < RuntimeError; end
|
28
16
|
end
|
data/lib/sugarcrm/module.rb
CHANGED
@@ -15,48 +15,49 @@ module SugarCRM
|
|
15
15
|
@klass = name.classify
|
16
16
|
@table_name = name.tableize
|
17
17
|
@fields = {}
|
18
|
-
@link_fields
|
18
|
+
@link_fields = {}
|
19
19
|
@fields_registered = false
|
20
20
|
self
|
21
21
|
end
|
22
22
|
|
23
|
+
# Returns the fields associated with the module
|
23
24
|
def fields
|
24
|
-
return @fields if
|
25
|
+
return @fields if fields_registered?
|
25
26
|
all_fields = SugarCRM.connection.get_fields(@name)
|
26
|
-
@fields = all_fields["module_fields"]
|
27
|
-
@link_fields= all_fields["link_fields"]
|
27
|
+
@fields = all_fields["module_fields"].with_indifferent_access
|
28
|
+
@link_fields= all_fields["link_fields"]
|
29
|
+
handle_empty_arrays
|
28
30
|
@fields_registered = true
|
29
31
|
@fields
|
30
32
|
end
|
31
33
|
|
32
|
-
def
|
34
|
+
def fields_registered?
|
33
35
|
@fields_registered
|
34
36
|
end
|
35
37
|
|
38
|
+
alias :link_fields_registered? :fields_registered?
|
39
|
+
|
40
|
+
# Returns the required fields
|
36
41
|
def required_fields
|
37
42
|
required_fields = []
|
38
|
-
ignore_fields = [
|
43
|
+
ignore_fields = [:id, :date_entered, :date_modified]
|
39
44
|
self.fields.each_value do |field|
|
40
|
-
next if ignore_fields.include? field["name"]
|
41
|
-
required_fields << field["name"] if field["required"] == 1
|
45
|
+
next if ignore_fields.include? field["name"].to_sym
|
46
|
+
required_fields << field["name"].to_sym if field["required"] == 1
|
42
47
|
end
|
43
48
|
required_fields
|
44
49
|
end
|
45
50
|
|
46
51
|
def link_fields
|
47
|
-
self.fields unless
|
48
|
-
|
52
|
+
self.fields unless link_fields_registered?
|
53
|
+
handle_empty_arrays
|
49
54
|
@link_fields
|
50
55
|
end
|
51
56
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
def handle_empty_array
|
57
|
-
if @link_fields.class == Array && @link_fields.length == 0
|
58
|
-
@link_fields = {}
|
59
|
-
end
|
57
|
+
# TODO: Refactor this to be less repetitive
|
58
|
+
def handle_empty_arrays
|
59
|
+
@fields = {}.with_indifferent_access if @fields.length == 0
|
60
|
+
@link_fields = {}.with_indifferent_access if @link_fields.length == 0
|
60
61
|
end
|
61
62
|
|
62
63
|
# Registers a single module by name
|
@@ -7,17 +7,17 @@ class TestGetEntry < Test::Unit::TestCase
|
|
7
7
|
@response = SugarCRM.connection.get_entry(
|
8
8
|
"Users",
|
9
9
|
1,
|
10
|
-
{:fields => ["first_name", "last_name"]}
|
10
|
+
{:fields => ["first_name", "last_name", "deleted", "date_modified"]}
|
11
11
|
)
|
12
12
|
end
|
13
|
-
# should "return a single entry when sent #get_entry." do
|
14
|
-
# assert @response.response. "entry_list"
|
15
|
-
# end
|
16
13
|
should "return an object when #get_entry" do
|
17
14
|
assert_instance_of SugarCRM::User, @response
|
18
15
|
end
|
19
16
|
should "typecast boolean fields properly" do
|
20
|
-
|
17
|
+
assert_instance_of FalseClass, @response.deleted
|
18
|
+
end
|
19
|
+
should "typecast date_time fields properly" do
|
20
|
+
assert_instance_of DateTime, @response.date_modified
|
21
21
|
end
|
22
22
|
end
|
23
23
|
end
|
@@ -7,7 +7,7 @@ class TestModuleFields < Test::Unit::TestCase
|
|
7
7
|
end
|
8
8
|
should "return a hash of module fields when #get_module_fields" do
|
9
9
|
fields = SugarCRM.connection.get_module_fields("Users")
|
10
|
-
assert_instance_of
|
10
|
+
assert_instance_of ActiveSupport::HashWithIndifferentAccess, fields
|
11
11
|
assert "address_city", fields.keys[0]
|
12
12
|
end
|
13
13
|
end
|
@@ -1,27 +1,19 @@
|
|
1
1
|
require 'helper'
|
2
2
|
|
3
|
-
class
|
3
|
+
class TestSetRelationship < Test::Unit::TestCase
|
4
4
|
context "A SugarCRM.connection" do
|
5
|
-
|
6
|
-
SugarCRM
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
response = SugarCRM.connection.set_relationship(
|
15
|
-
|
16
|
-
|
17
|
-
assert_equal("failed0deleted0created1", response.to_s)
|
18
|
-
end
|
19
|
-
|
20
|
-
should "remove the role that was previously added when sent #set_relationship and delete=1" do
|
21
|
-
response = SugarCRM.connection.set_relationship(
|
22
|
-
"Users","seed_will_id","aclroles",@id,opts={:delete => 1}
|
23
|
-
)
|
24
|
-
assert_equal("failed0deleted1created0", response.to_s)
|
5
|
+
should "add and remove a relationship when #set_relationship" do
|
6
|
+
SugarCRM.connect(URL, USER, PASS, {:debug => false})
|
7
|
+
meeting = SugarCRM::Meeting.new
|
8
|
+
meeting.date_start = DateTime.now
|
9
|
+
meeting.duration_hours = 0.5
|
10
|
+
meeting.name = "Stupid Meeting"
|
11
|
+
assert meeting.save!
|
12
|
+
response = SugarCRM.connection.set_relationship("Users","1","meetings", [meeting.id])
|
13
|
+
assert response["created"] == 1
|
14
|
+
response = SugarCRM.connection.set_relationship("Users","1","meetings", [meeting.id], {:delete => 1})
|
15
|
+
assert response["deleted"] == 1
|
16
|
+
assert meeting.delete
|
25
17
|
end
|
26
18
|
end
|
27
19
|
end
|
data/test/helper.rb
CHANGED
@@ -0,0 +1,12 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestAssociationCollection < Test::Unit::TestCase
|
4
|
+
context "A SugarCRM::AssociationCollection instance" do
|
5
|
+
should "create a new instance when #new" do
|
6
|
+
SugarCRM.connect!(URL, USER, PASS)
|
7
|
+
u = SugarCRM::User.find("seed_sarah_id")
|
8
|
+
ac = SugarCRM::AssociationCollection.new(u,:email_addresses)
|
9
|
+
assert_instance_of SugarCRM::AssociationCollection, ac
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|