sugarcrm 0.9.10 → 0.9.11

Sign up to get free protection for your applications and to get access to all the features.
Files changed (69) hide show
  1. data/LICENSE +1 -1
  2. data/README.rdoc +38 -6
  3. data/Rakefile +1 -0
  4. data/VERSION +1 -1
  5. data/bin/sugarcrm +26 -0
  6. data/lib/sugarcrm.rb +2 -2
  7. data/lib/sugarcrm/associations/association.rb +11 -8
  8. data/lib/sugarcrm/associations/association_collection.rb +1 -1
  9. data/lib/sugarcrm/associations/association_methods.rb +1 -1
  10. data/lib/sugarcrm/attributes/attribute_methods.rb +7 -2
  11. data/lib/sugarcrm/attributes/attribute_typecast.rb +2 -2
  12. data/lib/sugarcrm/base.rb +37 -236
  13. data/lib/sugarcrm/connection/api/get_available_modules.rb +2 -2
  14. data/lib/sugarcrm/connection/api/get_document_revision.rb +2 -2
  15. data/lib/sugarcrm/connection/api/get_entries.rb +2 -2
  16. data/lib/sugarcrm/connection/api/get_entries_count.rb +1 -1
  17. data/lib/sugarcrm/connection/api/get_entry.rb +2 -2
  18. data/lib/sugarcrm/connection/api/get_entry_list.rb +2 -2
  19. data/lib/sugarcrm/connection/api/get_module_fields.rb +2 -2
  20. data/lib/sugarcrm/connection/api/get_note_attachment.rb +1 -1
  21. data/lib/sugarcrm/connection/api/get_relationships.rb +2 -2
  22. data/lib/sugarcrm/connection/api/get_report_entries.rb +1 -1
  23. data/lib/sugarcrm/connection/api/get_server_info.rb +1 -1
  24. data/lib/sugarcrm/connection/api/get_user_id.rb +1 -1
  25. data/lib/sugarcrm/connection/api/get_user_team_id.rb +1 -1
  26. data/lib/sugarcrm/connection/api/logout.rb +1 -1
  27. data/lib/sugarcrm/connection/api/seamless_login.rb +1 -1
  28. data/lib/sugarcrm/connection/api/search_by_module.rb +1 -1
  29. data/lib/sugarcrm/connection/api/set_campaign_merge.rb +1 -1
  30. data/lib/sugarcrm/connection/api/set_document_revision.rb +1 -1
  31. data/lib/sugarcrm/connection/api/set_entries.rb +1 -1
  32. data/lib/sugarcrm/connection/api/set_entry.rb +1 -1
  33. data/lib/sugarcrm/connection/api/set_note_attachment.rb +1 -1
  34. data/lib/sugarcrm/connection/api/set_relationship.rb +1 -1
  35. data/lib/sugarcrm/connection/api/set_relationships.rb +1 -1
  36. data/lib/sugarcrm/connection/connection.rb +5 -10
  37. data/lib/sugarcrm/connection/helper.rb +3 -2
  38. data/lib/sugarcrm/connection/response.rb +8 -6
  39. data/lib/sugarcrm/exceptions.rb +3 -0
  40. data/lib/sugarcrm/finders.rb +2 -0
  41. data/lib/sugarcrm/{dynamic_finder_match.rb → finders/dynamic_finder_match.rb} +0 -0
  42. data/lib/sugarcrm/finders/finder_methods.rb +236 -0
  43. data/lib/sugarcrm/module.rb +35 -11
  44. data/lib/sugarcrm/module_methods.rb +68 -23
  45. data/lib/sugarcrm/session.rb +179 -0
  46. data/test/connection/test_get_available_modules.rb +1 -4
  47. data/test/connection/test_get_entries.rb +2 -8
  48. data/test/connection/test_get_entry.rb +1 -2
  49. data/test/connection/test_get_entry_list.rb +6 -12
  50. data/test/connection/test_get_module_fields.rb +1 -4
  51. data/test/connection/test_get_relationships.rb +1 -4
  52. data/test/connection/test_get_server_info.rb +1 -4
  53. data/test/connection/test_get_user_id.rb +1 -4
  54. data/test/connection/test_get_user_team_id.rb +1 -4
  55. data/test/connection/test_login.rb +3 -5
  56. data/test/connection/test_logout.rb +1 -4
  57. data/test/connection/test_set_note_attachment.rb +1 -2
  58. data/test/connection/test_set_relationship.rb +1 -2
  59. data/test/helper.rb +6 -8
  60. data/test/test_association_collection.rb +1 -2
  61. data/test/test_associations.rb +18 -1
  62. data/test/test_connection.rb +2 -6
  63. data/test/test_module.rb +34 -6
  64. data/test/test_response.rb +2 -3
  65. data/test/test_session.rb +109 -0
  66. data/test/test_sugarcrm.rb +58 -7
  67. metadata +14 -11
  68. data/lib/sugarcrm/environment.rb +0 -63
  69. data/test/test_environment.rb +0 -45
