sugarcrm_emp 0.10.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/.gitignore +29 -0
- data/Gemfile +14 -0
- data/LICENSE +20 -0
- data/README.rdoc +275 -0
- data/Rakefile +44 -0
- data/VERSION +1 -0
- data/WATCHLIST.rdoc +7 -0
- data/bin/sugarcrm +26 -0
- data/lib/rails/generators/sugarcrm/config/config_generator.rb +22 -0
- data/lib/rails/generators/sugarcrm/config/templates/initializer.rb +4 -0
- data/lib/rails/generators/sugarcrm/config/templates/sugarcrm.yml +19 -0
- data/lib/sugarcrm/associations/association.rb +170 -0
- data/lib/sugarcrm/associations/association_cache.rb +36 -0
- data/lib/sugarcrm/associations/association_collection.rb +141 -0
- data/lib/sugarcrm/associations/association_methods.rb +91 -0
- data/lib/sugarcrm/associations/associations.rb +61 -0
- data/lib/sugarcrm/associations.rb +5 -0
- data/lib/sugarcrm/attributes/attribute_methods.rb +203 -0
- data/lib/sugarcrm/attributes/attribute_serializers.rb +55 -0
- data/lib/sugarcrm/attributes/attribute_typecast.rb +44 -0
- data/lib/sugarcrm/attributes/attribute_validations.rb +62 -0
- data/lib/sugarcrm/attributes.rb +4 -0
- data/lib/sugarcrm/base.rb +355 -0
- data/lib/sugarcrm/config/sugarcrm.yaml +10 -0
- data/lib/sugarcrm/connection/api/get_available_modules.rb +22 -0
- data/lib/sugarcrm/connection/api/get_document_revision.rb +14 -0
- data/lib/sugarcrm/connection/api/get_entries.rb +23 -0
- data/lib/sugarcrm/connection/api/get_entries_count.rb +20 -0
- data/lib/sugarcrm/connection/api/get_entry.rb +23 -0
- data/lib/sugarcrm/connection/api/get_entry_list.rb +31 -0
- data/lib/sugarcrm/connection/api/get_module_fields.rb +15 -0
- data/lib/sugarcrm/connection/api/get_note_attachment.rb +14 -0
- data/lib/sugarcrm/connection/api/get_relationships.rb +30 -0
- data/lib/sugarcrm/connection/api/get_report_entries.rb +17 -0
- data/lib/sugarcrm/connection/api/get_server_info.rb +7 -0
- data/lib/sugarcrm/connection/api/get_user_id.rb +13 -0
- data/lib/sugarcrm/connection/api/get_user_team_id.rb +14 -0
- data/lib/sugarcrm/connection/api/login.rb +18 -0
- data/lib/sugarcrm/connection/api/logout.rb +15 -0
- data/lib/sugarcrm/connection/api/seamless_login.rb +13 -0
- data/lib/sugarcrm/connection/api/search_by_module.rb +25 -0
- data/lib/sugarcrm/connection/api/set_campaign_merge.rb +15 -0
- data/lib/sugarcrm/connection/api/set_document_revision.rb +35 -0
- data/lib/sugarcrm/connection/api/set_entries.rb +15 -0
- data/lib/sugarcrm/connection/api/set_entry.rb +15 -0
- data/lib/sugarcrm/connection/api/set_note_attachment.rb +25 -0
- data/lib/sugarcrm/connection/api/set_relationship.rb +27 -0
- data/lib/sugarcrm/connection/api/set_relationships.rb +22 -0
- data/lib/sugarcrm/connection/connection.rb +201 -0
- data/lib/sugarcrm/connection/helper.rb +50 -0
- data/lib/sugarcrm/connection/request.rb +61 -0
- data/lib/sugarcrm/connection/response.rb +91 -0
- data/lib/sugarcrm/connection.rb +5 -0
- data/lib/sugarcrm/connection_pool.rb +163 -0
- data/lib/sugarcrm/exceptions.rb +23 -0
- data/lib/sugarcrm/extensions/README.txt +23 -0
- data/lib/sugarcrm/finders/dynamic_finder_match.rb +41 -0
- data/lib/sugarcrm/finders/finder_methods.rb +243 -0
- data/lib/sugarcrm/finders.rb +2 -0
- data/lib/sugarcrm/module.rb +174 -0
- data/lib/sugarcrm/module_methods.rb +91 -0
- data/lib/sugarcrm/session.rb +218 -0
- data/lib/sugarcrm.rb +22 -0
- data/sugarcrm.gemspec +178 -0
- data/test/config_test.yaml +15 -0
- data/test/connection/test_get_available_modules.rb +9 -0
- data/test/connection/test_get_entries.rb +15 -0
- data/test/connection/test_get_entry.rb +22 -0
- data/test/connection/test_get_entry_list.rb +23 -0
- data/test/connection/test_get_module_fields.rb +11 -0
- data/test/connection/test_get_relationships.rb +12 -0
- data/test/connection/test_get_server_info.rb +9 -0
- data/test/connection/test_get_user_id.rb +9 -0
- data/test/connection/test_get_user_team_id.rb +9 -0
- data/test/connection/test_login.rb +9 -0
- data/test/connection/test_logout.rb +9 -0
- data/test/connection/test_set_document_revision.rb +28 -0
- data/test/connection/test_set_entry.rb +15 -0
- data/test/connection/test_set_note_attachment.rb +16 -0
- data/test/connection/test_set_relationship.rb +18 -0
- data/test/extensions_test/patch.rb +9 -0
- data/test/helper.rb +17 -0
- data/test/test_association_collection.rb +11 -0
- data/test/test_associations.rb +156 -0
- data/test/test_connection.rb +13 -0
- data/test/test_connection_pool.rb +40 -0
- data/test/test_finders.rb +201 -0
- data/test/test_module.rb +51 -0
- data/test/test_request.rb +35 -0
- data/test/test_response.rb +26 -0
- data/test/test_session.rb +136 -0
- data/test/test_sugarcrm.rb +213 -0
- metadata +266 -0
data/.document
ADDED
data/.gitignore
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
## MAC OS
|
2
|
+
.DS_Store
|
3
|
+
|
4
|
+
## TEXTMATE
|
5
|
+
*.tmproj
|
6
|
+
tmtags
|
7
|
+
|
8
|
+
## EMACS
|
9
|
+
*~
|
10
|
+
\#*
|
11
|
+
.\#*
|
12
|
+
|
13
|
+
## VIM
|
14
|
+
*.swp
|
15
|
+
|
16
|
+
## PROJECT::GENERAL
|
17
|
+
coverage
|
18
|
+
rdoc
|
19
|
+
pkg
|
20
|
+
Gemfile.lock
|
21
|
+
|
22
|
+
## PROJECT::SPECIFIC
|
23
|
+
|
24
|
+
.bundle
|
25
|
+
|
26
|
+
lib/sugarcrm/monkey_patches/*.rb
|
27
|
+
|
28
|
+
# ignore test config file so each contributor can have his own
|
29
|
+
test/config.yaml
|
data/Gemfile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
source "http://rubygems.org"
|
2
|
+
|
3
|
+
gem "activesupport", ">= 2.3.10", :require => "active_support"
|
4
|
+
gem "i18n"
|
5
|
+
gem "json"
|
6
|
+
gem "httpclient"
|
7
|
+
|
8
|
+
# Add dependencies to develop your gem here.
|
9
|
+
# Include everything needed to run rake, tests, features, etc.
|
10
|
+
group :development do
|
11
|
+
gem "shoulda", ">= 0"
|
12
|
+
gem "bundler", "~> 1.0.0"
|
13
|
+
gem "jeweler", "~> 1.5.2"
|
14
|
+
end
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Carl Hicks
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,275 @@
|
|
1
|
+
= SugarCRM
|
2
|
+
|
3
|
+
* http://github.com/chicks/sugarcrm
|
4
|
+
|
5
|
+
REST Bindings for SugarCRM!
|
6
|
+
|
7
|
+
== SUMMARY:
|
8
|
+
|
9
|
+
A less clunky way to interact with SugarCRM via REST.
|
10
|
+
|
11
|
+
== FEATURES/PROBLEMS:
|
12
|
+
|
13
|
+
* Works with all v2 API calls
|
14
|
+
* Supports Rails 2 and 3
|
15
|
+
* ActiveRecord style finders and objects
|
16
|
+
* Supports creation, saving, and deletion of SugarCRM specific objects
|
17
|
+
* Flexible extension framework
|
18
|
+
* Validations, typecasting, and serialization of boolean, date, and integer fields
|
19
|
+
* Query, update and delete records from collections
|
20
|
+
* Access API methods directly on the SugarCRM.connection object
|
21
|
+
|
22
|
+
== SYNOPSIS:
|
23
|
+
|
24
|
+
require 'sugarcrm'
|
25
|
+
|
26
|
+
# Establish a connection
|
27
|
+
SugarCRM.connect("http://localhost/sugarcrm", 'user', 'password')
|
28
|
+
|
29
|
+
# Enable debugging on the current connection
|
30
|
+
SugarCRM.connection.debug = true
|
31
|
+
|
32
|
+
# Reload the environment (will make the gem classes reflect changes made on SugarCRM server, such as adding fields)
|
33
|
+
SugarCRM.reload!
|
34
|
+
|
35
|
+
# Get the logged in user
|
36
|
+
SugarCRM.current_user
|
37
|
+
|
38
|
+
# Show a list of available modules
|
39
|
+
SugarCRM.modules
|
40
|
+
|
41
|
+
# Retrieve a User by user_name
|
42
|
+
users = SugarCRM::User.find_by_user_name("admin")
|
43
|
+
|
44
|
+
# Return a User instance's SugarCRM URL (also works for any other module instance)
|
45
|
+
users.url
|
46
|
+
|
47
|
+
# Update a User's title
|
48
|
+
u = SugarCRM::User.find_by_first_name_and_last_name("Will", "Westin")
|
49
|
+
u.title = "Sales Manager Central"
|
50
|
+
u.save
|
51
|
+
|
52
|
+
# Check if an object is valid (i.e. if it has the required fields to save)
|
53
|
+
u.valid?
|
54
|
+
|
55
|
+
# Access the errors collection
|
56
|
+
u.errors
|
57
|
+
|
58
|
+
# Show the fields required to save
|
59
|
+
u.required_attributes
|
60
|
+
|
61
|
+
# Delete an Account
|
62
|
+
a = SugarCRM::Account.find_by_name("JAB Funds Ltd.")
|
63
|
+
a.delete
|
64
|
+
|
65
|
+
# Retrieve all Email Addresses assigned to a particular User.
|
66
|
+
SugarCRM::User.find_by_user_name('sarah').email_addresses
|
67
|
+
|
68
|
+
# Retrieve all Email Addresses on an Account
|
69
|
+
SugarCRM::Account.find_by_name("JAB Funds Ltd.").contacts.each do |contact|
|
70
|
+
contact.email_addresses.each do |email|
|
71
|
+
puts "#{email.email_address}" unless email.opt_out
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Add a Meeting to a Contact
|
76
|
+
c = SugarCRM::Contact.first
|
77
|
+
c.meetings << SugarCRM::Meeting.new({
|
78
|
+
:name => "Product Introduction",
|
79
|
+
:date_start => DateTime.now,
|
80
|
+
:duration_hours => 1
|
81
|
+
})
|
82
|
+
c.save!
|
83
|
+
|
84
|
+
# Add a Contact to an Account
|
85
|
+
a = SugarCRM::Account.find_by_name("JAB Funds Ltd.")
|
86
|
+
c = SugarCRM::Contact.new
|
87
|
+
c.last_name = 'Doe'
|
88
|
+
a.contacts << c
|
89
|
+
a.save # or a.contacts.save
|
90
|
+
|
91
|
+
# Check if an Account has a specific Contact associated with it
|
92
|
+
c = SugarCRM::Contact.find_by_last_name("Doe")
|
93
|
+
a = SugarCRM::Account.find_by_name("JAB Funds Ltd.")
|
94
|
+
a.contacts.include?(c)
|
95
|
+
|
96
|
+
# Remove a Contact from an Account
|
97
|
+
c = SugarCRM::Contact.find_by_last_name("Doe")
|
98
|
+
a = SugarCRM::Account.find_by_name("JAB Funds Ltd.")
|
99
|
+
a.contacts.delete(c)
|
100
|
+
a.save # or a.contacts.save
|
101
|
+
|
102
|
+
# Retrieve the number of accounts in CRM with 'Inc' in their name
|
103
|
+
SugarCRM::Account.count(:conditions => {:name => "LIKE '%Inc'"})
|
104
|
+
|
105
|
+
# Look up the Case with the smallest case number
|
106
|
+
SugarCRM::Case.first({
|
107
|
+
:order_by => 'case_number'
|
108
|
+
})
|
109
|
+
|
110
|
+
# Retrieve the first 10 Accounts with a zip code between 10000 and 10500
|
111
|
+
SugarCRM::Account.all({
|
112
|
+
:conditions => { :billing_address_postalcode => ["> '10000'", "<= '10500'" ] },
|
113
|
+
:limit => '10',
|
114
|
+
:order_by => 'billing_address_postalcode'
|
115
|
+
})
|
116
|
+
|
117
|
+
# Retrieve all Accounts with a zip code
|
118
|
+
SugarCRM::Account.all({
|
119
|
+
:conditions => { :billing_address_postalcode => "<> NULL" }
|
120
|
+
})
|
121
|
+
|
122
|
+
# Retrieve all Accounts with 'Fund' in their name
|
123
|
+
SugarCRM::Account.all({
|
124
|
+
:conditions => { :name => "LIKE '%Fund%'" } # note that SQL operators must be uppercase
|
125
|
+
})
|
126
|
+
|
127
|
+
# Use block to iterate over results: print all account names
|
128
|
+
SugarCRM::Account.all{|a| puts a.name } # note: this method will be much quicker than SugarCRM::Account.all.each{|a| puts a.name }
|
129
|
+
# because records are passed to the block as they get fetched from SugarCRM (whereas `each`
|
130
|
+
# will fetch all records, and only then pass them to the block). This will make a major difference
|
131
|
+
# in resource use and execution time if you're dealing with many records.
|
132
|
+
|
133
|
+
# Create a document instance and upload a file
|
134
|
+
file = File.read(File.join(File.dirname(__FILE__),"test_excel.xls"))
|
135
|
+
doc = SugarCRM::Document.new
|
136
|
+
doc.active_date = Date.today
|
137
|
+
doc.document_name = "test_excel.xls"
|
138
|
+
doc.filename = "test_excel.xls"
|
139
|
+
doc.revision = 0
|
140
|
+
doc.uploadfile = SugarCRM.connection.b64_encode(file)
|
141
|
+
doc.save!
|
142
|
+
|
143
|
+
# Look up the fields for a given module
|
144
|
+
SugarCRM::Module.find("Accounts").fields
|
145
|
+
|
146
|
+
# Look up the relationships for a given module
|
147
|
+
SugarCRM::Module.find("Accounts").link_fields
|
148
|
+
|
149
|
+
# Use the HTTP Connection and SugarCRM API to load the Admin user
|
150
|
+
SugarCRM.connection.get_entry("Users", 1)
|
151
|
+
|
152
|
+
# Retrieve all Accounts by user name (direct API method)
|
153
|
+
SugarCRM.connection.get_entry_list(
|
154
|
+
"Users",
|
155
|
+
"users.user_name = \'sarah\'",
|
156
|
+
{
|
157
|
+
:link_fields => [
|
158
|
+
{
|
159
|
+
"name" => "accounts",
|
160
|
+
"value" => ["id", "name"]
|
161
|
+
}
|
162
|
+
]
|
163
|
+
}
|
164
|
+
)
|
165
|
+
|
166
|
+
== USING THE GEM WITH RAILS 3
|
167
|
+
|
168
|
+
Note: this gem works with Active Support >= 2.3.10, but is optimized for Rails 3.
|
169
|
+
|
170
|
+
1. Add the sugarcrm gem to your Gemfile (sugarcrm gem version >= 0.9.12)
|
171
|
+
2. Run `bundle install`
|
172
|
+
4. Run `rails g sugarcrm:config`
|
173
|
+
5. Edit the configuration file in `config/sugarcrm.yml` to match your environment
|
174
|
+
|
175
|
+
Example apps:
|
176
|
+
* https://github.com/davidsulc/sugar_on_rails_basic
|
177
|
+
* https://github.com/davidsulc/dev_zone_basic_rails_app
|
178
|
+
|
179
|
+
== USING A CONFIGURATION FILE
|
180
|
+
|
181
|
+
If you want to use a configuration file instead of always specifying the url, username, and password to connect to SugarCRM, you can add your credentials to one (or more) of
|
182
|
+
|
183
|
+
* `/etc/sugarcrm.yaml`
|
184
|
+
* `~/.sugarcrm.yaml` (i.e. your home directory on Linux and Mac OSX)
|
185
|
+
* a `sugarcrm.yaml` file at the root of you Windows home directory (execute `ENV['USERPROFILE']` in Ruby to see which directory should contain the file)
|
186
|
+
* `config/sugarcrm.yaml` (will need to be copied each time you upgrade or reinstall the gem)
|
187
|
+
* a YAML file and call `SugarCRM.load_config` followed by the absolute path to your configuration file
|
188
|
+
|
189
|
+
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`.
|
190
|
+
|
191
|
+
Your configuration should be in YAML format:
|
192
|
+
|
193
|
+
config:
|
194
|
+
base_url: http://127.0.0.1/sugarcrm
|
195
|
+
username: admin
|
196
|
+
password: letmein
|
197
|
+
|
198
|
+
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`
|
199
|
+
|
200
|
+
== USING THE GEM IN A CONSOLE
|
201
|
+
|
202
|
+
1. Type `irb` in your command prompt
|
203
|
+
|
204
|
+
2. Require the gem with `require 'sugarcrm'` (Note: Windows users might need to `require 'rubygems'` before `require 'sugarcrm'`.)
|
205
|
+
|
206
|
+
3. * if your login credentials are stored in the `config/sugarcrm.yaml` file, you have been automagically logged in already ;
|
207
|
+
* if your login credentials are stored in a different config file, just call `SugarCRM.load_config` followed by the absolute path to your config file. This will log you in automatically ;
|
208
|
+
* 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)
|
209
|
+
|
210
|
+
4. You now have full access to the gem's functionality, e.g. `puts SugarCRM::Account.first.name`
|
211
|
+
|
212
|
+
5. If you make changes on the SugarCRM server (e.g. adding a field to a module), you can call `SugarCRM.reload!` to rebuild the gem's modules and gain access to the new fields
|
213
|
+
|
214
|
+
== EXTENDING THE GEM
|
215
|
+
|
216
|
+
If you want to extend the gem's capabilities (e.g. to add methods specific to your environment), you can either
|
217
|
+
|
218
|
+
* drop your `*.rb` files in `lib/sugarcrm/extensions/` (see the README in that folder)
|
219
|
+
|
220
|
+
* drop your `*.rb` files in any other folder and call `SugarCRM.extensions_folder = ` followed by the absolute path to the folder containing your extensions
|
221
|
+
|
222
|
+
== WORKING WITH SIMULTANEOUS SESSIONS
|
223
|
+
|
224
|
+
This gem allows you to work with several SugarCRM session simultaneously: on each `SugarCRM.connect` call, a namespace is returned. Make sure you do NOT store this namespace in a reserved name (such as SugarCRM). This namespace can then be used just like you would use the `SugarCRM` module. For example:
|
225
|
+
|
226
|
+
ServerOne = SugarCRM.connect(URL1,...)
|
227
|
+
ServerOne::User.first
|
228
|
+
ServerTwo = SugarCRM.connect(URL2,...)
|
229
|
+
ServerTwo::User.first
|
230
|
+
|
231
|
+
If you have only one active session, calls to SugarCRM are delegated to the active session's namespace, like so
|
232
|
+
|
233
|
+
ServerOne = SugarCRM.connect(...)
|
234
|
+
ServerOne::User.first # this call does
|
235
|
+
SugarCRM::User.first # the exact same thing as this one
|
236
|
+
|
237
|
+
To replace your session to connect with different credentials, use
|
238
|
+
|
239
|
+
ServerOne.reconnect(...)
|
240
|
+
|
241
|
+
Then your session will be reused (SugarCRM modules will be reloaded).
|
242
|
+
|
243
|
+
To disconnect an active session:
|
244
|
+
|
245
|
+
ServerOne.disconnect!
|
246
|
+
|
247
|
+
== REQUIREMENTS:
|
248
|
+
|
249
|
+
* activesupport >= 2.3.10
|
250
|
+
* i18n
|
251
|
+
* json
|
252
|
+
|
253
|
+
== INSTALL:
|
254
|
+
|
255
|
+
* sudo gem install sugarcrm
|
256
|
+
|
257
|
+
Note: Windows users might need to `require 'rubygems'` before `require 'sugarcrm'`.
|
258
|
+
|
259
|
+
== TEST:
|
260
|
+
|
261
|
+
Put your credentials in a file called `test/config.yaml` (which you will have to create). These must point to a SugarCRM test instance with demo data. See an example file in `test/config_test.yaml` (leave that file as is).
|
262
|
+
|
263
|
+
== Note on Patches/Pull Requests
|
264
|
+
|
265
|
+
* Fork the project.
|
266
|
+
* Make your feature addition or bug fix.
|
267
|
+
* Add tests for it. This is important so I don't break it in a
|
268
|
+
future version unintentionally.
|
269
|
+
* Commit, do not mess with rakefile, version, or history.
|
270
|
+
(if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
|
271
|
+
* Send me a pull request. Bonus points for topic branches.
|
272
|
+
|
273
|
+
== Copyright
|
274
|
+
|
275
|
+
Copyright (c) 2011 Carl Hicks. See LICENSE for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "sugarcrm"
|
16
|
+
gem.summary = %Q{A less clunky way to interact with SugarCRM via REST.}
|
17
|
+
gem.email = "carl.hicks@gmail.com"
|
18
|
+
gem.homepage = "http://github.com/chicks/sugarcrm"
|
19
|
+
gem.authors = ["Carl Hicks", "David Sulc"]
|
20
|
+
gem.files = `git ls-files`.split("\n")
|
21
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
gem.require_paths = ["lib"]
|
24
|
+
end
|
25
|
+
Jeweler::RubygemsDotOrgTasks.new
|
26
|
+
|
27
|
+
require 'rake/testtask'
|
28
|
+
Rake::TestTask.new(:test) do |test|
|
29
|
+
test.libs << 'lib' << 'test'
|
30
|
+
test.pattern = 'test/**/test_*.rb'
|
31
|
+
test.verbose = true
|
32
|
+
end
|
33
|
+
|
34
|
+
task :default => :test
|
35
|
+
|
36
|
+
require 'rake/rdoctask'
|
37
|
+
Rake::RDocTask.new do |rdoc|
|
38
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
39
|
+
|
40
|
+
rdoc.rdoc_dir = 'rdoc'
|
41
|
+
rdoc.title = "sugarcrm #{version}"
|
42
|
+
rdoc.rdoc_files.include('README*')
|
43
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
44
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.9.18
|
data/WATCHLIST.rdoc
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Each time a new version of SugarCRM comes out, the following should be checked to see whether it is still an issue. Conditional code based on SugarCRM version can be added if SugarCRM patches buggy functionality.
|
2
|
+
|
3
|
+
The get_entries_count function does not work properly (always returns 0) if a condition on a custom (i.e. made with studio and ending in _c) is passed
|
4
|
+
When functionality is fixed in SugarCRM: add conditional code so InvalidAttribute exception is raised on ly when dealing with broken versions
|
5
|
+
|
6
|
+
get_entry_list function 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
|
7
|
+
When functionality is fixed in SugarCRM: update comment in find_by_sql to reflect the fix (code can stay as is)
|
data/bin/sugarcrm
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require 'irb'
|
4
|
+
|
5
|
+
begin
|
6
|
+
require 'sugarcrm'
|
7
|
+
rescue LoadError
|
8
|
+
sugarcrm_path = File.join(File.dirname(__FILE__), '..', 'lib')
|
9
|
+
$:.unshift(sugarcrm_path)
|
10
|
+
require 'sugarcrm'
|
11
|
+
end
|
12
|
+
puts <<-EOF
|
13
|
+
Welcome to the SugarCRM Console!
|
14
|
+
EOF
|
15
|
+
|
16
|
+
IRB.start
|
17
|
+
IRB.conf[:PROMPT][:SUGARCRM] = {
|
18
|
+
:PROMPT_C => "SugarCRM :%03n > ",
|
19
|
+
:AUTO_INDENT=>true,
|
20
|
+
:RETURN=>" => %s \n",
|
21
|
+
:PROMPT_I=>"SugarCRM :%03n > ",
|
22
|
+
:PROMPT_N=>"SugarCRM :%03n?> ",
|
23
|
+
:PROMPT_S=>"SugarCRM :%03n%l> "
|
24
|
+
}
|
25
|
+
IRB.conf[:PROMPT_MODE] = :SUGARCRM
|
26
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
|
3
|
+
module Sugarcrm
|
4
|
+
module Generators
|
5
|
+
class ConfigGenerator < Rails::Generators::Base
|
6
|
+
desc 'Creates a SugarCRM gem configuration file at config/sugarcrm.yml, and an initializer at config/initializers/sugarcrm.rb'
|
7
|
+
|
8
|
+
def self.source_root
|
9
|
+
@_sugarcrm_source_root ||= File.expand_path("../templates", __FILE__)
|
10
|
+
end
|
11
|
+
|
12
|
+
def create_config_file
|
13
|
+
template 'sugarcrm.yml', File.join('config', 'sugarcrm.yml')
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_initializer_file
|
17
|
+
template 'initializer.rb', File.join('config', 'initializers', 'sugarcrm.rb')
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
@@ -0,0 +1,4 @@
|
|
1
|
+
# Load the values in config/sugarcrm.yml into a hash
|
2
|
+
config_values = SugarCRM::Session.parse_config_file(File.join(Rails.root, 'config', 'sugarcrm.yml'))
|
3
|
+
# Connect to appropriate SugarCRM instance (depending on Rails environment)
|
4
|
+
SugarCRM::Session.from_hash(config_values[Rails.env.to_sym])
|
@@ -0,0 +1,19 @@
|
|
1
|
+
development:
|
2
|
+
base_url: http://127.0.0.1/sugarcrm # where your SugarCRM instance is located
|
3
|
+
username: admin
|
4
|
+
password: letmein
|
5
|
+
# connection pool settings
|
6
|
+
# if you don't know what purpose this serves, you can safely ignore these settings
|
7
|
+
# works similarly to Rails' database connection pooling (documented here: http://api.rubyonrails.org/classes/ActiveRecord/ConnectionAdapters/ConnectionPool.html)
|
8
|
+
# pool: 3 # the maximum number of simulatenous connections the gem should open with the SugarCRM server (default is 5)
|
9
|
+
# wait_timeout: 7 # maximum time to wait for an available connection to SugarCRM server, in seconds (default is 5)
|
10
|
+
|
11
|
+
test:
|
12
|
+
base_url: http://127.0.0.1/sugarcrm
|
13
|
+
username: admin
|
14
|
+
password: letmein
|
15
|
+
|
16
|
+
production:
|
17
|
+
base_url: http://127.0.0.1/sugarcrm
|
18
|
+
username: admin
|
19
|
+
password: letmein
|
@@ -0,0 +1,170 @@
|
|
1
|
+
module SugarCRM
|
2
|
+
|
3
|
+
# Associations are middlemen between the object that holds the association, known as the @owner,
|
4
|
+
# and the actual associated object, known as the @target. Methods are added to the @owner that
|
5
|
+
# allow access to the association collection, and are held in @proxy_methods. The cardinality
|
6
|
+
# of the association is available in @cardinality, and the actual relationship details are held
|
7
|
+
# in @relationship.
|
8
|
+
class Association
|
9
|
+
attr :owner, true
|
10
|
+
attr :target, true
|
11
|
+
attr :link_field, true
|
12
|
+
attr :relationship, true
|
13
|
+
attr :attributes, true
|
14
|
+
attr :proxy_methods, true
|
15
|
+
attr :cardinality, true
|
16
|
+
|
17
|
+
# Creates a new instance of an Association
|
18
|
+
def initialize(owner,link_field,opts={})
|
19
|
+
@options = { :define_methods? => true }.merge! opts
|
20
|
+
@owner = owner
|
21
|
+
check_valid_owner
|
22
|
+
@link_field = link_field
|
23
|
+
@attributes = owner.link_fields[link_field]
|
24
|
+
@relationship = relationship_for(@attributes["relationship"])
|
25
|
+
@proxy_methods = define_methods if @options[:define_methods?]
|
26
|
+
@target = resolve_target
|
27
|
+
@cardinality = resolve_cardinality
|
28
|
+
self
|
29
|
+
end
|
30
|
+
|
31
|
+
# Returns true if the association includes an attribute that matches
|
32
|
+
# the provided string
|
33
|
+
def include?(attribute)
|
34
|
+
return true if attribute.class == @target
|
35
|
+
return true if attribute == link_field
|
36
|
+
return true if methods.include? attribute
|
37
|
+
false
|
38
|
+
end
|
39
|
+
|
40
|
+
def ==(comparison_object)
|
41
|
+
comparison_object.instance_of?(self.class) &&
|
42
|
+
@target.class == comparison_object.class &&
|
43
|
+
@link_field == comparison_object.link_field
|
44
|
+
end
|
45
|
+
alias :eql? :==
|
46
|
+
|
47
|
+
def hash
|
48
|
+
"#{@target.class}##{@link_field}".hash
|
49
|
+
end
|
50
|
+
|
51
|
+
def inspect
|
52
|
+
self
|
53
|
+
end
|
54
|
+
|
55
|
+
def to_s
|
56
|
+
"#<#{@owner.class.session.namespace_const}::Association @proxy_methods=[#{@proxy_methods.join(", ")}], " +
|
57
|
+
"@link_field=\"#{@link_field}\", @target=#{@target}, @owner=#{@owner.class}, " +
|
58
|
+
"@cardinality=:#{@cardinality}>"
|
59
|
+
end
|
60
|
+
|
61
|
+
def pretty_print(pp)
|
62
|
+
pp.text self.inspect, 0
|
63
|
+
end
|
64
|
+
|
65
|
+
protected
|
66
|
+
|
67
|
+
def check_valid_owner
|
68
|
+
valid = @owner.class.ancestors.include? SugarCRM::Base
|
69
|
+
raise InvalidModule, "#{@owner} is not registered, or is not a descendant of SugarCRM::Base" unless valid
|
70
|
+
end
|
71
|
+
|
72
|
+
# Attempts to determine the class of the target in the association
|
73
|
+
def resolve_target
|
74
|
+
# Use the link_field name first
|
75
|
+
klass = @link_field.singularize.camelize
|
76
|
+
namespace = @owner.class.session.namespace_const
|
77
|
+
return namespace.const_get(klass) if namespace.const_defined? klass
|
78
|
+
# Use the link_field attribute "module"
|
79
|
+
if @attributes["module"].length > 0
|
80
|
+
module_name = SugarCRM::Module.find(@attributes["module"], @owner.class.session)
|
81
|
+
return namespace.const_get(module_name.klass) if namespace.const_defined? module_name.klass
|
82
|
+
end
|
83
|
+
# Use the "relationship" target
|
84
|
+
if @attributes["relationship"].length > 0
|
85
|
+
klass = @relationship[:target][:name].singularize.camelize
|
86
|
+
return namespace.const_get(klass) if namespace.const_defined? klass
|
87
|
+
end
|
88
|
+
|
89
|
+
if @proxy_methods.is_a? Array
|
90
|
+
if @proxy_methods.last.include? "_1"
|
91
|
+
return namespace.const_get(@proxy_methods.last[0..-3].camelize) if @proxy_methods.last != "securitygroups_1"
|
92
|
+
return namespace.const_get("SecurityGroup")
|
93
|
+
end
|
94
|
+
|
95
|
+
if @proxy_methods.include? "acl_roles"
|
96
|
+
return namespace.const_get("ACLRole")
|
97
|
+
end
|
98
|
+
end
|
99
|
+
false
|
100
|
+
end
|
101
|
+
|
102
|
+
# Defines methods for accessing the association target on the owner class.
|
103
|
+
# If the link_field name includes the owner class name, it is stripped before
|
104
|
+
# creating the method. If this occurs, we also create an alias to the stripped
|
105
|
+
# method using the full link_field name.
|
106
|
+
def define_methods
|
107
|
+
methods = []
|
108
|
+
pretty_name = @relationship[:target][:name]
|
109
|
+
methods << define_method(@link_field)
|
110
|
+
methods << define_alias(pretty_name, @link_field) if pretty_name != @link_field
|
111
|
+
methods
|
112
|
+
end
|
113
|
+
|
114
|
+
# Generates the association proxy method for related module
|
115
|
+
def define_method(link_field)
|
116
|
+
raise ArgumentException, "argument cannot be nil" if link_field.nil?
|
117
|
+
if (@owner.respond_to? link_field.to_sym) && @owner.debug
|
118
|
+
warn "Warning: Overriding method: #{@owner.class}##{link_field}"
|
119
|
+
end
|
120
|
+
@owner.class.module_eval %Q?
|
121
|
+
def #{link_field}
|
122
|
+
query_association :#{link_field}
|
123
|
+
end
|
124
|
+
?
|
125
|
+
link_field
|
126
|
+
end
|
127
|
+
|
128
|
+
# Defines a method alias. Checks to see if a method is already defined.
|
129
|
+
def define_alias(alias_name, method_name)
|
130
|
+
@owner.class.module_eval %Q?
|
131
|
+
alias :#{alias_name} :#{method_name}
|
132
|
+
?
|
133
|
+
alias_name
|
134
|
+
end
|
135
|
+
|
136
|
+
# This method breaks the relationship into parts and returns them
|
137
|
+
def relationship_for(relationship)
|
138
|
+
# We need to run both regexes, because the plurality of the @owner module name is
|
139
|
+
# important
|
140
|
+
plural_regex = /((.*)_)?(#{Regexp.quote(@owner.class._module.name.downcase)})(_(.*))?/
|
141
|
+
singular_regex = /((.*)_)?(#{Regexp.quote(@owner.class._module.name.downcase.singularize)})(_(.*))?/
|
142
|
+
# Break the loop if we match
|
143
|
+
[plural_regex, singular_regex].each {|r| break if relationship.match(r)}
|
144
|
+
# Assign sane values to things if we didnt match
|
145
|
+
o = $3
|
146
|
+
o = @owner.class._module.name.downcase if o.nil? || o.empty?
|
147
|
+
t = [$2, $5].compact.join('_')
|
148
|
+
t = @link_field if t.nil? || t.empty?
|
149
|
+
# Look up the cardinality
|
150
|
+
o_c, t_c = cardinality_for(o,t)
|
151
|
+
{:owner => {:name => o, :cardinality => o_c},
|
152
|
+
:target => {:name => t, :cardinality => t_c}}
|
153
|
+
end
|
154
|
+
|
155
|
+
# Determines if the provided string is plural or singular
|
156
|
+
# Plurality == Cardinality
|
157
|
+
def cardinality_for(*args)
|
158
|
+
args.inject([]) {|results,arg|
|
159
|
+
result = :many
|
160
|
+
result = :one if arg.singularize == arg
|
161
|
+
results << result
|
162
|
+
}
|
163
|
+
end
|
164
|
+
|
165
|
+
def resolve_cardinality
|
166
|
+
"#{@relationship[:owner][:cardinality]}_to_#{@relationship[:target][:cardinality]}".to_sym
|
167
|
+
end
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module SugarCRM; module AssociationCache
|
2
|
+
|
3
|
+
attr :association_cache, false
|
4
|
+
|
5
|
+
# Returns true if an association is cached
|
6
|
+
def association_cached?(association)
|
7
|
+
@association_cache.symbolize_keys.include? association.to_sym
|
8
|
+
end
|
9
|
+
|
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
|
21
|
+
|
22
|
+
# Returns true if an association collection has changed
|
23
|
+
def associations_changed?
|
24
|
+
@association_cache.values.each do |collection|
|
25
|
+
return true if collection.changed?
|
26
|
+
end
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
protected
|
31
|
+
|
32
|
+
# Resets the association cache
|
33
|
+
def clear_association_cache
|
34
|
+
@association_cache = {}.with_indifferent_access
|
35
|
+
end
|
36
|
+
end; end
|