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