@@ -12,7 +12,8 @@ module SugarCRM
12
12
  # Dynamically register objects based on Module name
13
13
  # I.e. a SugarCRM Module named Users will generate
14
14
  # a SugarCRM::User class.
15
- def initialize(name)
15
+ def initialize(session, name)
16
+ @session = session # the session from which this module was retrieved
16
17
  @name = name
17
18
  @klass = name.classify
18
19
  unless custom_module?
@@ -50,7 +51,7 @@ module SugarCRM
50
51
  # Returns the fields associated with the module
51
52
  def fields
52
53
  return @fields if fields_registered?
53
- all_fields = SugarCRM.connection.get_fields(@name)
54
+ all_fields = @session.connection.get_fields(@name)
54
55
  @fields = all_fields["module_fields"].with_indifferent_access
55
56
  @link_fields= all_fields["link_fields"]
56
57
  handle_empty_arrays
@@ -94,20 +95,30 @@ module SugarCRM
94
95
  def register
95
96
  return self if registered?
96
97
  mod_instance = self
98
+ sess = @session
97
99
  # class Class < SugarCRM::Base
98
100
  # module_name = "Accounts"
99
101
  # end
100
102
  klass = Class.new(SugarCRM::Base) do
101
103
  self._module = mod_instance
102
- end
104
+ self.session = sess
105
+ end
103
106
 
104
107
  # class Account < SugarCRM::Base
105
- SugarCRM.const_set self.klass, klass
108
+ @session.namespace_const.const_set self.klass, klass
106
109
  self
107
110
  end
111
+
112
+ # Deregisters the module
113
+ def deregister
114
+ return true unless registered?
115
+ klass = self.klass
116
+ @session.namespace_const.instance_eval{ remove_const klass }
117
+ true
118
+ end
108
119
 
109
120
  def registered?
110
- SugarCRM.const_defined? @klass
121
+ @session.namespace_const.const_defined? @klass
111
122
  end
112
123
 
113
124
  def to_s
@@ -122,18 +133,31 @@ module SugarCRM
122
133
  @initialized = false
123
134
 
124
135
  # Registers all of the SugarCRM Modules
125
- def register_all
126
- SugarCRM.connection.get_modules.each do |m|
127
- SugarCRM.modules << m.register
136
+ def register_all(session)
137
+ namespace = session.namespace_const
138
+ session.connection.get_modules.each do |m|
139
+ session.modules << m.register
128
140
  end
129
141
  @initialized = true
130
142
  true
131
143
  end
144
+
145
+ # Deregisters all of the SugarCRM Modules
146
+ def deregister_all(session)
147
+ namespace = session.namespace_const
148
+ session.modules.each do |m|
149
+ m.deregister
150
+ end
151
+ session.modules = []
152
+ @initialized = false
153
+ true
154
+ end
132
155
 
