rhoconnect 4.0.0.beta.12 → 4.0.0.beta.24

Sign up to get free protection for your applications and to get access to all the features.
Files changed (88) hide show
  1. data/CHANGELOG.md +34 -0
  2. data/Gemfile +5 -8
  3. data/Gemfile.lock +34 -32
  4. data/Rakefile +2 -12
  5. data/bench/run_bench.sh +1 -1
  6. data/bench/spec/bench_spec_helper.rb +3 -5
  7. data/bin/rhoconnect +20 -13
  8. data/commands/dtach/dtach_install.rb +3 -3
  9. data/commands/generators/update.rb +1 -0
  10. data/commands/parser.rb +180 -0
  11. data/commands/rhoconnect/config.rb +8 -4
  12. data/commands/rhoconnect/get_token.rb +3 -1
  13. data/commands/rhoconnect/routes.rb +34 -0
  14. data/commands/rhoconnect/secret.rb +1 -1
  15. data/commands/rhoconnect/set_admin_password.rb +2 -1
  16. data/commands/rhoconnect/startbg.rb +4 -4
  17. data/commands/rhoconnect/stop.rb +2 -2
  18. data/commands/rhoconnect_console/console.rb +6 -6
  19. data/commands/rhoconnect_spec/spec.rb +2 -1
  20. data/commands/rhoconnect_war/war.rb +1 -0
  21. data/commands/utilities/redis_runner.rb +2 -2
  22. data/doc/data-partitioning.txt +20 -7
  23. data/doc/extending-rhoconnect-server.txt +36 -7
  24. data/doc/push-client-setup-rps.txt +3 -3
  25. data/doc/source-adapters-js.txt +41 -4
  26. data/doc/source-adapters.txt +3 -0
  27. data/generators/templates/application/rcgemfile +1 -5
  28. data/generators/templates/application/spec/spec_helper.rb +0 -9
  29. data/generators/templates/source/models/js/model.js +8 -0
  30. data/generators/templates/source/models/ruby/model.rb +5 -11
  31. data/install.sh +2 -2
  32. data/installer/unix-like/rho_connect_install_constants.rb +3 -3
  33. data/installer/utils/delete_from_s3.rb +3 -6
  34. data/installer/utils/download_from_s3.rb +5 -9
  35. data/installer/utils/verify_checksum.rb +16 -11
  36. data/js-adapters/ballroom.js +9 -0
  37. data/js-adapters/exceptions.js +60 -0
  38. data/js-adapters/node.rb +14 -5
  39. data/js-adapters/node_channel.rb +68 -48
  40. data/js-adapters/rhoconnect_helpers.js +8 -2
  41. data/js-adapters/router.js +16 -14
  42. data/lib/rhoconnect.rb +5 -5
  43. data/lib/rhoconnect/client.rb +2 -2
  44. data/lib/rhoconnect/condition/add_parameter.rb +21 -0
  45. data/lib/rhoconnect/controller/clients_controller.rb +1 -1
  46. data/lib/rhoconnect/controller/js_base.rb +1 -2
  47. data/lib/rhoconnect/controller/system_controller.rb +33 -10
  48. data/lib/rhoconnect/db_adapter.rb +1 -1
  49. data/lib/rhoconnect/document.rb +11 -3
  50. data/lib/rhoconnect/handler/changes.rb +20 -19
  51. data/lib/rhoconnect/handler/changes/engine.rb +48 -25
  52. data/lib/rhoconnect/handler/changes/hooks.rb +36 -0
  53. data/lib/rhoconnect/handler/changes/runner.rb +12 -2
  54. data/lib/rhoconnect/handler/helpers.rb +4 -4
  55. data/lib/rhoconnect/jobs/source_job.rb +1 -1
  56. data/lib/rhoconnect/model/base.rb +32 -8
  57. data/lib/rhoconnect/model/helpers.rb +15 -0
  58. data/lib/rhoconnect/model/helpers/find_duplicates_on_update.rb +85 -0
  59. data/lib/rhoconnect/model/js_base.rb +23 -28
  60. data/lib/rhoconnect/server.rb +23 -18
  61. data/lib/rhoconnect/source.rb +10 -17
  62. data/lib/rhoconnect/store.rb +36 -57
  63. data/lib/rhoconnect/test_methods.rb +5 -4
  64. data/lib/rhoconnect/utilities.rb +7 -5
  65. data/lib/rhoconnect/version.rb +2 -2
  66. data/lib/rhoconnect/web-console/server.rb +17 -16
  67. data/rhoconnect.gemspec +23 -24
  68. data/spec/apps/rhotestapp/controllers/js/js_sample_controller.js +4 -0
  69. data/spec/apps/rhotestapp/models/js/js_sample.js +36 -0
  70. data/spec/apps/rhotestapp/models/ruby/sample_adapter.rb +34 -19
  71. data/spec/async_spec.rb +1 -1
  72. data/spec/cli/cli_spec.rb +69 -31
  73. data/spec/client_sync_spec.rb +30 -13
  74. data/spec/controllers/js_base_spec.rb +10 -4
  75. data/spec/jobs/source_job_spec.rb +1 -1
  76. data/spec/models/{js_model_spec.rb → js_base_spec.rb} +63 -13
  77. data/spec/server/server_spec.rb +20 -0
  78. data/spec/server/stats_spec.rb +7 -17
  79. data/spec/source_adapter_spec.rb +6 -0
  80. data/spec/source_sync_spec.rb +219 -54
  81. data/spec/spec_helper.rb +8 -13
  82. data/spec/store_spec.rb +6 -4
  83. data/spec/test_methods_spec.rb +4 -4
  84. metadata +14 -27
  85. data/commands/execute.rb +0 -48
  86. data/commands/utilities/utilities.rb +0 -6
  87. data/generators/templates/application/controllers/application_controller.rb +0 -17
  88. data/lib/rhoconnect/js_adapter.rb +0 -79
