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.
Files changed (34) hide show
  1. data/README.rdoc +58 -19
  2. data/Rakefile +1 -4
  3. data/VERSION +1 -1
  4. data/lib/sugarcrm.rb +7 -4
  5. data/lib/sugarcrm/associations.rb +2 -0
  6. data/lib/sugarcrm/associations/association_collection.rb +143 -0
  7. data/lib/sugarcrm/associations/association_methods.rb +92 -0
  8. data/lib/sugarcrm/attributes.rb +4 -0
  9. data/lib/sugarcrm/attributes/attribute_methods.rb +131 -0
  10. data/lib/sugarcrm/attributes/attribute_serializers.rb +55 -0
  11. data/lib/sugarcrm/attributes/attribute_typecast.rb +39 -0
  12. data/lib/sugarcrm/attributes/attribute_validations.rb +37 -0
  13. data/lib/sugarcrm/base.rb +77 -21
  14. data/lib/sugarcrm/connection.rb +3 -137
  15. data/lib/sugarcrm/connection/api/get_entry_list.rb +1 -1
  16. data/lib/sugarcrm/connection/api/get_note_attachment.rb +0 -1
  17. data/lib/sugarcrm/connection/api/set_relationship.rb +3 -0
  18. data/lib/sugarcrm/connection/connection.rb +144 -0
  19. data/lib/sugarcrm/{request.rb → connection/request.rb} +2 -10
  20. data/lib/sugarcrm/{response.rb → connection/response.rb} +10 -4
  21. data/lib/sugarcrm/exceptions.rb +14 -26
  22. data/lib/sugarcrm/module.rb +19 -18
  23. data/test/connection/test_get_entry.rb +5 -5
  24. data/test/connection/test_get_module_fields.rb +1 -1
  25. data/test/connection/test_set_relationship.rb +13 -21
  26. data/test/helper.rb +1 -1
  27. data/test/test_association_collection.rb +12 -0
  28. data/test/test_associations.rb +33 -0
  29. data/test/test_connection.rb +0 -7
  30. data/test/test_module.rb +1 -1
  31. data/test/test_sugarcrm.rb +16 -8
  32. metadata +22 -28
  33. data/lib/sugarcrm/association_methods.rb +0 -46
  34. data/lib/sugarcrm/attribute_methods.rb +0 -170
@@ -2,7 +2,6 @@ module SugarCRM; class Connection
2
2
  # Retrieves an attachment from a note.
3
3
  def get_note_attachment(id)
4
4
  login! unless logged_in?
5
-
6
5
  json = <<-EOF
7
6
  {
8
7
  \"session\": \"#{@session}\"\,
@@ -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
- puts e
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(id, attributes)
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
@@ -1,28 +1,16 @@
1
1
  module SugarCRM
2
- class LoginError < RuntimeError
3
- end
4
-
5
- class EmptyResponse < RuntimeError
6
- end
7
-
8
- class UnhandledResponse < RuntimeError
9
- end
10
-
11
- class InvalidSugarCRMUrl < RuntimeError
12
- end
13
-
14
- class InvalidRequest < RuntimeError
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
@@ -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 fields?
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 fields?
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 = ["id", "date_entered", "date_modified"]
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 link_fields?
48
- handle_empty_array
52
+ self.fields unless link_fields_registered?
53
+ handle_empty_arrays
49
54
  @link_fields
50
55
  end
51
56
 
52
- def link_fields?
53
- @fields_registered
54
- end
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
- assert !(@response.deleted)
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 Hash, fields
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 TestSetRelationships < Test::Unit::TestCase
3
+ class TestSetRelationship < Test::Unit::TestCase
4
4
  context "A SugarCRM.connection" do
5
- setup do
6
- SugarCRM::Connection.new(URL, USER, PASS, {:register_modules => false, :debug => false})
7
-
8
- #retrieve ID of ACLRole and append shove it into the @id array
9
- @id = [SugarCRM::ACLRole.find_by_name("Marketing Administrator").id]
10
- assert @id
11
- end
12
-
13
- should "add the role found above to the user who's id is below when sent #set_relationship" do
14
- response = SugarCRM.connection.set_relationship(
15
- "Users","seed_will_id","aclroles",@id
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
@@ -8,7 +8,7 @@ require 'sugarcrm'
8
8
 
9
9
  class Test::Unit::TestCase
10
10
  # Replace these with your test instance
11
- URL = "http://valet/sugarcrm"
11
+ URL = "http://localhost:8080/sugarcrm"
12
12
  USER = "admin"
13
13
  PASS = 'letmein'
14
14
 
@@ -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