133
156
  # Finds a module by name, or klass name
134
- def find(name)
135
- register_all unless initialized?
136
- SugarCRM.modules.each do |m|
157
+ def find(name, session=nil)
158
+ session ||= SugarCRM.session
159
+ register_all(session) unless initialized?
160
+ session.modules.each do |m|
137
161
  return m if m.name == name
138
162
  return m if m.klass == name
139
163
  end
@@ -1,29 +1,69 @@
1
1
  module SugarCRM
2
+ # store the namespaces that have been used to prevent namespace collision
3
+ @@used_namespaces = []
4
+ def self.used_namespaces
5
+ @@used_namespaces
6
+ end
2
7
 
3
- @@connection = nil
4
- def self.connection
5
- @@connection
8
+ # return the namespaces linked to active sessions
9
+ def self.namespaces
10
+ result = []
11
+ @@used_namespaces.each{|n|
12
+ result << n if SugarCRM.const_defined? n
13
+ }
14
+ result
15
+ end
16
+
17
+ # store the various connected sessions
18
+ # key = session.id, value = session instance
19
+ @@sessions = {}
20
+ def self.sessions
21
+ @@sessions
22
+ end
23
+
24
+ def self.add_session(session)
25
+ @@used_namespaces << session.namespace unless @@used_namespaces.include? session.namespace
26
+ @@sessions[session.id] = session
27
+ end
28
+
29
+ def self.remove_session(session)
30
+ @@sessions.delete(session.id)
31
+ end
32
+
33
+ def self.session
34
+ return nil if @@sessions.size < 1
35
+ (raise SugarCRM::MultipleSessions, "There are multiple active sessions: use the session namespace instead of SugarCRM") if @@sessions.size > 1
36
+ @@sessions.values.first
6
37
  end
7
- def self.connection=(connection)
8
- @@connection = connection
38
+
39
+ def self.connection
40
+ return nil unless self.session
41
+ self.session.connection
9
42
  end
10
- def self.connect(url=SugarCRM::Environment.config[:base_url], user=SugarCRM::Environment.config[:username], pass=SugarCRM::Environment.config[:password], options={})
11
- SugarCRM::Base.establish_connection(url, user, pass, options)
43
+
44
+ def self.connect(url, user, pass, options={})
45
+ session = SugarCRM::Session.new(url, user, pass, options)
46
+ # return the namespace module
47
+ session.namespace_const
12
48
  end
49
+
13
50
  class << self
14
51
  alias :connect! :connect
15
52
  end
16
53
 
17
- @@modules = []
18
- def self.modules
19
- @@modules
20
- end
21
- def self.modules=(modules)
22
- @@modules = modules
54
+ def respond_to?(sym)
55
+ return true if @@sessions.size == 1 && SugarCRM.session.namespace_const.respond_to?(sym)
56
+ super
23
57
  end
24
58
 
25
- def self.current_user
26
- SugarCRM::User.find_by_user_name(connection.user)
59
+ def self.method_missing(sym, *args, &block)
60
+ (raise SugarCRM::NoActiveSession, "No session is active. Create a new session with 'SugarCRM.connect(...)'") if @@sessions.size < 1
61
+ (raise SugarCRM::MultipleSessions, "There are multiple active sessions: call methods on the session namespace instead of SugarCRM") if @@sessions.size > 1
62
+ if SugarCRM.session.namespace_const.respond_to? sym
63
+ SugarCRM.session.namespace_const.send(sym, *args, &block)
64
+ else
65
+ super
66
+ end
27
67
  end
28
68
 
29
69
  # If a user tries to access a SugarCRM class before they're logged in,
@@ -31,16 +71,21 @@ module SugarCRM
31
71
  # This will trigger module loading,
32
72
  # and we can then attempt to return the requested class automagically
33
73
  def self.const_missing(sym)