@@ -1,14 +1,16 @@
1
1
  Execute.define_task do
2
2
  desc "config", "Config", :hide => true
3
3
  def config(settings_file=false)
4
+ require 'uri'
5
+ require 'yaml'
4
6
  if File.exist?(File.join('settings','settings.yml'))
5
- settings = load_settings(File.join('settings','settings.yml'))
7
+ settings = YAML.load_file(File.join('settings','settings.yml'))
6
8
  rackup_config = "config.ru"
7
9
  elsif settings_file
8
- settings = load_settings(settings_file)
10
+ settings = YAML.load_file(settings_file)
9
11
  rackup_config = File.join(File.dirname(__FILE__), '..', 'utilities', 'blank_app.ru')
10
12
  elsif File.exist?(File.join(ENV['HOME'], '.rhoconnect.yml'))
11
- settings = load_settings(File.join(ENV['HOME'], '.rhoconnect.yml'))
13
+ settings = YAML.load_file(File.join(ENV['HOME'], '.rhoconnect.yml'))
12
14
  rackup_config = File.join(File.dirname(__FILE__), '..', 'utilities', 'blank_app.ru')
13
15
  else
14
16
  options = { :syncserver => "http://localhost:#{RHOCONNECT_PORT}",
@@ -16,7 +18,9 @@ Execute.define_task do
16
18
  settings = { :development => options, :test => options, :production => options }
17
19
  rackup_config = File.join(File.dirname(__FILE__), '..', 'utilities', 'blank_app.ru')
18
20
  end
19
- settings = settings[Rhoconnect.environment]
21
+
22
+ environment = (ENV['RHO_ENV'] || ENV['RACK_ENV'] || :development).to_sym # FIXME:
23
+ settings = settings[environment]
20
24
  # syncserver settings
21
25
  uri = URI.parse(settings[:syncserver])
22
26
  port = uri.port unless port
@@ -1,6 +1,7 @@
1
1
  Execute.define_task do
2
2
  desc "get-token", "Fetch current api token from rhoconnect"
3
3
  def get_token(save = true)
4
+ require 'rest_client'
4
5
  url = config[:syncserver]
5
6
  password = ''
6
7
  begin
@@ -15,7 +16,8 @@ desc "get-token", "Fetch current api token from rhoconnect"
15
16
  begin
16
17
  token = RestClient.post("#{url}/rc/v1/system/login",
17
18
  { :login => 'rhoadmin', :password => password }.to_json, :content_type => :json)
18
- rescue
19
+ rescue Exception => e
20
+ puts e.message
19
21
  puts "Rhoconnect server is not running or invalid credentials."
20
22
  exit
21
23
  end
@@ -0,0 +1,34 @@
1
+ Execute.define_task do
2
+ desc "routes", "Prints out routes defined in the application"
3
+ def routes
4
+ puts ""
5
+ puts "\tYour Application [#{Dir.pwd}] has the following routes map:"
6
+ puts ""
7
+ start_list = []
8
+ begin
9
+ redis_array = config[:redis]
10
+ redis_array.each do |redis_inst|
11
+ start_list << redis_inst unless RedisRunner.running?(redis_inst)
12
+ end
13
+ RedisRunner.startbg(start_list) unless start_list.empty?
14
+
15
+ require 'rhoconnect/application/init'
16
+ Rhoconnect::Server.set :secret, 'temp_secret'
17
+ Rhoconnect.url_map.each do |root, controller|
18
+ puts " #{controller.helpers.class.name}: #{root}"
19
+ controller.helpers.class.paths.each do |verb, paths|
20
+ paths.each do |path|
21
+ puts " --> #{verb.to_s.upcase}\t #{path}"
22
+ end
23
+ end
24
+ end
25
+ rescue Exception => e
26
+ Rhoconnect.log "#{e.inspect}"
27
+ ensure
28
+ Rhoconnect.shutdown
29
+ # TODO: Something is wrong here on Windows!!!
30
+ # if I use RedisRunner.stop - process hangs for several seconds
31
+ system("rhoconnect redis-stop") unless start_list.empty?
32
+ end
33
+ end
34
+ end
@@ -2,7 +2,7 @@ Execute.define_task do
2
2
  desc "secret", "Generate a cryptographically secure secret session key"
3
3
  def secret
4
4
  begin
5
- require 'securerandom'
5
+ require 'securerandom'
6
6
  puts SecureRandom.hex(64)
7
7
  rescue LoadError
8
8
  puts "Missing secure random generator. Try running `rhoconnect secret` in a rails application instead."
@@ -17,8 +17,9 @@ Execute.define_task do
17
17
  elsif new_pass == new_pass_confirm
18
18
  puts ""
19
19
  url = config[:syncserver]
20
- RestClient.put("#{url}/rc/v1/users/rhoadmin",
20
+ res = RestClient.put("#{url}/rc/v1/users/rhoadmin",
21
21
  { :attributes => { :new_password => new_pass }}.to_json, {:content_type => :json, 'X-RhoConnect-API-TOKEN' => token})
22
+ puts "Admin password is successfully updated" if res.code == 200
22
23
  else
23
24
  puts "\nNew password and confirmation must match."
24
25
  end
@@ -8,10 +8,10 @@
8
8
  Execute.define_task do
9
9
  desc "startbg [CHILDNAME]", "Start rhoconnect server in bg mode (Rhostudio) - this is an internal command", :hide => true
10
10
  def startbg(childname=nil)
11
- cmd = (jruby?) ? trinidad? : (thin? || mongrel? || report_missing_server)
11
+ cmd = "bundle exec rackup -s #{defined?(JRUBY_VERSION) ? 'puma' : 'thin'}"
12
12
  require 'win32/process' if windows?
13
-
14
- p1 = Process.fork {
13
+
14
+ p1 = Process.fork {
15
15
  if windows?
16
16
  puts 'Starting rhoconnect ...'
17
17
  system "#{cmd} config.ru -P #{rhoconnect_pid}"
@@ -23,7 +23,7 @@ Execute.define_task do
23
23
  end
24
24
  }
25
25
  Process.detach(p1)
26
-
26
+
27
27
  exit
28
28
  end #startbg
29
29
  end
@@ -5,12 +5,12 @@ Execute.define_task do
5
5
  File.delete "#{rhoconnect_pid}" if system("FOR /F %A in (#{rhoconnect_pid}) do taskkill /F /PID %A")
6
6
  else
7
7
  if File.exist?("#{rhoconnect_pid}")
8
- pid = `cat #{rhoconnect_pid}`
8
+ pid = `cat #{rhoconnect_pid}`
9
9
  puts "Sending a QUIT signal to process #{pid}"
10
10
  system "kill -3 #{pid}"
11
11
  3.times do
12
12
  sleep 1
13
- return if !File.exist?("#{rhoconnect_pid}")
13
+ return if !File.exist?("#{rhoconnect_pid}")
14
14
  end
15
15
 
16
16
  puts "Process #{pid} is still running. Sending a KILL signal to it ..."
@@ -2,17 +2,17 @@ Execute.define_task do
2
2
  desc "console [environment]", "Run rhoconnect console"
3
3
  def console(environment=nil)
4
4
  ENV['RACK_ENV'] = environment || 'development'
5
- application_file = (File.exist?(File.join(Dir.pwd, 'application.rb'))) ?
6
- File.join(Dir.pwd, 'application.rb') :
7
- File.join(File.dirname(__FILE__), '..', '..', 'generators', 'templates', 'application', 'application.rb')
5
+ controller_file = (File.exist?(File.join(Dir.pwd, 'controllers', 'ruby', 'application_controller.rb'))) ?
6
+ File.join(Dir.pwd, 'controllers', 'ruby', 'application_controller.rb') :
7
+ File.join(File.dirname(__FILE__), '..', '..', 'generators', 'templates', 'application', 'controllers', 'ruby', 'application_controller.rb')
8
8
 
9
- redis_url = config[:redis]
10
9
  if RedisRunner.running?
11
10
  system "irb -rubygems -r #{File.join(File.dirname(__FILE__),'console_helper')} " +
12
11
  "-r #{File.join(File.dirname(__FILE__), '..', '..', 'lib', 'rhoconnect') } " +
13
- "-r #{application_file}"
12
+ "-r #{File.join(File.dirname(__FILE__), '..', '..', 'lib', 'rhoconnect', 'server') } " +
13
+ "-r #{controller_file}"
14
14
  else
15
- puts "Redis is not running on #{redis_url}. Please start it by running 'rhoconnect redis-start' command."
15
+ puts "Redis is not running. Please start it by running 'rhoconnect redis-start' command."
16
16
  end
17
17
  end
18
18
  end
@@ -1,8 +1,9 @@
1
1
  Execute.define_task do
2
2
  begin
3
- require 'rspec/core/rake_task'
4
3
  desc "spec", "Run source adapter specs"
5
4
  def spec
5
+ require 'rspec/core/rake_task'
6
+
6
7
  files = File.join('spec','**','*_spec.rb')
7
8
  pattern = FileList[files]
8
9
  rspec_opts = "-fn -b --color"
@@ -2,6 +2,7 @@ Execute.define_task do
2
2
  desc "war", "Build executable WAR file to be used in Java App Servers"
3
3
  def war
4
4
  if jruby? then
5
+ require 'rake'
5
6
  puts "building the WAR file"
6
7
  if not File::exists? "config/warble.rb"
7
8
  puts "generating Warbler's config file"
@@ -65,13 +65,13 @@ class RedisRunner
65
65
  def self.startbg(redis_array)
66
66
  if windows?
67
67
  puts "Starting redis in a new window..."
68
- system "start #{File.join(redis_home,'redis-server')} #{File.join(redis_home,'redis.conf')}" rescue
68
+ system "start \"\" #{File.join(redis_home,'redis-server')} #{File.join(redis_home,'redis.conf')}" rescue
69
69
  "redis-server not installed on your path, please install redis."
70
70
  else
71
71
  puts "Starting redis ..."
72
72
  redis_array.each do |cfg|
73
73
  host, port = cfg.split(':')[0..1]
74
- system("redis-server --port #{port} &") if RedisRunner.local_ip?(host)
74
+ system("redis-server --port #{port} > /dev/null 2>&1 &") if RedisRunner.local_ip?(host)
75
75
  end
76
76
  end
77
77
  end
@@ -9,25 +9,38 @@ The MD is referenced in RhoConnect by a corresponding partition. Source adapter
9
9
 
10
10
  Likewise, app partitioning stores one copy of the source adapter MD for the entire application (all users and devices share the same data). App partitioning can be particularly useful if you have source adapter models which retrieve large amounts of data that is fixed from user to user, for example a global product catalog. Using app partitioning wherever possible ***greatly reduces*** the amount of data in redis.
11
11
 
12
- ### User Partition
13
- User partitioning is the default mode for source adapters, however you can explicitly define it in `settings/settings.yml` with:
12
+ ### App Partition
13
+ Enable app partitioning the same way:
14
14
 
15
15
  :::yaml
16
16
  :sources:
17
17
  Product:
18
18
  :poll_interval: 300
19
- :partition_type: user
19
+ :partition_type: app
20
20
 
21
- ### App Partition
22
- Enable app partitioning the same way:
21
+ Now you have a single copy of the `Product` source adapter dataset for all users.
22
+
23
+ ### User Partition
24
+ User partitioning is the default mode for source adapters, however you can explicitly define it in `settings/settings.yml` with:
23
25
 
24
26
  :::yaml
25
27
  :sources:
26
28
  Product:
27
29
  :poll_interval: 300
28
- :partition_type: app
30
+ :partition_type: user
29
31
 
30
- Now you have a single copy of the `Product` source adapter dataset for all users.
32
+ ### Custom User partitioning
33
+ Sometimes, different groups of users share the common source data. To leverage this, you can implement the following method in your Source Adapter Model to provide custom partition names for the users with shared data. In this case, RhoConnect will store the data for MD of the grouped users under your custom name, which will reduce the memory footprint in Redis. From this standpoint, `app` partition is the edge-case of the custom user partitioning where all users share the same data for the particular source.
34
+
35
+ To use the custom user partitioning, implement the following class method in your Source Adapter's model:
36
+
37
+ :::ruby
38
+ class Product < Rhoconnect::Model::Base
39
+ # group users by the first letter
40
+ def self.partition_name(user_id)
41
+ return user_id[0]
42
+ end
43
+ end
31
44
 
32
45
  ## Pass Through
33
46
  RhoConnect provides a simple way to keep data out of redis. If you have sensitive data that you do not want saved in redis, add the `pass_through` option in settings/settings.yml for each source:
@@ -1,23 +1,52 @@
1
1
  Extending RhoConnect Application with custom routes
2
2
  ===
3
3
 
4
+ Adding custom routes to a Controller
5
+ ---
6
+
4
7
  You can provide custom routes support in your RhoConnect application and utilize all the powerful features of the typical Sinatra app. To do this, simply define your routes in the corresponding controller class. Endpoint URL for your routes will be relative to the controller's root.
5
8
 
6
- The following example illustrates how to add a sample `my_custom_route' to the Product Controller:
9
+ The following example illustrates how to add a sample `my_custom_route` to the Product Controller:
7
10
 
8
11
  :::ruby
9
- class Product < Rhoconnect::Controller::Base
12
+ class ProductController < Rhoconnect::Controller::Base
10
13
  get '/my_custom_route', :login_required => true do
11
14
  send_file 'public/my_file.png'
12
15
  end
13
16
  end
14
17
 
15
- This route will have the following URL : GET '/app/v4/Product/my_custom_route'
18
+ This route will have the following URL : GET '/app/v1/Product/my_custom_route'
16
19
 
17
- The above custom route implementation will respond to client's GET request, verifies the current_user (which will be extracted from the RHoConnect session cookie and checked for validity by the `:login_required` condition)
20
+ The above custom route implementation will respond to client's GET request, verifies the current_user (which will be extracted from the RhoConnect session cookie and checked for validity by the `:login_required` condition)
18
21
  and returns a static PNG file (which can be later used in BLOB syncs)
19
22
 
20
- Using the New Custom route in BLOB syncs
23
+ Routes declaration order
24
+ ---
25
+
26
+ At run-time all requests are being matched against the declared routes to find the appropriate route handler. Sinatra matches the routes sequentially in the order of their declaration until the first matching route signature is found. This can lead to a possibility where the declared route can hide subsequent routes. For example:
27
+
28
+ :::ruby
29
+ class ProductController < Rhoconnect::Controller::Base
30
+ register Rhoconnect::Handler::Sync
31
+
32
+ delete '/my_custom_route_delete', :login_required => true do
33
+ # do something here
34
+ end
35
+ end
36
+
37
+ In the above example, user's custom route will never be called, because `register Rhoconnect::Handler::Sync` (which is responsible for [adding pre-defined SYNC routes](/rhoconnectapi/source-adapter-controller-api-ruby#register Rhoconnect::Handler::Sync)) will add `DELETE '/:id'` route.
38
+ As a result, ProductController will have the following routes map:
39
+
40
+ * 1) DELETE '/:id'
41
+ * 2) DELETE '/my_custom_route_delete'
42
+
43
+ At run-time, Controller will try to match `DELETE /my_custom_route_delete` with the FIRST appropriate route declaration - and, it will find that `DELETE '/:id'` is a good match. Controller will treat string `my_custom_route_delete` as an instance of an `:id` parameter. So, the user-defined custom route will never be called - it is being hidden by the more-generic route that has been declared first in order.
44
+
45
+ In summary, you should always specify more-specific routes first followed by the more-generic routes. In the above example, moving the custom route's declaration above the line 'register Rhoconnect::Handler::Sync' will make the custom route match happening before the parametrized route. Alternatively, you can write your routes using "as unique as possible" signatures, i.e. in the above example, declaring the route as `DELETE '/my/custom_route/delete'` will not match any other route declaration.
46
+
47
+ NOTE: You can always see the routes that are available in your application using the `rhoconnect routes` command.
48
+
49
+ Using custom routes in BLOB syncs
21
50
  ---
22
51
 
23
52
  After you create the new custom routes you can reference and use them from standard Rhodes applications.
@@ -52,11 +81,11 @@ Then, modify your RhoConnect source adapter model (in `rhoconnect_app/models/rub
52
81
 
53
82
  @result={}
54
83
  parsed.each do |item|
55
- item["product"]["my_custom_field-rhoblob"] = "http://localhost:9292/app/v4/Product/my_custom_route"
84
+ item["product"]["my_custom_field-rhoblob"] = "http://localhost:9292/app/v1/Product/my_custom_route"
56
85
  @result[item["product"]["id"].to_s] = item["product"]
57
86
  end if parsed
58
87
  end
59
88
 
60
89
  This way, your `my_custom_field` would be getting the BLOB image data from the custom route defined in your RhoConnect Source Adapter Controller.
61
90
 
62
- For more information on BLOB syncs, see [this section](http://docs.rhomobile.com/rhoconnect/blob-sync).
91
+ For more information on BLOB syncs, see [this section](/rhoconnect/blob-sync).
@@ -91,13 +91,13 @@ You can also configure more advanced settings in RhoConnect push by creating a c
91
91
  {
92
92
  "httpSecure": "n",
93
93
  "devAuthHost": "localhost",
94
- "devAuthUrl": "/rc/v1/app/ans_login",
94
+ "devAuthUrl": "/rc/v1/app/rps_login",
95
95
  "devAuthPort": "9292",
96
96
  "userAuthHost": "localhost",
97
- "userAuthUrl": "/rc/v1/app/ans_login",
97
+ "userAuthUrl": "/rc/v1/app/rps_login",
98
98
  "userAuthPort": "9292",
99
99
  "appAuthHost": "localhost",
100
- "appAuthUrl": "/rc/v1/app/ans_login",
100
+ "appAuthUrl": "/rc/v1/system/rps_login",
101
101
  "appAuthPort": "9292",
102
102
  "ansResponseTimeout": "300000",
103
103
  "ansServerPort": "8675",
@@ -55,6 +55,8 @@ Its purpose is to define the RhoConnect application HTTP end point and to regist
55
55
  The generated source adapter's model file (in this case, `models/product.js`) is similar to the code listing below:
56
56
 
57
57
  :::javascript
58
+ var rc = require('rhoconnect_helpers');
59
+
58
60
  var Product = function(){
59
61
 
60
62
  this.login = function(resp){
@@ -98,10 +100,20 @@ The generated source adapter's model file (in this case, `models/product.js`) is
98
100
  // TODO: Logout from the data source if necessary.
99
101
  resp.send(true);
100
102
  };
103
+
104
+ this.storeBlob = function(resp){
105
+ // TODO: Handle post requests for blobs here.
106
+ // Reference the blob object's path with resp.params.path.
107
+ new rc.Exception(
108
+ resp, "Please provide some code to handle blobs if you are using them."
109
+ );
110
+ };
101
111
  };
102
112
 
103
113
  module.exports = new Product();
104
114
 
115
+ In this model you will need to implement the functions necessary for the create, query, update and delete sync operations. Optionally you may implement functions for login, logoff and storeBlob (if your application uses blobs).
116
+
105
117
  ## Source Adapter API
106
118
 
107
119
  ### Source Adapter Controller API
@@ -131,7 +143,7 @@ You can write the following methods for your source adapter model. These methods
131
143
  * [getData](/rhoconnectapi/source-adapter-model-api-js#getdataresp-callback) - Get the model document data from Store.
132
144
  * [currentUser](/rhoconnectapi/source-adapter-model-api-js#currentuser) - Returns the current user who called the adapter's model.
133
145
  * [storeBlob](/rhoconnectapi/source-adapter-model-api-js#storeblob) - Save the incoming blob data into permanent storage for the future processing.
134
-
146
+ * [partitionName](/rhoconnectapi/source-adapter-model-api-js#partitionname) - Provide a custom user partition for a model.
135
147
 
136
148
  ### Request API
137
149
  The [request object](/rhoconnectapi/source-adapter-request-api-js) contains information about the HTTP request the app is receiving.
@@ -157,6 +169,17 @@ RhoConnect provides a simple [redis interface](/rhoconnectapi/source-adapter-sto
157
169
  * [getData](/rhoconnectapi/source-adapter-store-api-js#getdatarespcallback) - Retrieve an array or hash from redis.
158
170
  * [putData](/rhoconnectapi/source-adapter-store-api-js#putdatarespcallback) - Add an array or hash to redis.
159
171
 
172
+ ## Exception API
173
+ Use the following exception types in your JavaScript application to handle any error conditions. Don't worry, if your application throws an uncaught exception, the base `RhoConnect::Model::Exception` will automatically be used.
174
+
175
+ * [Exception](/rhoconnectapi/source-adapter-exception-api-js#exceptionresponse-message) - Raise a `RhoConnect::Model::Exception`.
176
+ * [LoginException](/rhoconnectapi/source-adapter-exception-api-js#loginexceptionresponse-message) - Raise a `RhoConnect::Model::LoginException`.
177
+ * [LogoffException](/rhoconnectapi/source-adapter-exception-api-js#logoffexceptionresponse-message) - Raise a `RhoConnect::Model::LogoffException`.
178
+ * [ServerTimeoutException](/rhoconnectapi/source-adapter-exception-api-js#servertimeoutexceptionresponse-message) - Raise a `RhoConnect::Model::ServerTimeoutException`.
179
+ * [ServerErrorException](/rhoconnectapi/source-adapter-exception-api-js#servererrorexceptionresponse-message) - Raise a `RhoConnect::Model::ServerErrorException`.
180
+ * [ObjectConflictErrorException](/rhoconnectapi/source-adapter-exception-api-js#objectconflicterrorexceptionresponse-message) - Raise a `RhoConnect::Model::ObjectConflictErrorException`.
181
+
182
+
160
183
  ## Sample Model
161
184
  Here's a complete example of how the completed [product model might look](https://gist.github.com/larsburgess/87753882f3a4a366b48b):
162
185
 
@@ -196,7 +219,10 @@ Here's a complete example of how the completed [product model might look](https:
196
219
  host: host,
197
220
  path: '/products.json',
198
221
  method: 'POST',
199
- headers: { 'Content-Type': 'application/json' }
222
+ headers: {
223
+ 'Content-Type': 'application/json',
224
+ 'Content-Length': postData.length
225
+ }
200
226
  };
201
227
  var req = http.request(options, function(res){
202
228
  res.on('data', function(chunk){
@@ -220,7 +246,10 @@ Here's a complete example of how the completed [product model might look](https:
220
246
  host: host,
221
247
  path: '/products/' + objId + '.json',
222
248
  method: 'PUT',
223
- headers: { 'Content-Type': 'application/json' }
249
+ headers: {
250
+ 'Content-Type': 'application/json',
251
+ 'Content-Length': putData.length
252
+ }
224
253
  };
225
254
  var req = http.request(options, function(res){
226
255
  res.on('data', function(){});
@@ -258,6 +287,14 @@ Here's a complete example of how the completed [product model might look](https:
258
287
  this.logoff = function(resp){
259
288
  resp.send(true);
260
289
  };
290
+
291
+ this.storeBlob = function(resp){
292
+ // TODO: Handle post requests for blobs here.
293
+ // Reference the blob object's path with resp.params.path.
294
+ new rc.Exception(
295
+ resp, "Please provide some code to handle blobs if you are using them."
296
+ );
297
+ };
261
298
  };
262
299
 
263
- module.exports = new Product();
300
+ module.exports = new Product();
@@ -130,6 +130,8 @@ During generation of the model, the `settings/settings.yml` file will be updated
130
130
 
131
131
  You can use the following methods and techniques inside of your source adapter controller.
132
132
 
133
+ * [register Rhoconnect::EndPoint](/rhoconnectapi/source-adapter-controller-api-ruby#register Rhoconnect::EndPoint) - adds Controller into the application's URL Map.
134
+ * [resgiter Rhoconnect::Handler::Sync](/rhoconnectapi/source-adapter-controller-api-ruby#register Rhoconnect::Handler::Sync) - adds SYNC routes to your Controller.
133
135
  * [@model](/rhoconnectapi/source-adapter-controller-api-ruby#@model) - Access to the source adapter's model instance.
134
136
  * [params,request,response](/rhoconnectapi/source-adapter-controller-api-ruby#sinatra-context) - Allows to use any standard Sinatra context objects.
135
137
  * [Sinatra framework](/rhoconnectapi/source-adapter-controller-api-ruby#sinatra-features) - Allows to use any standard Sinatra features.
@@ -148,6 +150,7 @@ You can write the following methods for your source adapter model. These methods
148
150
  * [current_user](/rhoconnectapi/source-adapter-model-api-ruby#currentuser) - Returns the current user which called the adapter's model.
149
151
  * [stash_result](/rhoconnectapi/source-adapter-model-api-ruby#stashresult) - Saves the current state of `@result` to redis and assigns it to `nil`.
150
152
  * [store_blob](/rhoconnectapi/source-adapter-model-api-ruby#store_blob) - Save the incoming blob data into permanent storage for the future processing.
153
+ * [self.partition_name](/rhoconnectapi/source-adapter-model-api-ruby#self.partition_name) - Implement this class method to provide custom user partition.
151
154
  * [get_data](/rhoconnectapi/source-adapter-model-api-ruby#get_data) - Get the model document data from Store.
152
155
 
153
156
  ## Data Partitioning