sugarcrm 0.9.8 → 0.9.9
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +1 -1
- data/README.rdoc +44 -0
- data/Rakefile +6 -6
- data/VERSION +1 -1
- data/lib/sugarcrm.rb +3 -0
- data/lib/sugarcrm/associations/association.rb +14 -24
- data/lib/sugarcrm/associations/association_cache.rb +12 -6
- data/lib/sugarcrm/associations/association_collection.rb +4 -1
- data/lib/sugarcrm/associations/association_methods.rb +17 -2
- data/lib/sugarcrm/base.rb +54 -4
- data/lib/sugarcrm/config/sugarcrm.yaml +10 -0
- data/lib/sugarcrm/connection/api/get_document_revision.rb +0 -1
- data/lib/sugarcrm/connection/api/get_entries.rb +1 -1
- data/lib/sugarcrm/connection/api/get_entry.rb +1 -1
- data/lib/sugarcrm/connection/api/login.rb +6 -6
- data/lib/sugarcrm/connection/connection.rb +6 -3
- data/lib/sugarcrm/connection/request.rb +3 -3
- data/lib/sugarcrm/connection/response.rb +2 -3
- data/lib/sugarcrm/environment.rb +48 -0
- data/lib/sugarcrm/extensions/README.txt +23 -0
- data/lib/sugarcrm/module.rb +10 -3
- data/lib/sugarcrm/module_methods.rb +18 -1
- data/test/config_test.yaml +15 -0
- data/test/extensions_test/patch.rb +9 -0
- data/test/test_association.rb +18 -1
- data/test/test_environment.rb +38 -0
- data/test/test_sugarcrm.rb +103 -12
- metadata +48 -32
data/Gemfile
CHANGED
@@ -2,6 +2,7 @@ source "http://rubygems.org"
|
|
2
2
|
|
3
3
|
gem "activesupport", ">= 3.0.0", :require => "active_support"
|
4
4
|
gem "i18n"
|
5
|
+
gem "json"
|
5
6
|
|
6
7
|
# Add dependencies to develop your gem here.
|
7
8
|
# Include everything needed to run rake, tests, features, etc.
|
@@ -9,5 +10,4 @@ group :development do
|
|
9
10
|
gem "shoulda", ">= 0"
|
10
11
|
gem "bundler", "~> 1.0.0"
|
11
12
|
gem "jeweler", "~> 1.5.2"
|
12
|
-
gem "rcov", ">= 0"
|
13
13
|
end
|
data/README.rdoc
CHANGED
@@ -143,6 +143,46 @@ Instead of SugarCRM.connection.get_entry("Users", "1") you could use SugarCRM::U
|
|
143
143
|
}
|
144
144
|
)
|
145
145
|
|
146
|
+
== USING A CONFIGURATION FILE
|
147
|
+
|
148
|
+
If you want to use a configuration file instead of always specifying the url, username, and password to connect to SugarCRM, you can either
|
149
|
+
|
150
|
+
* add your credentials to `/etc/sugarcrm.yaml`
|
151
|
+
* add your credentials to `~/.sugarcrm.yaml`
|
152
|
+
* add your credentials to `config/sugarcrm.yaml` (will need to be copied each time you upgrade or reinstall the gem)
|
153
|
+
* add your credentials to a YAML file and call `SugarCRM::Environment.load_config` followed by the absolute path to your configuration file
|
154
|
+
|
155
|
+
If there are several configuration files, they are loaded sequentially in the order above and will overwrite previous values (if present). This allows you to (e.g.) have a config file in `/etc/sugarcrm.yaml` with system-wide configuration information (such as the url where SugarCRM is located) and/or defaults. Each developer/user can then have his personal configuration file in `~/.sugarcrm.yaml` with his own username and password. A developer could also specify a different location for the SugarCRM instance (e.g. a local testing instance) in his configuration file, which will take precedence over the value in `/etc/sugarcrm.yaml`.
|
156
|
+
|
157
|
+
Your configuration should be in YAML format:
|
158
|
+
|
159
|
+
config:
|
160
|
+
base_url: http://127.0.0.1/sugarcrm
|
161
|
+
username: admin
|
162
|
+
password: letmein
|
163
|
+
|
164
|
+
An example, accompanied by instructions, can be found in the `config/sugarcrm.yaml` file. In addition, a working example used for testing can be found in `test/config_test.yaml`
|
165
|
+
|
166
|
+
== USING THE GEM IN A CONSOLE
|
167
|
+
|
168
|
+
1. Type `irb` in your command prompt
|
169
|
+
|
170
|
+
2. Require the gem with `require 'sugarcrm'`
|
171
|
+
|
172
|
+
3. * if your login credentials are stored in the `config/sugarcrm.yaml` file, you have been automagically logged in already ;
|
173
|
+
* if your login credentials are stored in a different config file, just call `SugarCRM::Environment.load_config` followed by the absolute path to your config file. This will log you in automatically ;
|
174
|
+
* if you don't have a configuration file, you can still call the basic `SugarCRM.connect` and give it the proper arguments (see documentation above)
|
175
|
+
|
176
|
+
4. You now have full access to the gem's functionality, e.g. `puts SugarCRM::Account.first.name`
|
177
|
+
|
178
|
+
== EXPANDING THE GEM
|
179
|
+
|
180
|
+
If you want to expand the gem's capabilities (e.g. to add methods specific to your environment), you can either
|
181
|
+
|
182
|
+
* drop your `*.rb` files in `lib/sugarcrm/extensions/` (see the README in that folder)
|
183
|
+
|
184
|
+
* drop your `*.rb` files in any other folder and call `SugarCRM::Environment.extensions_folder = ` followed by the absolute path to the folder containing your extensions
|
185
|
+
|
146
186
|
== REQUIREMENTS:
|
147
187
|
|
148
188
|
* >= activesupport 3.0.0 gem
|
@@ -151,6 +191,10 @@ Instead of SugarCRM.connection.get_entry("Users", "1") you could use SugarCRM::U
|
|
151
191
|
|
152
192
|
* sudo gem install sugarcrm
|
153
193
|
|
194
|
+
== TEST:
|
195
|
+
|
196
|
+
Set the values in `test/helper.rb` to point to a SugarCRM instance with demo data
|
197
|
+
|
154
198
|
== Note on Patches/Pull Requests
|
155
199
|
|
156
200
|
* Fork the project.
|
data/Rakefile
CHANGED
@@ -28,12 +28,12 @@ Rake::TestTask.new(:test) do |test|
|
|
28
28
|
test.verbose = true
|
29
29
|
end
|
30
30
|
|
31
|
-
require 'rcov/rcovtask'
|
32
|
-
Rcov::RcovTask.new do |test|
|
33
|
-
test.libs << 'test'
|
34
|
-
test.pattern = 'test/**/test_*.rb'
|
35
|
-
test.verbose = true
|
36
|
-
end
|
31
|
+
#require 'rcov/rcovtask'
|
32
|
+
#Rcov::RcovTask.new do |test|
|
33
|
+
# test.libs << 'test'
|
34
|
+
# test.pattern = 'test/**/test_*.rb'
|
35
|
+
# test.verbose = true
|
36
|
+
#end
|
37
37
|
|
38
38
|
task :default => :test
|
39
39
|
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.9.
|
1
|
+
0.9.9
|
data/lib/sugarcrm.rb
CHANGED
@@ -1,10 +1,13 @@
|
|
1
1
|
require 'net/https'
|
2
2
|
require 'pp'
|
3
3
|
require 'set'
|
4
|
+
require 'cgi'
|
4
5
|
require 'uri'
|
5
6
|
require 'rubygems'
|
6
7
|
require 'active_support/core_ext'
|
8
|
+
require 'json'
|
7
9
|
|
10
|
+
require 'sugarcrm/environment'
|
8
11
|
require 'sugarcrm/module_methods'
|
9
12
|
require 'sugarcrm/connection'
|
10
13
|
require 'sugarcrm/exceptions'
|
@@ -1,12 +1,13 @@
|
|
1
1
|
module SugarCRM
|
2
2
|
# Represents an association and it's metadata
|
3
3
|
class Association
|
4
|
-
attr :owner
|
5
|
-
attr :target
|
6
|
-
attr :link_field
|
7
|
-
attr :attributes
|
8
|
-
attr :methods
|
4
|
+
attr :owner, true
|
5
|
+
attr :target, true
|
6
|
+
attr :link_field, true
|
7
|
+
attr :attributes, true
|
8
|
+
attr :methods, true
|
9
9
|
|
10
|
+
# TODO: Describe this.
|
10
11
|
def initialize(owner,link_field,opts={})
|
11
12
|
@options = { :define_methods? => true }.merge! opts
|
12
13
|
@owner = owner
|
@@ -35,11 +36,11 @@ module SugarCRM
|
|
35
36
|
end
|
36
37
|
|
37
38
|
# Attempts to determine the class of the target in the association
|
39
|
+
# TODO: Write tests for this.
|
38
40
|
def resolve_target
|
39
41
|
# Use the link_field name first
|
40
42
|
klass = @link_field.singularize.camelize
|
41
43
|
return "SugarCRM::#{klass}".constantize if SugarCRM.const_defined? klass
|
42
|
-
|
43
44
|
# Use the link_field attribute "module"
|
44
45
|
if @attributes["module"].length > 0
|
45
46
|
module_name = SugarCRM::Module.find(@attributes["module"])
|
@@ -54,14 +55,12 @@ module SugarCRM
|
|
54
55
|
end
|
55
56
|
|
56
57
|
# Generates the association proxy method for related module
|
57
|
-
def define_method(link_field
|
58
|
-
pretty_name ||= link_field
|
58
|
+
def define_method(link_field)
|
59
59
|
@owner.class.module_eval %Q?
|
60
|
-
def #{
|
60
|
+
def #{link_field}
|
61
61
|
query_association :#{link_field}
|
62
62
|
end
|
63
63
|
?
|
64
|
-
pretty_name
|
65
64
|
end
|
66
65
|
|
67
66
|
# Defines methods for accessing the association target on the owner class.
|
@@ -71,10 +70,10 @@ module SugarCRM
|
|
71
70
|
def define_methods
|
72
71
|
methods = []
|
73
72
|
pretty_name = humanized_link_name(@link_field)
|
74
|
-
methods << define_method(
|
73
|
+
methods << define_method(@link_field)
|
75
74
|
if pretty_name != @link_field
|
76
75
|
@owner.class.module_eval %Q?
|
77
|
-
alias :#{
|
76
|
+
alias :#{pretty_name} #{@link_field}
|
78
77
|
?
|
79
78
|
methods << @link_field
|
80
79
|
end
|
@@ -85,18 +84,9 @@ module SugarCRM
|
|
85
84
|
# e.g. if a custom relationship is defined in Studio between Tasks and Documents,
|
86
85
|
# the link_field will be `tasks_documents` but a human would call the relationship `documents`
|
87
86
|
def humanized_link_name(link_field)
|
88
|
-
#
|
89
|
-
|
90
|
-
|
91
|
-
# Determine the parts we don't want
|
92
|
-
# SugarCRM::Contact => ["contacts", "contact"]
|
93
|
-
o = @owner.class._module.table_name
|
94
|
-
# Use array subtraction to remove parts representing the owner side of the relationship
|
95
|
-
# ["contact", "accounts"] - ["contacts", "contact"] => ["accounts"]
|
96
|
-
t = m - [o, o.singularize]
|
97
|
-
# Reassemble whatever's left
|
98
|
-
# "accounts"
|
99
|
-
t.join('_')
|
87
|
+
# the module name is used to function properly with modules containing '_' (e.g. a custom module abc_sale : custom modules need a prefix (abc here) so they will always have a '_' in their table name)
|
88
|
+
return link_field unless link_field.to_s =~ /((.*)_)?#{Regexp.quote(@owner.class._module.name.downcase)}(_(.*))?/
|
89
|
+
[$2, $4].compact.join('_')
|
100
90
|
end
|
101
91
|
|
102
92
|
end
|
@@ -7,7 +7,17 @@ module SugarCRM; module AssociationCache
|
|
7
7
|
@association_cache.symbolize_keys.include? association.to_sym
|
8
8
|
end
|
9
9
|
|
10
|
-
|
10
|
+
# Updates an association cache entry if it's been initialized
|
11
|
+
def update_association_cache_for(association, target, action=:add)
|
12
|
+
return unless association_cached? association
|
13
|
+
case action
|
14
|
+
when :add
|
15
|
+
return if @association_cache[association].collection.include? target
|
16
|
+
@association_cache[association].push(target) # don't use `<<` because overriden method in AssociationCollection gets called instead
|
17
|
+
when :delete
|
18
|
+
@association_cache[association].delete target
|
19
|
+
end
|
20
|
+
end
|
11
21
|
|
12
22
|
# Returns true if an association collection has changed
|
13
23
|
def associations_changed?
|
@@ -17,11 +27,7 @@ module SugarCRM; module AssociationCache
|
|
17
27
|
false
|
18
28
|
end
|
19
29
|
|
20
|
-
|
21
|
-
def update_association_cache_for(association, target)
|
22
|
-
# only add to the cache if the relationship has been queried
|
23
|
-
@association_cache[association] << target if association_cached? association
|
24
|
-
end
|
30
|
+
protected
|
25
31
|
|
26
32
|
# Resets the association cache
|
27
33
|
def clear_association_cache
|
@@ -2,6 +2,8 @@ module SugarCRM
|
|
2
2
|
# A class for handling association collections. Basically just an extension of Array
|
3
3
|
# doesn't actually load the records from Sugar until you invoke one of the public methods
|
4
4
|
class AssociationCollection
|
5
|
+
|
6
|
+
attr_reader :collection
|
5
7
|
|
6
8
|
# creates a new instance of an AssociationCollection
|
7
9
|
# Owner is the parent object, and association is the target
|
@@ -66,7 +68,8 @@ module SugarCRM
|
|
66
68
|
record.save! if record.new?
|
67
69
|
result = true
|
68
70
|
result = false if include?(record)
|
69
|
-
@
|
71
|
+
@owner.update_association_cache_for(@association, record, :add)
|
72
|
+
record.update_association_cache_for(record.associations.find!(@owner).link_field, @owner, :add)
|
70
73
|
result && self
|
71
74
|
end
|
72
75
|
alias :add :<<
|
@@ -24,7 +24,7 @@ module SugarCRM; module AssociationMethods
|
|
24
24
|
# In contrast to using account.contacts << contact, this method doesn't load the relationships
|
25
25
|
# before setting the new relationship.
|
26
26
|
# This method is useful when certain modules have many links to other modules: not loading the
|
27
|
-
# relationships allows one
|
27
|
+
# relationships allows one to avoid a Timeout::Error
|
28
28
|
def associate!(target,opts={})
|
29
29
|
targets = Array.wrap(target)
|
30
30
|
targets.each do |t|
|
@@ -37,10 +37,25 @@ module SugarCRM; module AssociationMethods
|
|
37
37
|
raise AssociationFailed,
|
38
38
|
"Couldn't associate #{self.class._module.name}: #{self.id} -> #{t}: #{t.id}!"
|
39
39
|
end
|
40
|
-
|
40
|
+
# We need to update the association cache for any changes we make.
|
41
|
+
if opts[:delete]
|
42
|
+
update_association_cache_for(association.link_field, t, :delete)
|
43
|
+
t.update_association_cache_for(association.link_field, self, :delete)
|
44
|
+
else
|
45
|
+
update_association_cache_for(association.link_field, t, :add)
|
46
|
+
t.update_association_cache_for(t.associations.find!(self).link_field, self, :add)
|
47
|
+
end
|
41
48
|
end
|
42
49
|
true
|
43
50
|
end
|
51
|
+
alias :relate! :associate!
|
52
|
+
|
53
|
+
# Removes a relationship between the current object and the target object
|
54
|
+
# TODO: Write a test for this.
|
55
|
+
def disassociate!(target)
|
56
|
+
associate!(target,{:delete => 1})
|
57
|
+
end
|
58
|
+
alias :unrelate! :disassociate!
|
44
59
|
|
45
60
|
protected
|
46
61
|
|
data/lib/sugarcrm/base.rb
CHANGED
@@ -27,7 +27,7 @@ module SugarCRM; class Base
|
|
27
27
|
def establish_connection(url, user, pass, opts={})
|
28
28
|
options = {
|
29
29
|
:debug => false,
|
30
|
-
:register_modules => true
|
30
|
+
:register_modules => true
|
31
31
|
}.merge(opts)
|
32
32
|
@debug = options[:debug]
|
33
33
|
@@connection = SugarCRM::Connection.new(url, user, pass, options)
|
@@ -96,7 +96,9 @@ module SugarCRM; class Base
|
|
96
96
|
|
97
97
|
def find_initial(options)
|
98
98
|
options.update(:limit => 1)
|
99
|
-
|
99
|
+
result = find_by_sql(options)
|
100
|
+
return result.first if result.instance_of? Array # find_by_sql will return an Array if result are found
|
101
|
+
result
|
100
102
|
end
|
101
103
|
|
102
104
|
def find_from_ids(ids, options)
|
@@ -152,8 +154,56 @@ module SugarCRM; class Base
|
|
152
154
|
end
|
153
155
|
|
154
156
|
def find_by_sql(options)
|
155
|
-
|
156
|
-
|
157
|
+
# SugarCRM REST API has a bug where, when :limit and :offset options are passed simultaneously, :limit is considered to be the smallest of the two, and :offset is the larger
|
158
|
+
# in addition to allowing querying of large datasets while avoiding timeouts,
|
159
|
+
# this implementation fixes the :limit - :offset bug so that it behaves correctly
|
160
|
+
local_options = {}
|
161
|
+
options.keys.each{|k|
|
162
|
+
local_options[k] = options[k]
|
163
|
+
}
|
164
|
+
local_options.delete(:offset) if local_options[:offset] == 0
|
165
|
+
|
166
|
+
# store the number of records wanted by user, as we'll overwrite :limit option to obtain several slices of records (to avoid timeout issues)
|
167
|
+
nb_to_fetch = local_options[:limit]
|
168
|
+
nb_to_fetch = nb_to_fetch.to_i if nb_to_fetch
|
169
|
+
offset_value = local_options[:offset] || 10 # arbitrary value, must be bigger than :limit used (see comment above)
|
170
|
+
offset_value = offset_value.to_i
|
171
|
+
offset_value.freeze
|
172
|
+
initial_limit = nb_to_fetch.nil? ? offset_value : [offset_value, nb_to_fetch].min # how many records should be fetched on first pass
|
173
|
+
# ensure results are ordered so :limit and :offset option behave in a deterministic fashion
|
174
|
+
local_options = { :order_by => :id }.merge(local_options)
|
175
|
+
local_options.update(:limit => initial_limit) # override original argument
|
176
|
+
|
177
|
+
# get first slice of results
|
178
|
+
# note: to work around a SugarCRM REST API bug, the :limit option must always be smaller than the :offset option
|
179
|
+
# this is the reason this first query is separate (not in the loop): the initial query has a larger limit, so that we can then use the loop
|
180
|
+
# with :limit always smaller than :offset
|
181
|
+
results = SugarCRM.connection.get_entry_list(self._module.name, query_from_options(local_options), local_options)
|
182
|
+
return nil unless results
|
183
|
+
results = Array.wrap(results)
|
184
|
+
|
185
|
+
limit_value = [5, offset_value].min # arbitrary value, must be smaller than :offset used (see comment above)
|
186
|
+
limit_value.freeze
|
187
|
+
local_options = { :order_by => :id }.merge(local_options)
|
188
|
+
local_options.update(:limit => limit_value)
|
189
|
+
|
190
|
+
# a portion of the results has already been queried
|
191
|
+
# update or set the :offset value to reflect this
|
192
|
+
local_options[:offset] ||= results.size
|
193
|
+
local_options[:offset] += offset_value
|
194
|
+
|
195
|
+
# continue fetching results until we either
|
196
|
+
# a) have as many results as the user wants (specified via the original :limit option)
|
197
|
+
# b) there are no more results matching the criteria
|
198
|
+
while result_slice = SugarCRM.connection.get_entry_list(self._module.name, query_from_options(local_options), local_options)
|
199
|
+
results.concat(Array.wrap(result_slice))
|
200
|
+
# make sure we don't return more results than the user requested (via original :limit option)
|
201
|
+
if nb_to_fetch && results.size >= nb_to_fetch
|
202
|
+
return results.slice(0, nb_to_fetch)
|
203
|
+
end
|
204
|
+
local_options[:offset] += local_options[:limit] # update :offset as we get more records
|
205
|
+
end
|
206
|
+
results
|
157
207
|
end
|
158
208
|
|
159
209
|
def query_from_options(options)
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# below is an example configuration file
|
2
|
+
#
|
3
|
+
# to create you own configuration file, simply copy and adapt the text below, removing the '#' in front of the key-value pairs
|
4
|
+
#
|
5
|
+
# you'll find an example of a configuration file (used for tests) in test/config_test.yaml
|
6
|
+
#
|
7
|
+
# config:
|
8
|
+
# base_url: http://127.0.0.1/sugarcrm # where your SugarCRM instance is located
|
9
|
+
# username: admin
|
10
|
+
# password: letmein
|
@@ -14,7 +14,7 @@ module SugarCRM; class Connection
|
|
14
14
|
\"module_name\": \"#{module_name}\"\,
|
15
15
|
\"ids\": #{ids.to_json}\,
|
16
16
|
\"select_fields\": #{resolve_fields(module_name, options[:fields])}\,
|
17
|
-
\"link_name_to_fields_array\": #{options[:link_fields].to_json}
|
17
|
+
\"link_name_to_fields_array\": #{options[:link_fields].to_json}
|
18
18
|
}
|
19
19
|
EOF
|
20
20
|
json.gsub!(/^\s{6}/,'')
|
@@ -13,7 +13,7 @@ module SugarCRM; class Connection
|
|
13
13
|
\"module_name\": \"#{module_name}\"\,
|
14
14
|
\"id\": \"#{id}\"\,
|
15
15
|
\"select_fields\": #{resolve_fields(module_name, options[:fields])}\,
|
16
|
-
\"link_name_to_fields_array\": #{options[:link_fields]}
|
16
|
+
\"link_name_to_fields_array\": #{options[:link_fields]}
|
17
17
|
}
|
18
18
|
EOF
|
19
19
|
|
@@ -1,15 +1,15 @@
|
|
1
1
|
module SugarCRM; class Connection
|
2
2
|
# Logs the user into the Sugar application.
|
3
3
|
def login
|
4
|
-
connect! unless connected?
|
4
|
+
connect! unless connected?
|
5
5
|
json = <<-EOF
|
6
6
|
{
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
7
|
+
"user_auth": {
|
8
|
+
"user_name": "#{@user}",
|
9
|
+
"password": "#{OpenSSL::Digest::MD5.new(@pass)}",
|
10
|
+
"version": 2
|
11
11
|
},
|
12
|
-
|
12
|
+
"application": "sugarcrm_rubygem"
|
13
13
|
}
|
14
14
|
EOF
|
15
15
|
json.gsub!(/^\s{6}/,'')
|
@@ -18,7 +18,8 @@ module SugarCRM; class Connection
|
|
18
18
|
def initialize(url, user, pass, options={})
|
19
19
|
@options = {
|
20
20
|
:debug => false,
|
21
|
-
:register_modules => true
|
21
|
+
:register_modules => true,
|
22
|
+
:load_environment => true
|
22
23
|
}.merge(options)
|
23
24
|
@url = URI.parse(url)
|
24
25
|
@user = user
|
@@ -27,6 +28,8 @@ module SugarCRM; class Connection
|
|
27
28
|
@response = ""
|
28
29
|
resolve_url
|
29
30
|
login!
|
31
|
+
# make sure the environment singleton gets loaded
|
32
|
+
SugarCRM::Environment.instance if @options[:load_environment]
|
30
33
|
self
|
31
34
|
end
|
32
35
|
|
@@ -123,12 +126,12 @@ module SugarCRM; class Connection
|
|
123
126
|
return @response.body if RESPONSE_IS_NOT_JSON.include? @request.method
|
124
127
|
begin
|
125
128
|
# Push it through the old meat grinder.
|
126
|
-
response_json =
|
129
|
+
response_json = JSON.parse(@response.body)
|
127
130
|
rescue StandardError => e
|
128
131
|
raise UnhandledResponse, @response.body
|
129
132
|
end
|
130
133
|
# Empty result. Is this wise?
|
131
|
-
return
|
134
|
+
return nil if response_json["result_count"] == 0
|
132
135
|
# Filter debugging on REALLY BIG responses
|
133
136
|
if @options[:debug] && !(DONT_SHOW_DEBUG_FOR.include? @request.method)
|
134
137
|
puts "#{@request.method}: JSON Response:"
|
@@ -8,14 +8,14 @@ module SugarCRM; class Request
|
|
8
8
|
def initialize(url, method, json, debug=false)
|
9
9
|
@url = url
|
10
10
|
@method = method
|
11
|
-
@json = json
|
11
|
+
@json = CGI.escape(json)
|
12
12
|
@request = 'method=' << @method.to_s
|
13
13
|
@request << '&input_type=JSON'
|
14
14
|
@request << '&response_type=JSON'
|
15
15
|
@request << '&rest_data=' << @json
|
16
16
|
if debug
|
17
17
|
puts "#{method}: Request:"
|
18
|
-
puts
|
18
|
+
puts json
|
19
19
|
puts "\n"
|
20
20
|
end
|
21
21
|
self
|
@@ -26,6 +26,6 @@ module SugarCRM; class Request
|
|
26
26
|
end
|
27
27
|
|
28
28
|
def to_s
|
29
|
-
|
29
|
+
@request
|
30
30
|
end
|
31
31
|
end; end
|
@@ -17,9 +17,8 @@ module SugarCRM; class Response
|
|
17
17
|
if SugarCRM.connection.debug?
|
18
18
|
puts "Failed to process JSON:"
|
19
19
|
pp json
|
20
|
-
raise e
|
21
20
|
end
|
22
|
-
|
21
|
+
raise e
|
23
22
|
end
|
24
23
|
end
|
25
24
|
end
|
@@ -36,7 +35,7 @@ module SugarCRM; class Response
|
|
36
35
|
# populated from the response
|
37
36
|
def to_obj
|
38
37
|
# If this is not a "entry_list" response, just return
|
39
|
-
return @response unless @response["entry_list"]
|
38
|
+
return @response unless @response && @response["entry_list"]
|
40
39
|
|
41
40
|
objects = []
|
42
41
|
@response["entry_list"].each do |object|
|
@@ -0,0 +1,48 @@
|
|
1
|
+
require 'singleton'
|
2
|
+
|
3
|
+
module SugarCRM; class Environment
|
4
|
+
include Singleton
|
5
|
+
|
6
|
+
attr_reader :config
|
7
|
+
|
8
|
+
def initialize
|
9
|
+
@config = {}
|
10
|
+
|
11
|
+
# see README for reasoning behind the priorization
|
12
|
+
['/etc/sugarcrm.yaml', File.expand_path('~/.sugarcrm.yaml'), File.join(File.dirname(__FILE__), 'config', 'sugarcrm.yaml')].each{|path|
|
13
|
+
load_config path if File.exists? path
|
14
|
+
}
|
15
|
+
extensions_folder = File.join(File.dirname(__FILE__), 'extensions')
|
16
|
+
SugarCRM::Base.establish_connection(@config[:base_url], @config[:username], @config[:password], {:load_environment => false}) if SugarCRM.connection.nil? && connection_info_loaded?
|
17
|
+
end
|
18
|
+
|
19
|
+
def connection_info_loaded?
|
20
|
+
@config[:base_url] && @config[:username] && @config[:password]
|
21
|
+
end
|
22
|
+
|
23
|
+
def load_config(path)
|
24
|
+
validate_path path
|
25
|
+
config = YAML.load_file(path)
|
26
|
+
if config && config["config"]
|
27
|
+
config["config"].each{|k,v|
|
28
|
+
@config[k.to_sym] = v
|
29
|
+
}
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
# load all the monkey patch extension files in the provided folder
|
34
|
+
def extensions_folder=(folder, dirstring=nil)
|
35
|
+
validate_path folder
|
36
|
+
path = File.expand_path(folder, dirstring)
|
37
|
+
Dir[File.join(path, '**', '*.rb').to_s].each { |f| load(f) }
|
38
|
+
end
|
39
|
+
|
40
|
+
def self.method_missing(method_id, *args, &block)
|
41
|
+
self.instance.send(method_id, *args, &block)
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
def validate_path(path)
|
46
|
+
raise "Invalid path: #{path}" unless File.exists? path
|
47
|
+
end
|
48
|
+
end; end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
# Include your extension files here, as simple *.rb files. Here is an example of an extension:
|
2
|
+
|
3
|
+
SugarCRM::Contact.class_eval do
|
4
|
+
def self.ten_oldest
|
5
|
+
self.find(:order_by => 'date_entered', :limit => 10)
|
6
|
+
end
|
7
|
+
|
8
|
+
def vip?
|
9
|
+
self.opportunities.size > 100
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
# This will enable you to call
|
14
|
+
|
15
|
+
SugarCRM::Contact.ten_oldest
|
16
|
+
|
17
|
+
# to get the 10 oldest contacts entered in CRM .
|
18
|
+
#
|
19
|
+
# You will also be able to call
|
20
|
+
|
21
|
+
SugarCRM::Contact.first.vip?
|
22
|
+
|
23
|
+
# to see whether a contact is VIP or not.
|
data/lib/sugarcrm/module.rb
CHANGED
@@ -15,7 +15,11 @@ module SugarCRM
|
|
15
15
|
def initialize(name)
|
16
16
|
@name = name
|
17
17
|
@klass = name.classify
|
18
|
-
|
18
|
+
unless custom_module?
|
19
|
+
@table_name = name.tableize
|
20
|
+
else
|
21
|
+
@table_name = @name
|
22
|
+
end
|
19
23
|
@custom_table_name = resolve_custom_table_name
|
20
24
|
@fields = {}
|
21
25
|
@link_fields = {}
|
@@ -36,8 +40,11 @@ module SugarCRM
|
|
36
40
|
# For custom modules (created in the Studio), table name don't need to be tableized since
|
37
41
|
# the name passed to the constructor is already tableized
|
38
42
|
def resolve_custom_table_name
|
39
|
-
|
40
|
-
|
43
|
+
if custom_module?
|
44
|
+
@custom_table_name = @name + "_cstm"
|
45
|
+
else
|
46
|
+
@custom_table_name = @table_name + "_cstm"
|
47
|
+
end
|
41
48
|
end
|
42
49
|
|
43
50
|
# Returns the fields associated with the module
|
@@ -7,7 +7,7 @@ module SugarCRM
|
|
7
7
|
def self.connection=(connection)
|
8
8
|
@@connection = connection
|
9
9
|
end
|
10
|
-
def self.connect(url, user, pass, options={})
|
10
|
+
def self.connect(url=SugarCRM::Environment.config[:base_url], user=SugarCRM::Environment.config[:username], pass=SugarCRM::Environment.config[:password], options={})
|
11
11
|
SugarCRM::Base.establish_connection(url, user, pass, options)
|
12
12
|
end
|
13
13
|
class << self
|
@@ -26,4 +26,21 @@ module SugarCRM
|
|
26
26
|
SugarCRM::User.find_by_user_name(connection.user)
|
27
27
|
end
|
28
28
|
|
29
|
+
# If a user tries to access a SugarCRM class before they're logged in,
|
30
|
+
# try to log in using credentials from config file.
|
31
|
+
# This will trigger module loading,
|
32
|
+
# and we can then attempt to return the requested class automagically
|
33
|
+
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?
|
36
|
+
super
|
37
|
+
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)
|
44
|
+
end
|
45
|
+
end
|
29
46
|
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
# below is an example configuration file
|
2
|
+
#
|
3
|
+
# to create you own configuration file, simply copy and adapt the text below, removing the '#' in front of the key-value pairs
|
4
|
+
#
|
5
|
+
# you'll find an example of a configuration file (used for tests) in test/config_test.yaml
|
6
|
+
#
|
7
|
+
# config:
|
8
|
+
# base_url: http://127.0.0.1/sugarcrm # where your SugarCRM instance is located
|
9
|
+
# username: admin
|
10
|
+
# password: letmein
|
11
|
+
|
12
|
+
config:
|
13
|
+
base_url: http://127.0.0.1/sugarcrm # where your SugarCRM instance is located
|
14
|
+
username: admin
|
15
|
+
password: letmein
|
data/test/test_association.rb
CHANGED
@@ -1 +1,18 @@
|
|
1
|
-
#TODO: Figure out how to test this.
|
1
|
+
#TODO: Figure out how to test this.
|
2
|
+
|
3
|
+
|
4
|
+
# That code works, but what didn't work:
|
5
|
+
# document.tasks.size
|
6
|
+
# document.associate!(task)
|
7
|
+
# document.tasks.size
|
8
|
+
# This seems to be fixed in HEAD in my repo. I haven't pushed the changes to your repo, because I don't really see why commit https://github.com/davidsulc/sugarcrm/commit/228375348c9113324370afa0aca4120eb117d3e1 fixes the issue...
|
9
|
+
# Also, I fixed this case
|
10
|
+
# document.tasks.size
|
11
|
+
# document.associate!(task)
|
12
|
+
# document.tasks.size # => 1 (correct)
|
13
|
+
# document.associate!(task)
|
14
|
+
# document.tasks.size # => 2 (incorrect: should remain 1)
|
15
|
+
# One thing we should look into is
|
16
|
+
# document.associate!(task)
|
17
|
+
# document.tasks.size # => 1
|
18
|
+
# task.documents.size # => 0 (should be 1)
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'helper'
|
2
|
+
|
3
|
+
class TestEnvironment < Test::Unit::TestCase
|
4
|
+
context "A SugarCRM::Environment singleton" do
|
5
|
+
|
6
|
+
should "delegate missing methods to singleton instance" do
|
7
|
+
assert_equal SugarCRM::Environment.instance.config, SugarCRM::Environment.config
|
8
|
+
end
|
9
|
+
|
10
|
+
should "load monkey patch extensions" do
|
11
|
+
SugarCRM::Environment.extensions_folder = File.join(File.dirname(__FILE__), 'extensions_test')
|
12
|
+
assert SugarCRM::Contact.is_extended?
|
13
|
+
assert SugarCRM::Contact.is_extended?
|
14
|
+
end
|
15
|
+
|
16
|
+
should "load config file" do
|
17
|
+
SugarCRM::Environment.load_config File.join(File.dirname(__FILE__), 'config_test.yaml')
|
18
|
+
|
19
|
+
config_contents = {
|
20
|
+
:config => {
|
21
|
+
:base_url => 'http://127.0.0.1/sugarcrm',
|
22
|
+
:username => 'admin',
|
23
|
+
:password => 'letmein'
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
config_contents[:config].each{|k,v|
|
28
|
+
assert_equal v, SugarCRM::Environment.config[k]
|
29
|
+
}
|
30
|
+
end
|
31
|
+
|
32
|
+
should "log in to Sugar automatically if credentials are present in config file" do
|
33
|
+
SugarCRM::Environment.load_config File.join(File.dirname(__FILE__), 'config_test.yaml')
|
34
|
+
assert SugarCRM.connection.logged_in?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
data/test/test_sugarcrm.rb
CHANGED
@@ -52,28 +52,26 @@ class TestSugarCRM < Test::Unit::TestCase
|
|
52
52
|
end
|
53
53
|
|
54
54
|
should "not save a record that is missing required attributes" do
|
55
|
-
SugarCRM.connection.debug = false
|
56
55
|
u = SugarCRM::User.new
|
57
56
|
u.last_name = "Test"
|
58
57
|
assert !u.save
|
59
|
-
SugarCRM.connection.debug = false
|
60
58
|
assert_raise SugarCRM::InvalidRecord do
|
61
59
|
u.save!
|
62
60
|
end
|
63
61
|
end
|
64
62
|
|
65
63
|
should "always return an Array when :all" do
|
66
|
-
users = SugarCRM::User.all
|
64
|
+
users = SugarCRM::User.all(:limit => 10)
|
67
65
|
assert_instance_of Array, users
|
68
66
|
users = SugarCRM::User.find(:all, :conditions => {:user_name => '= admin'})
|
69
67
|
assert_instance_of Array, users
|
68
|
+
assert users.length == 1
|
70
69
|
users = SugarCRM::User.find(:all, :conditions => {:user_name => '= invalid_user_123'})
|
71
70
|
assert_instance_of Array, users
|
72
71
|
assert users.length == 0
|
73
72
|
end
|
74
73
|
|
75
74
|
should "create, modify, and delete a record" do
|
76
|
-
#SugarCRM.connection.debug = true
|
77
75
|
u = SugarCRM::User.new
|
78
76
|
u.email1 = "abc@abc.com"
|
79
77
|
u.first_name = "Test"
|
@@ -85,10 +83,10 @@ class TestSugarCRM < Test::Unit::TestCase
|
|
85
83
|
assert u.save!
|
86
84
|
assert !u.new?
|
87
85
|
m = SugarCRM::User.find_by_first_name_and_last_name("Test", "User")
|
86
|
+
assert m.user_name != "admin"
|
88
87
|
m.title = "Test User"
|
89
88
|
assert m.save!
|
90
89
|
assert m.delete
|
91
|
-
#SugarCRM.connection.debug = false
|
92
90
|
end
|
93
91
|
|
94
92
|
should "support finding first instance (sorted by attribute)" do
|
@@ -126,7 +124,48 @@ class TestSugarCRM < Test::Unit::TestCase
|
|
126
124
|
|
127
125
|
should "return an array of records when sent #find([id1, id2, id3])" do
|
128
126
|
users = SugarCRM::User.find(["seed_sarah_id", 1])
|
129
|
-
assert_equal "
|
127
|
+
assert_equal "admin", users.last.user_name
|
128
|
+
end
|
129
|
+
|
130
|
+
# test Base#find_by_sql edge case
|
131
|
+
should "return an array of records with small limit and an offset of 0" do
|
132
|
+
accounts = SugarCRM::Account.all(:limit => 3, :offset => 0)
|
133
|
+
assert_equal 3, accounts.size
|
134
|
+
end
|
135
|
+
|
136
|
+
# test Base#find_by_sql standard case
|
137
|
+
should "return an array of records with high limit" do
|
138
|
+
accounts = SugarCRM::Account.all(:limit => 12)
|
139
|
+
assert_equal 12, accounts.size
|
140
|
+
end
|
141
|
+
|
142
|
+
should "return an array of records when using :order_by, :limit, and :offset options" do
|
143
|
+
accounts = SugarCRM::Account.all(:order_by => 'name', :limit => 3, :offset => 10)
|
144
|
+
accounts_api = SugarCRM.connection.get_entry_list('Accounts', '1=1', :order_by => 'name', :limit => 3, :offset => 10)
|
145
|
+
assert_equal accounts_api, accounts
|
146
|
+
end
|
147
|
+
|
148
|
+
should "return an array of records working around a SugarCRM bug when :limit > :offset" do
|
149
|
+
accounts = SugarCRM::Account.all(:order_by => 'name', :limit => 10, :offset => 2)
|
150
|
+
assert_equal 10, accounts.size
|
151
|
+
end
|
152
|
+
|
153
|
+
should "return an array of 1 record with :limit => 1, :offset => 1" do
|
154
|
+
accounts = SugarCRM::Account.all(:order_by => 'name', :limit => 1, :offset => 1)
|
155
|
+
assert_equal 1, accounts.size
|
156
|
+
end
|
157
|
+
|
158
|
+
should "ignore :offset => 0" do
|
159
|
+
accounts = SugarCRM::Account.all(:order_by => 'name', :limit => 3)
|
160
|
+
accounts_offset = SugarCRM::Account.all(:order_by => 'name', :limit => 3, :offset => 0)
|
161
|
+
assert_equal accounts, accounts_offset
|
162
|
+
end
|
163
|
+
|
164
|
+
should "compute offsets correctly" do
|
165
|
+
accounts = SugarCRM::Account.all(:order_by => 'name', :limit => 10, :offset => 3)
|
166
|
+
accounts_first_slice = SugarCRM::Account.all(:order_by => 'name', :limit => 5, :offset => 3)
|
167
|
+
accounts_second_slice = SugarCRM::Account.all(:order_by => 'name', :limit => 5, :offset => 8)
|
168
|
+
assert_equal accounts, accounts_first_slice.concat(accounts_second_slice)
|
130
169
|
end
|
131
170
|
|
132
171
|
should "return an instance of User when sent User#find_by_username" do
|
@@ -143,12 +182,64 @@ class TestSugarCRM < Test::Unit::TestCase
|
|
143
182
|
assert a.delete
|
144
183
|
end
|
145
184
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
185
|
+
should "update association cache on associate! only if association changes" do
|
186
|
+
a = SugarCRM::Account.first
|
187
|
+
c = SugarCRM::Contact.create(:last_name => 'Doe')
|
188
|
+
|
189
|
+
nb_contacts = a.contacts.size
|
190
|
+
a.associate!(c)
|
191
|
+
assert_equal nb_contacts + 1, a.contacts.size
|
192
|
+
a.associate!(c)
|
193
|
+
assert_equal nb_contacts + 1, a.contacts.size # should not change: already associated
|
194
|
+
|
195
|
+
c.delete
|
196
|
+
end
|
197
|
+
|
198
|
+
should "update association cache on << only if association changes" do
|
199
|
+
a = SugarCRM::Account.first
|
200
|
+
c = SugarCRM::Contact.create(:last_name => 'Doe')
|
201
|
+
|
202
|
+
nb_contacts = a.contacts.size
|
203
|
+
a.contacts << c
|
204
|
+
assert_equal nb_contacts + 1, a.contacts.size
|
205
|
+
a.contacts << c
|
206
|
+
assert_equal nb_contacts + 1, a.contacts.size # should not change: already associated
|
207
|
+
|
208
|
+
c.delete
|
209
|
+
end
|
210
|
+
|
211
|
+
should "update association cache for both sides of the relationship when calling associate!" do
|
212
|
+
a = SugarCRM::Account.first
|
213
|
+
c = SugarCRM::Contact.create(:last_name => 'Doe')
|
214
|
+
|
215
|
+
nb_contacts = a.contacts.size
|
216
|
+
nb_accounts = c.accounts.size
|
217
|
+
a.associate!(c)
|
218
|
+
assert_equal nb_contacts + 1, a.contacts.size
|
219
|
+
assert_equal nb_accounts + 1, c.accounts.size
|
220
|
+
|
221
|
+
c.delete
|
222
|
+
end
|
223
|
+
|
224
|
+
should "update association cache for both sides of the relationship when calling <<" do
|
225
|
+
a = SugarCRM::Account.first
|
226
|
+
c = SugarCRM::Contact.create(:last_name => 'Doe')
|
227
|
+
|
228
|
+
nb_contacts = a.contacts.size
|
229
|
+
nb_accounts = c.accounts.size
|
230
|
+
a.contacts << c
|
231
|
+
assert_equal nb_contacts + 1, a.contacts.size
|
232
|
+
assert_equal nb_accounts + 1, c.accounts.size
|
233
|
+
|
234
|
+
c.delete
|
235
|
+
end
|
236
|
+
|
237
|
+
should "support saving of records with special characters in them" do
|
238
|
+
a = SugarCRM::Account.new
|
239
|
+
a.name = "COHEN, WEISS & SIMON LLP"
|
240
|
+
assert a.save!
|
241
|
+
assert a.delete
|
242
|
+
end
|
152
243
|
end
|
153
244
|
|
154
245
|
end
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sugarcrm
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
+
hash: 41
|
4
5
|
prerelease: false
|
5
6
|
segments:
|
6
7
|
- 0
|
7
8
|
- 9
|
8
|
-
-
|
9
|
-
version: 0.9.
|
9
|
+
- 9
|
10
|
+
version: 0.9.9
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Carl Hicks
|
@@ -14,93 +15,99 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2011-01-
|
18
|
+
date: 2011-01-30 00:00:00 -08:00
|
18
19
|
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
21
24
|
name: activesupport
|
22
|
-
|
25
|
+
version_requirements: &id001 !ruby/object:Gem::Requirement
|
23
26
|
none: false
|
24
27
|
requirements:
|
25
28
|
- - ">="
|
26
29
|
- !ruby/object:Gem::Version
|
30
|
+
hash: 7
|
27
31
|
segments:
|
28
32
|
- 3
|
29
33
|
- 0
|
30
34
|
- 0
|
31
35
|
version: 3.0.0
|
36
|
+
requirement: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
32
38
|
type: :runtime
|
33
39
|
prerelease: false
|
34
|
-
version_requirements: *id001
|
35
|
-
- !ruby/object:Gem::Dependency
|
36
40
|
name: i18n
|
37
|
-
|
41
|
+
version_requirements: &id002 !ruby/object:Gem::Requirement
|
38
42
|
none: false
|
39
43
|
requirements:
|
40
44
|
- - ">="
|
41
45
|
- !ruby/object:Gem::Version
|
46
|
+
hash: 3
|
42
47
|
segments:
|
43
48
|
- 0
|
44
49
|
version: "0"
|
50
|
+
requirement: *id002
|
51
|
+
- !ruby/object:Gem::Dependency
|
45
52
|
type: :runtime
|
46
53
|
prerelease: false
|
47
|
-
|
54
|
+
name: json
|
55
|
+
version_requirements: &id003 !ruby/object:Gem::Requirement
|
56
|
+
none: false
|
57
|
+
requirements:
|
58
|
+
- - ">="
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
hash: 3
|
61
|
+
segments:
|
62
|
+
- 0
|
63
|
+
version: "0"
|
64
|
+
requirement: *id003
|
48
65
|
- !ruby/object:Gem::Dependency
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
49
68
|
name: shoulda
|
50
|
-
|
69
|
+
version_requirements: &id004 !ruby/object:Gem::Requirement
|
51
70
|
none: false
|
52
71
|
requirements:
|
53
72
|
- - ">="
|
54
73
|
- !ruby/object:Gem::Version
|
74
|
+
hash: 3
|
55
75
|
segments:
|
56
76
|
- 0
|
57
77
|
version: "0"
|
78
|
+
requirement: *id004
|
79
|
+
- !ruby/object:Gem::Dependency
|
58
80
|
type: :development
|
59
81
|
prerelease: false
|
60
|
-
version_requirements: *id003
|
61
|
-
- !ruby/object:Gem::Dependency
|
62
82
|
name: bundler
|
63
|
-
|
83
|
+
version_requirements: &id005 !ruby/object:Gem::Requirement
|
64
84
|
none: false
|
65
85
|
requirements:
|
66
86
|
- - ~>
|
67
87
|
- !ruby/object:Gem::Version
|
88
|
+
hash: 23
|
68
89
|
segments:
|
69
90
|
- 1
|
70
91
|
- 0
|
71
92
|
- 0
|
72
93
|
version: 1.0.0
|
94
|
+
requirement: *id005
|
95
|
+
- !ruby/object:Gem::Dependency
|
73
96
|
type: :development
|
74
97
|
prerelease: false
|
75
|
-
version_requirements: *id004
|
76
|
-
- !ruby/object:Gem::Dependency
|
77
98
|
name: jeweler
|
78
|
-
|
99
|
+
version_requirements: &id006 !ruby/object:Gem::Requirement
|
79
100
|
none: false
|
80
101
|
requirements:
|
81
102
|
- - ~>
|
82
103
|
- !ruby/object:Gem::Version
|
104
|
+
hash: 7
|
83
105
|
segments:
|
84
106
|
- 1
|
85
107
|
- 5
|
86
108
|
- 2
|
87
109
|
version: 1.5.2
|
88
|
-
|
89
|
-
prerelease: false
|
90
|
-
version_requirements: *id005
|
91
|
-
- !ruby/object:Gem::Dependency
|
92
|
-
name: rcov
|
93
|
-
requirement: &id006 !ruby/object:Gem::Requirement
|
94
|
-
none: false
|
95
|
-
requirements:
|
96
|
-
- - ">="
|
97
|
-
- !ruby/object:Gem::Version
|
98
|
-
segments:
|
99
|
-
- 0
|
100
|
-
version: "0"
|
101
|
-
type: :development
|
102
|
-
prerelease: false
|
103
|
-
version_requirements: *id006
|
110
|
+
requirement: *id006
|
104
111
|
description: A less clunky way to interact with SugarCRM via REST. Instead of SugarCRM.connection.get_entry("Users", "1") you could use SugarCRM::User.find(1). There is support for collections a la SugarCRM::User.find(1).email_addresses, or SugarCRM::Contact.first.meetings << new_meeting. ActiveRecord style finders are in place, with limited support for conditions and joins.
|
105
112
|
email: carl.hicks@gmail.com
|
106
113
|
executables: []
|
@@ -130,6 +137,7 @@ files:
|
|
130
137
|
- lib/sugarcrm/attributes/attribute_typecast.rb
|
131
138
|
- lib/sugarcrm/attributes/attribute_validations.rb
|
132
139
|
- lib/sugarcrm/base.rb
|
140
|
+
- lib/sugarcrm/config/sugarcrm.yaml
|
133
141
|
- lib/sugarcrm/connection.rb
|
134
142
|
- lib/sugarcrm/connection/api/get_available_modules.rb
|
135
143
|
- lib/sugarcrm/connection/api/get_document_revision.rb
|
@@ -160,9 +168,12 @@ files:
|
|
160
168
|
- lib/sugarcrm/connection/request.rb
|
161
169
|
- lib/sugarcrm/connection/response.rb
|
162
170
|
- lib/sugarcrm/dynamic_finder_match.rb
|
171
|
+
- lib/sugarcrm/environment.rb
|
163
172
|
- lib/sugarcrm/exceptions.rb
|
173
|
+
- lib/sugarcrm/extensions/README.txt
|
164
174
|
- lib/sugarcrm/module.rb
|
165
175
|
- lib/sugarcrm/module_methods.rb
|
176
|
+
- test/config_test.yaml
|
166
177
|
- test/connection/test_get_available_modules.rb
|
167
178
|
- test/connection/test_get_entries.rb
|
168
179
|
- test/connection/test_get_entry.rb
|
@@ -175,11 +186,13 @@ files:
|
|
175
186
|
- test/connection/test_login.rb
|
176
187
|
- test/connection/test_logout.rb
|
177
188
|
- test/connection/test_set_relationship.rb
|
189
|
+
- test/extensions_test/patch.rb
|
178
190
|
- test/helper.rb
|
179
191
|
- test/test_association.rb
|
180
192
|
- test/test_association_collection.rb
|
181
193
|
- test/test_associations.rb
|
182
194
|
- test/test_connection.rb
|
195
|
+
- test/test_environment.rb
|
183
196
|
- test/test_module.rb
|
184
197
|
- test/test_response.rb
|
185
198
|
- test/test_sugarcrm.rb
|
@@ -197,7 +210,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
197
210
|
requirements:
|
198
211
|
- - ">="
|
199
212
|
- !ruby/object:Gem::Version
|
200
|
-
hash:
|
213
|
+
hash: 3
|
201
214
|
segments:
|
202
215
|
- 0
|
203
216
|
version: "0"
|
@@ -206,6 +219,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
206
219
|
requirements:
|
207
220
|
- - ">="
|
208
221
|
- !ruby/object:Gem::Version
|
222
|
+
hash: 3
|
209
223
|
segments:
|
210
224
|
- 0
|
211
225
|
version: "0"
|
@@ -229,11 +243,13 @@ test_files:
|
|
229
243
|
- test/connection/test_login.rb
|
230
244
|
- test/connection/test_logout.rb
|
231
245
|
- test/connection/test_set_relationship.rb
|
246
|
+
- test/extensions_test/patch.rb
|
232
247
|
- test/helper.rb
|
233
248
|
- test/test_association.rb
|
234
249
|
- test/test_association_collection.rb
|
235
250
|
- test/test_associations.rb
|
236
251
|
- test/test_connection.rb
|
252
|
+
- test/test_environment.rb
|
237
253
|
- test/test_module.rb
|
238
254
|
- test/test_response.rb
|
239
255
|
- test/test_sugarcrm.rb
|