34
- # if we're logged in, modules should be loaded and available
35
- if SugarCRM.connection && SugarCRM.connection.logged_in?
74
+ (raise SugarCRM::MultipleSessions, "There are multiple active sessions: use the session namespace instead of SugarCRM") if @@sessions.size > 1
75
+ # make sure we have an active session
76
+ begin
77
+ Session.new unless SugarCRM.connection && SugarCRM.connection.logged_in?
78
+ rescue SugarCRM::MissingCredentials => e
79
+ # unable to load necessary login credentials from config file => pass exception on
36
80
  super
81
+ end
82
+
83
+ # if user calls (e.g.) SugarCRM::Account, delegate to SugarCRM::Namespace0::Account
84
+ namespace_const = SugarCRM.session.namespace_const
85
+ if namespace_const.const_defined? sym
86
+ namespace_const.const_get(sym)
37
87
  else
38
- # here, we initialize the environment (which happens on any method call, if singleton hasn't already been initialized)
39
- # initializing the environment will log user in if credentials present in config file
40
- # if it isn't possible to log in and access modules, pass the exception on
41
- super unless SugarCRM::Environment.connection_info_loaded?
42
- # try and return the requested module
43
- SugarCRM.const_get(sym)
88
+ super
44
89
  end
45
90
  end
46
91
  end
@@ -0,0 +1,179 @@
1
+ # This class hold an individual connection to a SugarCRM server.
2
+ # There can be several such simultaneous connections
3
+ module SugarCRM; class Session
4
+ attr_reader :config, :connection, :extensions_path, :id, :namespace, :namespace_const
5
+ attr_accessor :modules
6
+ def initialize(url, user, pass, opts={})
7
+ options = {
8
+ :debug => false,
9
+ :register_modules => true
10
+ }.merge(opts)
11
+
12
+ @modules = []
13
+ @namespace = "Namespace#{SugarCRM.used_namespaces.size}"
14
+ @config = {:base_url => url,:username => user,:password => pass,:options => options}
15
+ @extensions_path = File.join(File.dirname(__FILE__), 'extensions')
16
+
17
+ setup_connection
18
+ register_namespace
19
+ connect_and_add_session
20
+ end
21
+
22
+ # create a new session from the credentials present in a file
23
+ def self.new_from_file(path, opts={})
24
+ config = load_and_parse_config(path)
25
+ begin
26
+ session = self.new(config[:base_url], config[:username], config[:password], opts)
27
+ rescue MissingCredentials => e
28
+ return false
29
+ end
30
+ session.namespace_const
31
+ end
32
+
33
+ def setup_connection
34
+ load_config_files unless connection_info_loaded?
35
+ raise MissingCredentials, "Missing login credentials. Make sure you provide the SugarCRM URL, username, and password" unless connection_info_loaded?
36
+ end
37
+
38
+ # re-use this session and namespace if the user wants to connect with different credentials
39
+ def connect(url=nil, user=nil, pass=nil, opts={})
40
+ options = {
41
+ :debug => (@connection && @connection.debug?),
42
+ :register_modules => true
43
+ }.merge(opts)
44
+
45
+ {:base_url => url, :username => user, :password => pass}.each{|k,v|
46
+ @config[k] = v unless v.nil?
47
+ }
48
+
49
+ SugarCRM::Module.deregister_all(self)
50
+ SugarCRM.remove_session(self)
51
+ @connection = SugarCRM::Connection.new(@config[:base_url], @config[:username], @config[:password], options) if connection_info_loaded?
52
+ @connection.session = self
53
+ @id = @connection.session_id
54
+ SugarCRM.add_session(self) # must be removed and added back, as the session id (used as the hash key) changes
55
+ SugarCRM::Module.register_all(self)
56
+ load_extensions
57
+ true
58
+ end
59
+ alias :connect! :connect
60
+ alias :reconnect :connect
61
+ alias :reconnect! :connect
62
+ alias :reload! :connect
63
+
64
+ # log out from SugarCRM and cleanup (deregister modules, remove session, etc.)
65
+ def disconnect
66
+ @connection.logout
67
+ SugarCRM::Module.deregister_all(self)
68
+ namespace = @namespace
69
+ SugarCRM.instance_eval{ remove_const namespace } # remove NamespaceX from SugarCRM
70
+ SugarCRM.remove_session(self)
71
+ end
72
+ alias :disconnect! :disconnect
73
+
74
+ def extensions_folder=(folder, dirstring=nil)
75
+ path = File.expand_path(folder, dirstring)
76
+ @extensions_path = path
77
+ load_extensions
78
+ end
79
+
80
+ # load credentials from file, and (re)connect to SugarCRM
81
+ def load_config(path)
82
+ @config = self.class.load_and_parse_config(path)
83
+ reconnect(@config[:base_url], @config[:username], @config[:password]) if connection_info_loaded?
84
+ @config
85
+ end
86
+
87
+ def update_config(params)
88
+ params.each{|k,v|
89
+ @config[k.to_sym] = v
90
+ }
91
+ @config
92
+ end
93
+
94
+ # lazy load the SugarCRM version we're connecting to
95
+ def sugar_version
96
+ @version ||= @connection.get_server_info["version"]
97
+ end
98
+
99
+ private
100
+ def self.load_and_parse_config(path)
101
+ validate_path path
102
+ hash = {}
103
+ config = YAML.load_file(path)
104
+ if config && config["config"]
105
+ config["config"].each{|k,v|
106
+ hash[k.to_sym] = v
107
+ }
108
+ end
109
+ hash
110
+ end
111
+
112
+ def self.validate_path(path)
113
+ raise "Invalid path: #{path}" unless File.exists? path
114
+ end
115
+
116
+ def config_file_paths
117
+ # see README for reasoning behind the priorization
118
+ paths = ['/etc/sugarcrm.yaml', File.expand_path('~/.sugarcrm.yaml'), File.join(File.dirname(__FILE__), 'config', 'sugarcrm.yaml')]
119
+ paths.insert(1, File.join(ENV['USERPROFILE'], 'sugarcrm.yaml')) if ENV['USERPROFILE']
120
+ paths
121
+ end
122
+
123
+ def connection_info_loaded?
124
+ @config[:base_url] && @config[:username] && @config[:password]
125
+ end
126
+
127
+ def generate_namespace_module
128
+ # Create a new module to have a separate namespace in which to register the SugarCRM modules.
129
+ # This will prevent issues with modules from separate SugarCRM instances colliding within the same namespace
130
+ # (e.g. 2 SugarCRM instances where only one has custom fields on the Account module)
131
+ namespace_module = Object::Module.new do
132
+ @session = nil
133
+ def self.session
134
+ @session
135
+ end
136
+ def self.session=(sess)
137
+ @session = sess
138
+ end
139
+ def self.current_user
140
+ (@session.namespace_const)::User.find_by_user_name(@session.config[:username])
141
+ end
142
+ def self.respond_to?(sym)
143
+ return true if @session.respond_to? sym
144
+ super
145
+ end
146
+ def self.method_missing(sym, *args, &block)
147
+ raise unless @session.respond_to? sym
148
+ @session.send(sym, *args, &block)
149
+ end
150
+ end
151
+ # set the session: will be needed in SugarCRM::Base to call the API methods on the correct session
152
+ namespace_module.session = self
153
+ namespace_module
154
+ end
155
+
156
+ def load_config_files
157
+ # see README for reasoning behind the priorization
158
+ config_file_paths.each{|path|
159
+ load_config path if File.exists? path
160
+ }
161
+ end
162
+
163
+ def register_namespace
164
+ SugarCRM.const_set(@namespace, generate_namespace_module)
165
+ @namespace_const = SugarCRM.const_get(@namespace)
166
+ end
167
+
168
+ # load all the monkey patch extension files in the provided folder
169
+ def load_extensions
170
+ self.class.validate_path @extensions_path
171
+ Dir[File.join(@extensions_path, '**', '*.rb').to_s].each { |f| load(f) }
172
+ end
173
+
174
+ def connect_and_add_session
175
+ connect(@config[:base_url], @config[:username], @config[:password], @config[:options])
176
+ SugarCRM.add_session(self)
177
+ end
178
+
179
+ end; end
@@ -1,10 +1,7 @@
1
1
  require 'helper'
2
2
 
3
- class TestGetAvailableModules < Test::Unit::TestCase
3
+ class TestGetAvailableModules < ActiveSupport::TestCase
4
4
  context "A SugarCRM.connection" do
5
- setup do
6
- SugarCRM::Connection.new(URL, USER, PASS, {:debug => false})
7
- end
8
5
  should "return an array of modules when #get_modules" do
9
6
  assert_instance_of SugarCRM::Module, SugarCRM.connection.get_modules[0]
10
7
  end
@@ -1,19 +1,13 @@
1
1
  require 'helper'
2
2
 
3
- class TestGetEntries < Test::Unit::TestCase
3
+ class TestGetEntries < ActiveSupport::TestCase
4
4
  context "A SugarCRM.connection" do
5
- setup do
6
- SugarCRM::Connection.new(URL, USER, PASS, {:register_modules => true, :debug => false})
5
+ should "return an object when #get_entries" do
7
6
  @response = SugarCRM.connection.get_entries(
8
7
  "Users",
9
8
  [1,"seed_sally_id"],
10
9
  {:fields => ["first_name", "last_name"]}
11
10
  )
12
- end
13
- # should "return a list of entries when sent #get_entries." do
14
- # assert @response.response.key? "entry_list"
15
- # end
16
- should "return an object when #get_entries" do
17
11
  assert_instance_of Array, @response
18
12
  assert_instance_of SugarCRM::User, @response[0]
19
13
  end
@@ -1,9 +1,8 @@
1
1
  require 'helper'
2
2
 
3
- class TestGetEntry < Test::Unit::TestCase
3
+ class TestGetEntry < ActiveSupport::TestCase
4
4
  context "A SugarCRM.connection" do
5
5
  setup do
6
- SugarCRM::Connection.new(URL, USER, PASS, {:debug => false })
7
6
  @response = SugarCRM.connection.get_entry(
8
7
  "Users",
9
8
  1,
@@ -1,18 +1,7 @@
1
1
  require 'helper'
2
2
 
3
- class TestGetEntryList < Test::Unit::TestCase
3
+ class TestGetEntryList < ActiveSupport::TestCase
4
4
  context "A SugarCRM.connection" do
5
- setup do
6
- SugarCRM::Connection.new(URL, USER, PASS, {:register_modules => true, :debug => false})
7
- @response = SugarCRM.connection.get_entry_list(
8
- "Users",
9
- "users.deleted = 0",
10
- {:fields => ["first_name", "last_name"]}
11
- )
12
- end
13
- # should "return a list of entries when sent #get_entry_list." do
14
- # assert @response.response.key? "entry_list"
15
- # end
16
5
  should "return a list of entries when sent #get_entry_list and no fields." do
17
6
  users = SugarCRM.connection.get_entry_list(
18
7
  "Users",
@@ -21,6 +10,11 @@ class TestGetEntryList < Test::Unit::TestCase
21
10
  assert_equal "admin", users.first.user_name
22
11
  end
23
12
  should "return an object when #get_entry_list" do
13
+ @response = SugarCRM.connection.get_entry_list(
14
+ "Users",
15
+ "users.deleted = 0",
16
+ {:fields => ["first_name", "last_name"]}
17
+ )
24
18
  assert_instance_of Array, @response
25
19
  assert_instance_of SugarCRM::User, @response[0]
26
20
  end