rhoconnect-rb 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.autotest +3 -0
- data/.gitignore +5 -0
- data/.rspec +3 -0
- data/Gemfile +10 -0
- data/README.md +50 -0
- data/Rakefile +21 -0
- data/config/routes.rb +7 -0
- data/init.rb +1 -0
- data/lib/rhoconnect-rb.rb +7 -0
- data/lib/rhosync/client.rb +88 -0
- data/lib/rhosync/configuration.rb +38 -0
- data/lib/rhosync/endpoints.rb +167 -0
- data/lib/rhosync/resource.rb +143 -0
- data/lib/rhosync/version.rb +3 -0
- data/rhoconnect-rb.gemspec +27 -0
- data/spec/client_spec.rb +125 -0
- data/spec/endpoints_spec.rb +234 -0
- data/spec/resource_spec.rb +118 -0
- data/spec/spec_helper.rb +103 -0
- metadata +118 -0
data/.autotest
ADDED
data/.rspec
ADDED
data/Gemfile
ADDED
data/README.md
ADDED
@@ -0,0 +1,50 @@
|
|
1
|
+
rhoconnect-rb
|
2
|
+
===
|
3
|
+
|
4
|
+
A ruby client library for the [RhoSync](http://rhomobile.com/products/rhosync) App Integration Server.
|
5
|
+
|
6
|
+
Using rhoconnect-rb, your application's model data will transparently synchronize with a mobile application built using the [Rhodes framework](http://rhomobile.com/products/rhodes), or any of the available [RhoSync clients](http://rhomobile.com/products/rhosync/). This client includes built-in support for [ActiveRecord](http://ar.rubyonrails.org/) and [DataMapper](http://datamapper.org/) models.
|
7
|
+
|
8
|
+
## Getting started
|
9
|
+
|
10
|
+
Load the `rhoconnect-rb` library:
|
11
|
+
|
12
|
+
require 'rhoconnect-rb'
|
13
|
+
|
14
|
+
Note, if you are using datamapper, install the `dm-serializer` library and require it in your application. `rhoconnect-rb` depends on this utility to interact with RhoSync applications using JSON.
|
15
|
+
|
16
|
+
## Usage
|
17
|
+
Now include Rhosync::Resource in a model that you want to synchronize with your mobile application:
|
18
|
+
|
19
|
+
class Product < ActiveRecord::Base
|
20
|
+
include Rhosync::Resource
|
21
|
+
end
|
22
|
+
|
23
|
+
Or, if you are using DataMapper:
|
24
|
+
|
25
|
+
class Product
|
26
|
+
include DataMapper::Resource
|
27
|
+
include Rhosync::Resource
|
28
|
+
end
|
29
|
+
|
30
|
+
Next, your models will need to declare a partition key for `rhoconnect-rb`. This partition key is used by `rhoconnect-rb` to uniquely identify the model dataset when it is stored in a RhoSync application. It is typically an attribute on the model or related model. `rhoconnect-rb` supports two types of partitions:
|
31
|
+
|
32
|
+
* :app - No unique key will be used, a shared dataset is used for all users.
|
33
|
+
* lambda { some lambda } - Execute a lambda which returns the unique key string.
|
34
|
+
|
35
|
+
For example, the `Product` model above might have a `belongs_to :user` relationship. The partition identifying the username would be declared as:
|
36
|
+
|
37
|
+
class Product < ActiveRecord::Base
|
38
|
+
include Rhosync::Resource
|
39
|
+
|
40
|
+
belongs_to :user
|
41
|
+
|
42
|
+
partition lambda { self.user.username }
|
43
|
+
end
|
44
|
+
|
45
|
+
For more information about RhoSync partitions, please refer to the [RhoSync docs](http://docs.rhomobile.com/rhosync/source-adapters#data-partitioning).
|
46
|
+
|
47
|
+
## Meta
|
48
|
+
Created and maintained by Vladimir Tarasov and Lars Burgess.
|
49
|
+
|
50
|
+
Released under the [MIT License](http://www.opensource.org/licenses/mit-license.php).
|
data/Rakefile
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
Bundler.setup(:default, :test)
|
4
|
+
require 'bundler/gem_tasks'
|
5
|
+
|
6
|
+
require 'rspec/core/rake_task'
|
7
|
+
|
8
|
+
desc "Run all specs"
|
9
|
+
RSpec::Core::RakeTask.new(:spec) do |t|
|
10
|
+
t.rspec_opts = ["-b", "-c", "-fd"]
|
11
|
+
t.pattern = 'spec/**/*_spec.rb'
|
12
|
+
end
|
13
|
+
|
14
|
+
desc "Run all specs with rcov"
|
15
|
+
RSpec::Core::RakeTask.new(:rcov) do |t|
|
16
|
+
t.rcov = true
|
17
|
+
t.rspec_opts = ["-b", "-c", "-fd"]
|
18
|
+
t.rcov_opts = ['--exclude', 'spec/*,gems/*']
|
19
|
+
end
|
20
|
+
|
21
|
+
task :default => :spec
|
data/config/routes.rb
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
Rails.application.routes.draw do
|
2
|
+
match '/rhosync/authenticate' => Rhosync::Authenticate
|
3
|
+
match '/rhosync/query' => Rhosync::Query
|
4
|
+
match '/rhosync/create' => Rhosync::Create
|
5
|
+
match '/rhosync/update' => Rhosync::Update
|
6
|
+
match '/rhosync/delete' => Rhosync::Delete
|
7
|
+
end
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'rhoconnect-rb'
|
@@ -0,0 +1,88 @@
|
|
1
|
+
require 'uri'
|
2
|
+
|
3
|
+
module Rhosync
|
4
|
+
class Client
|
5
|
+
attr_accessor :uri, :token
|
6
|
+
|
7
|
+
# allow configuration, uri or environment variable initialization
|
8
|
+
def initialize(params = {})
|
9
|
+
uri = params[:uri] || Rhosync.configuration.uri || ENV['RHOSYNC_URL']
|
10
|
+
raise ArgumentError.new("Please provide a :uri or set RHOSYNC_URL") unless uri
|
11
|
+
uri = URI.parse(uri)
|
12
|
+
|
13
|
+
@token = params[:token] || Rhosync.configuration.token || uri.user
|
14
|
+
uri.user = nil; @uri = uri.to_s
|
15
|
+
raise ArgumentError.new("Please provide a :token or set it in uri") unless @token
|
16
|
+
|
17
|
+
RestClient.proxy = ENV['HTTP_PROXY'] || ENV['http_proxy']
|
18
|
+
end
|
19
|
+
|
20
|
+
def create(source_name, partition, obj = {})
|
21
|
+
send_objects(:push_objects, source_name, partition, obj)
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy(source_name, partition, obj = {})
|
25
|
+
send_objects(:push_deletes, source_name, partition, obj)
|
26
|
+
end
|
27
|
+
|
28
|
+
# update, create, it doesn't matter :)
|
29
|
+
alias :update :create
|
30
|
+
|
31
|
+
def set_auth_callback(callback)
|
32
|
+
process(:post, "/api/set_auth_callback", { :callback => callback })
|
33
|
+
end
|
34
|
+
|
35
|
+
def set_query_callback(source_name, callback)
|
36
|
+
process(:post, "/api/set_query_callback",
|
37
|
+
{
|
38
|
+
:source_id => source_name,
|
39
|
+
:callback => callback
|
40
|
+
}
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
protected
|
45
|
+
|
46
|
+
def validate_args(source_name, partition, obj = {}) # :nodoc:
|
47
|
+
raise ArgumentError.new("Missing object id for #{obj.inspect}") unless obj['id']
|
48
|
+
raise ArgumentError.new("Missing source_name.") unless source_name or source_name.empty?
|
49
|
+
raise ArgumentError.new("Missing partition for #{model}.") unless partition or partition.empty?
|
50
|
+
end
|
51
|
+
|
52
|
+
def send_objects(action, source_name, partition, obj = {}) # :nodoc:
|
53
|
+
validate_args(source_name, partition, obj)
|
54
|
+
|
55
|
+
process(:post, "/api/#{action}",
|
56
|
+
{
|
57
|
+
:source_id => source_name,
|
58
|
+
:user_id => partition,
|
59
|
+
:objects => action == :push_deletes ? [obj['id'].to_s] : { obj['id'] => obj }
|
60
|
+
}
|
61
|
+
)
|
62
|
+
end
|
63
|
+
|
64
|
+
def resource(path) # :nodoc:
|
65
|
+
RestClient::Resource.new(@uri)[path]
|
66
|
+
end
|
67
|
+
|
68
|
+
def process(method, path, payload = nil) # :nodoc:
|
69
|
+
headers = api_headers
|
70
|
+
unless method == :get
|
71
|
+
payload = payload.merge!(:api_token => @token).to_json
|
72
|
+
headers = api_headers.merge(:content_type => 'application/json')
|
73
|
+
end
|
74
|
+
args = [method, payload, headers].compact
|
75
|
+
response = resource(path).send(*args)
|
76
|
+
response
|
77
|
+
end
|
78
|
+
|
79
|
+
def api_headers # :nodoc:
|
80
|
+
{
|
81
|
+
'User-Agent' => Rhosync::VERSION,
|
82
|
+
'X-Ruby-Version' => RUBY_VERSION,
|
83
|
+
'X-Ruby-Platform' => RUBY_PLATFORM
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
end
|
88
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Rhosync
|
2
|
+
class Configuration
|
3
|
+
attr_accessor :uri, :token, :authenticate, :sync_time_as_int
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@sync_time_as_int = true
|
7
|
+
end
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
class << self
|
12
|
+
attr_accessor :configuration
|
13
|
+
end
|
14
|
+
|
15
|
+
# Configure RhoSync in an initializer:
|
16
|
+
# like config/initializers/rhosync.rb
|
17
|
+
#
|
18
|
+
# Setup the RhoSync uri and api token.
|
19
|
+
# Use rhosync:get_token to get the token value.
|
20
|
+
#
|
21
|
+
# config.uri = "http://myrhosync.com"
|
22
|
+
# config.token = "secrettoken"
|
23
|
+
# config.authenticate = lambda { |credentials|
|
24
|
+
# User.authenticate(credentials)
|
25
|
+
# }
|
26
|
+
#
|
27
|
+
# @example
|
28
|
+
# Rhosync.configure do |config|
|
29
|
+
# config.uri = "http://myrhosync.com"
|
30
|
+
# config.token = "secrettoken"
|
31
|
+
# end
|
32
|
+
def self.configure
|
33
|
+
self.configuration = Configuration.new
|
34
|
+
yield(configuration)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
Rhosync.configure { }
|
@@ -0,0 +1,167 @@
|
|
1
|
+
require 'json'
|
2
|
+
|
3
|
+
module Rhosync
|
4
|
+
class EndpointHelpers
|
5
|
+
def self.authenticate(content_type, body)
|
6
|
+
code, params = 200, parse_params(content_type, body)
|
7
|
+
if Rhosync.configuration.authenticate
|
8
|
+
code = 401 unless Rhosync.configuration.authenticate.call(params)
|
9
|
+
end
|
10
|
+
[code, {'Content-Type' => 'text/plain'}, [""]]
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.query(content_type, body)
|
14
|
+
params = parse_params(content_type, body)
|
15
|
+
action, c_type, result, records = :rhosync_query, 'application/json', {}, []
|
16
|
+
# Call resource rhosync_query class method
|
17
|
+
code, error = get_rhosync_resource(params['resource'], action) do |klass|
|
18
|
+
records = klass.send(action, params['partition'])
|
19
|
+
end
|
20
|
+
if code == 200
|
21
|
+
# Serialize records into hash of hashes
|
22
|
+
records.each do |record|
|
23
|
+
result[record.id.to_s] = record.normalized_attributes
|
24
|
+
end
|
25
|
+
result = result.to_json
|
26
|
+
else
|
27
|
+
result = error
|
28
|
+
c_type = 'text/plain'
|
29
|
+
# Log warning if something broke
|
30
|
+
warn error
|
31
|
+
end
|
32
|
+
[code, {'Content-Type' => c_type}, [result]]
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.on_cud(action, content_type, body)
|
36
|
+
params = parse_params(content_type, body)
|
37
|
+
object_id = ""
|
38
|
+
code, error = get_rhosync_resource(params['resource'], action) do |klass|
|
39
|
+
object_id = klass.send("rhosync_receive_#{action}".to_sym,
|
40
|
+
params['partition'], params['attributes'])
|
41
|
+
object_id = object_id.to_s if object_id
|
42
|
+
end
|
43
|
+
[code, {'Content-Type' => "text/plain"}, [error || object_id]]
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.create(content_type, body)
|
47
|
+
self.on_cud(:create, content_type, body)
|
48
|
+
end
|
49
|
+
|
50
|
+
def self.update(content_type, body)
|
51
|
+
self.on_cud(:update, content_type, body)
|
52
|
+
end
|
53
|
+
|
54
|
+
def self.delete(content_type, body)
|
55
|
+
self.on_cud(:delete, content_type, body)
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def self.get_rhosync_resource(resource_name, action)
|
61
|
+
code, error = 200, nil
|
62
|
+
begin
|
63
|
+
klass = Kernel.const_get(resource_name)
|
64
|
+
yield klass
|
65
|
+
rescue NoMethodError => ne
|
66
|
+
error = "error on method `#{action}` for #{resource_name}: #{ne.message}"
|
67
|
+
code = 404
|
68
|
+
rescue NameError
|
69
|
+
error = "Missing Rhosync::Resource #{resource_name}"
|
70
|
+
code = 404
|
71
|
+
# TODO: catch HaltException and Exception here, built-in source adapter will handle them
|
72
|
+
rescue Exception => e
|
73
|
+
error = e.message
|
74
|
+
code = 500
|
75
|
+
end
|
76
|
+
[code, error]
|
77
|
+
end
|
78
|
+
|
79
|
+
def self.parse_params(content_type, params)
|
80
|
+
if content_type and content_type.match(/^application\/json/) and params and params.length > 2
|
81
|
+
JSON.parse(params)
|
82
|
+
else
|
83
|
+
{}
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
# Detect if we're running inside of rails
|
90
|
+
if defined? Rails
|
91
|
+
#if Rails::VERSION::STRING.to_i >= 3
|
92
|
+
class Engine < Rails::Engine; end
|
93
|
+
#end
|
94
|
+
|
95
|
+
module Rhosync
|
96
|
+
class BaseEndpoint
|
97
|
+
def self.call(env)
|
98
|
+
req = Rack::Request.new(env)
|
99
|
+
Rhosync::EndpointHelpers.send(self.to_s.downcase.split("::")[1].to_sym, req.content_type, req.body.read)
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
class Authenticate < BaseEndpoint; end
|
104
|
+
|
105
|
+
class Query < BaseEndpoint; end
|
106
|
+
|
107
|
+
class Create < BaseEndpoint; end
|
108
|
+
|
109
|
+
class Update < BaseEndpoint; end
|
110
|
+
|
111
|
+
class Delete < BaseEndpoint; end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
|
116
|
+
# Detect if we're running inside of sinatra
|
117
|
+
if defined? Sinatra
|
118
|
+
# Defines Sinatra routes
|
119
|
+
# This is automatically registered if you are using
|
120
|
+
# the 'classic' style sinatra application. To use in a
|
121
|
+
# classic application:
|
122
|
+
#
|
123
|
+
# require 'rubygems'
|
124
|
+
# require 'sinatra'
|
125
|
+
# require 'rhoconnect-rb'
|
126
|
+
#
|
127
|
+
# get '/' do
|
128
|
+
# 'hello world'
|
129
|
+
# end
|
130
|
+
#
|
131
|
+
# For modular sinatra applications, you will need to register
|
132
|
+
# the module inside your class. To use in a modular application:
|
133
|
+
#
|
134
|
+
# require 'sinatra/base'
|
135
|
+
# require 'rhoconnect-rb'
|
136
|
+
#
|
137
|
+
# class Myapp < Sinatra::Base
|
138
|
+
# register Sinatra::RhosyncEndpoints
|
139
|
+
# get '/' do
|
140
|
+
# 'hello world'
|
141
|
+
# end
|
142
|
+
# end
|
143
|
+
module Sinatra
|
144
|
+
module RhosyncHelpers
|
145
|
+
def call_helper(method,*args)
|
146
|
+
code, c_type, body = Rhosync::EndpointHelpers.send(method,*args)
|
147
|
+
content_type c_type['Content-Type']
|
148
|
+
status code
|
149
|
+
body[0]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
|
153
|
+
module RhosyncEndpoints
|
154
|
+
def self.registered(app)
|
155
|
+
# install our endpoint helpers
|
156
|
+
app.send(:include, RhosyncHelpers)
|
157
|
+
|
158
|
+
[:authenticate,:query,:create,:update,:delete].each do |endpoint|
|
159
|
+
app.post "/rhosync/#{endpoint}" do
|
160
|
+
call_helper(endpoint, request.env['CONTENT_TYPE'], request.body.read)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
end
|
165
|
+
register RhosyncEndpoints
|
166
|
+
end
|
167
|
+
end
|
@@ -0,0 +1,143 @@
|
|
1
|
+
module Rhosync
|
2
|
+
module Resource
|
3
|
+
|
4
|
+
def self.included(model)
|
5
|
+
model.extend(ClassMethods)
|
6
|
+
model.extend(HelperMethods)
|
7
|
+
|
8
|
+
model.send(:include, InstanceMethods)
|
9
|
+
model.send(:include, Callbacks)
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
def partition(p)
|
15
|
+
@partition = p
|
16
|
+
end
|
17
|
+
|
18
|
+
def get_partition
|
19
|
+
@partition.is_a?(Proc) ? @partition.call : @partition
|
20
|
+
end
|
21
|
+
|
22
|
+
def rhosync_receive_create(partition, attributes)
|
23
|
+
instance = self.send(:new)
|
24
|
+
instance.send(:rhosync_apply_attributes, partition, attributes)
|
25
|
+
instance.skip_rhosync_callbacks = true
|
26
|
+
instance.save
|
27
|
+
instance.id #=> return object id
|
28
|
+
end
|
29
|
+
|
30
|
+
def rhosync_receive_update(partition, attributes)
|
31
|
+
object_id = attributes.delete('id')
|
32
|
+
instance = self.send(is_datamapper? ? :get : :find, object_id)
|
33
|
+
instance.send(:rhosync_apply_attributes, partition, attributes)
|
34
|
+
instance.skip_rhosync_callbacks = true
|
35
|
+
instance.save
|
36
|
+
object_id
|
37
|
+
end
|
38
|
+
|
39
|
+
def rhosync_receive_delete(partition, attributes)
|
40
|
+
object_id = attributes['id']
|
41
|
+
instance = self.send(is_datamapper? ? :get : :find, object_id)
|
42
|
+
instance.skip_rhosync_callbacks = true
|
43
|
+
instance.destroy
|
44
|
+
object_id
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module InstanceMethods
|
49
|
+
attr_accessor :skip_rhosync_callbacks
|
50
|
+
|
51
|
+
def rhosync_create
|
52
|
+
call_client_method(:create)
|
53
|
+
end
|
54
|
+
|
55
|
+
def rhosync_destroy
|
56
|
+
call_client_method(:destroy)
|
57
|
+
end
|
58
|
+
|
59
|
+
def rhosync_update
|
60
|
+
call_client_method(:update)
|
61
|
+
end
|
62
|
+
|
63
|
+
def rhosync_query(partition)
|
64
|
+
#return all objects for this partition
|
65
|
+
end
|
66
|
+
|
67
|
+
# By default we ignore partition
|
68
|
+
# TODO: Document - this is user-facing function
|
69
|
+
def rhosync_apply_attributes(partition, attributes)
|
70
|
+
self.attributes = attributes
|
71
|
+
end
|
72
|
+
|
73
|
+
# Return Rhosync-friendly attributes list
|
74
|
+
def normalized_attributes
|
75
|
+
attribs = self.attributes.dup
|
76
|
+
attribs.each do |key,value|
|
77
|
+
attribs[key] = Time.parse(value.to_s).to_i.to_s if value.is_a?(Time) or value.is_a?(DateTime)
|
78
|
+
end if Rhosync.configuration.sync_time_as_int
|
79
|
+
attribs
|
80
|
+
end
|
81
|
+
|
82
|
+
private
|
83
|
+
|
84
|
+
def call_client_method(action)
|
85
|
+
unless self.skip_rhosync_callbacks
|
86
|
+
attribs = self.normalized_attributes
|
87
|
+
begin
|
88
|
+
Rhosync::Client.new.send(action, self.class.to_s, self.class.get_partition, attribs)
|
89
|
+
rescue RestClient::Exception => re
|
90
|
+
warn "#{self.class.to_s}: rhosync_#{action} returned error: #{re.message} - #{re.http_body}"
|
91
|
+
rescue Exception => e
|
92
|
+
warn "#{self.class.to_s}: rhosync_#{action} returned unexpected error: #{e.message}"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
end
|
98
|
+
|
99
|
+
module Callbacks
|
100
|
+
|
101
|
+
def self.included(model)
|
102
|
+
model.class_eval do
|
103
|
+
install_callbacks
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
module HelperMethods
|
109
|
+
|
110
|
+
def install_callbacks
|
111
|
+
if is_datamapper?
|
112
|
+
# test for dm-serializer
|
113
|
+
if not is_defined?(DataMapper::Serialize)
|
114
|
+
raise "Rhosync::Resource requires dm-serializer to work with DataMapper. Install with `gem install dm-serializer` and add to your application."
|
115
|
+
end
|
116
|
+
after :create, :rhosync_create
|
117
|
+
after :destroy, :rhosync_destroy
|
118
|
+
after :update, :rhosync_update
|
119
|
+
elsif is_activerecord?
|
120
|
+
after_create :rhosync_create
|
121
|
+
after_destroy :rhosync_destroy
|
122
|
+
after_update :rhosync_update
|
123
|
+
else
|
124
|
+
raise "Rhosync::Resource only supports ActiveRecord or DataMapper at this time."
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
private
|
129
|
+
|
130
|
+
def is_defined?(const) # :nodoc:
|
131
|
+
defined?(const)
|
132
|
+
end
|
133
|
+
|
134
|
+
def is_datamapper? # :nodoc:
|
135
|
+
self.included_modules.include?(DataMapper::Resource) rescue false
|
136
|
+
end
|
137
|
+
|
138
|
+
def is_activerecord? # :nodoc:
|
139
|
+
self.superclass == ActiveRecord::Base rescue false
|
140
|
+
end
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require 'rhosync/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "rhoconnect-rb"
|
7
|
+
s.version = Rhosync::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Rhomobile"]
|
10
|
+
s.date = Time.now.strftime('%Y-%m-%d')
|
11
|
+
s.email = ["support@rhomobile.com"]
|
12
|
+
s.homepage = %q{http://rhomobile.com}
|
13
|
+
s.summary = %q{RhoSync rails plugin}
|
14
|
+
s.description = %q{RhoSync rails plugin}
|
15
|
+
|
16
|
+
s.rubyforge_project = nil
|
17
|
+
s.add_dependency('rest-client', '~>1.6.1')
|
18
|
+
s.add_dependency('json', '~>1.4.6')
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
22
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
23
|
+
s.require_paths = ["lib"]
|
24
|
+
|
25
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
26
|
+
|
27
|
+
end
|
data/spec/client_spec.rb
ADDED
@@ -0,0 +1,125 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe Rhosync::Client do
|
4
|
+
|
5
|
+
context "on initialize" do
|
6
|
+
it "should initialize with RHOSYNC_URL environment var" do
|
7
|
+
ENV['RHOSYNC_URL'] = "http://token@test.rhosync.com"
|
8
|
+
c = Rhosync::Client.new
|
9
|
+
c.token.should == 'token'
|
10
|
+
c.uri.should == 'http://test.rhosync.com'
|
11
|
+
ENV.delete('RHOSYNC_URL')
|
12
|
+
end
|
13
|
+
|
14
|
+
it "should initialize with :uri parameter" do
|
15
|
+
c = Rhosync::Client.new(:uri => "http://token@test.rhosync.com")
|
16
|
+
c.token.should == 'token'
|
17
|
+
c.uri.should == 'http://test.rhosync.com'
|
18
|
+
end
|
19
|
+
|
20
|
+
it "should initialize with :token parameter" do
|
21
|
+
c = Rhosync::Client.new(:uri => "http://test.rhosync.com", :token => "token")
|
22
|
+
c.token.should == 'token'
|
23
|
+
c.uri.should == 'http://test.rhosync.com'
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should initialize with configure block" do
|
27
|
+
Rhosync.configure do |config|
|
28
|
+
config.uri = "http://test.rhosync.com"
|
29
|
+
config.token = "token"
|
30
|
+
end
|
31
|
+
begin
|
32
|
+
c = Rhosync::Client.new
|
33
|
+
c.token.should == 'token'
|
34
|
+
c.uri.should == 'http://test.rhosync.com'
|
35
|
+
ensure
|
36
|
+
Rhosync.configure do |config|
|
37
|
+
config.uri = nil
|
38
|
+
config.token = nil
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should raise ArgumentError if uri is missing" do
|
44
|
+
lambda { Rhosync::Client.new }.should raise_error(ArgumentError, "Please provide a :uri or set RHOSYNC_URL")
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should raise ArugmentError if token is missing" do
|
48
|
+
lambda {
|
49
|
+
Rhosync::Client.new(:uri => "http://test.rhosync.com")
|
50
|
+
}.should raise_error(ArgumentError, "Please provide a :token or set it in uri")
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
context "on create update destroy" do
|
55
|
+
before(:each) do
|
56
|
+
@client = Rhosync::Client.new(:uri => "http://token@test.rhosync.com")
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should create an object" do
|
60
|
+
stub_request(:post, "http://test.rhosync.com/api/push_objects").with(
|
61
|
+
:headers => {"Content-Type" => "application/json"}
|
62
|
+
).to_return(:status => 200, :body => "done")
|
63
|
+
resp = @client.create("Person", "user1",
|
64
|
+
{
|
65
|
+
'id' => 1,
|
66
|
+
'name' => 'user1'
|
67
|
+
}
|
68
|
+
)
|
69
|
+
resp.body.should == "done"
|
70
|
+
resp.code.should == 200
|
71
|
+
end
|
72
|
+
|
73
|
+
it "should update an object" do
|
74
|
+
stub_request(:post, "http://test.rhosync.com/api/push_objects").with(
|
75
|
+
:headers => {"Content-Type" => "application/json"}
|
76
|
+
).to_return(:status => 200, :body => "done")
|
77
|
+
resp = @client.update("Person", "user1",
|
78
|
+
{
|
79
|
+
'id' => 1,
|
80
|
+
'name' => 'user1'
|
81
|
+
}
|
82
|
+
)
|
83
|
+
resp.body.should == "done"
|
84
|
+
resp.code.should == 200
|
85
|
+
end
|
86
|
+
|
87
|
+
it "should destroy an object" do
|
88
|
+
stub_request(:post, "http://test.rhosync.com/api/push_deletes").with(
|
89
|
+
:headers => {"Content-Type" => "application/json"}
|
90
|
+
).to_return(:status => 200, :body => "done")
|
91
|
+
resp = @client.destroy("Person", "user1",
|
92
|
+
{
|
93
|
+
'id' => 1,
|
94
|
+
'name' => 'user1'
|
95
|
+
}
|
96
|
+
)
|
97
|
+
resp.body.should == "done"
|
98
|
+
resp.code.should == 200
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
context "on set callbacks" do
|
103
|
+
before(:each) do
|
104
|
+
@client = Rhosync::Client.new(:uri => "http://token@test.rhosync.com")
|
105
|
+
end
|
106
|
+
|
107
|
+
it "should set auth callback" do
|
108
|
+
stub_request(:post, "http://test.rhosync.com/api/set_auth_callback").with(
|
109
|
+
:headers => {"Content-Type" => "application/json"}
|
110
|
+
).to_return(:status => 200, :body => "done")
|
111
|
+
resp = @client.set_auth_callback("http://example.com/callback")
|
112
|
+
resp.body.should == "done"
|
113
|
+
resp.code.should == 200
|
114
|
+
end
|
115
|
+
|
116
|
+
it "should set query callback" do
|
117
|
+
stub_request(:post, "http://test.rhosync.com/api/set_query_callback").with(
|
118
|
+
:headers => {"Content-Type" => "application/json"}
|
119
|
+
).to_return(:status => 200, :body => "done")
|
120
|
+
resp = @client.set_query_callback("Person", "http://example.com/callback")
|
121
|
+
resp.body.should == "done"
|
122
|
+
resp.code.should == 200
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -0,0 +1,234 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe Rhosync::EndpointHelpers do
|
4
|
+
|
5
|
+
# Auth stub class
|
6
|
+
class AuthTest; end
|
7
|
+
class BrokenResource < ActiveRecord::Base
|
8
|
+
include Rhosync::Resource
|
9
|
+
end
|
10
|
+
|
11
|
+
# Query stub class
|
12
|
+
class Product < ActiveRecord::Base
|
13
|
+
include Rhosync::Resource
|
14
|
+
def self.rhosync_query(partition)
|
15
|
+
[self.new]
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def setup_auth_test(success)
|
20
|
+
AuthTest.stub!(:do_auth).and_return(success)
|
21
|
+
AuthTest.should_receive(:do_auth).with(@creds)
|
22
|
+
|
23
|
+
Rhosync.configure do |config|
|
24
|
+
config.uri = "http://test.rhosync.com"
|
25
|
+
config.token = "token"
|
26
|
+
config.authenticate = lambda {|credentials|
|
27
|
+
AuthTest.do_auth(credentials)
|
28
|
+
}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
before(:all) do
|
33
|
+
@params = {'partition' => 'testuser', 'resource' => 'Product'}
|
34
|
+
@creds = {'user' => 'john', 'pass' => 'secret'}
|
35
|
+
end
|
36
|
+
|
37
|
+
context "on Rails auth endpoint" do
|
38
|
+
before(:each) do
|
39
|
+
strio = mock("StringIO")
|
40
|
+
strio.stub!(:read).and_return(JSON.generate(@creds))
|
41
|
+
@env = mock("env")
|
42
|
+
@env.stub!(:body).and_return(strio)
|
43
|
+
@env.stub!(:content_type).and_return('application/json')
|
44
|
+
Rack::Request.stub!(:new).and_return(@env)
|
45
|
+
end
|
46
|
+
|
47
|
+
it "should call configured authenticate block" do
|
48
|
+
setup_auth_test(true)
|
49
|
+
Rhosync::Authenticate.call(@env).should == [
|
50
|
+
200, {'Content-Type' => 'text/plain'}, [""]
|
51
|
+
]
|
52
|
+
end
|
53
|
+
|
54
|
+
it "should call configured authenticate block with 401" do
|
55
|
+
setup_auth_test(false)
|
56
|
+
Rhosync::Authenticate.call(@env).should == [
|
57
|
+
401, {'Content-Type' => 'text/plain'}, [""]
|
58
|
+
]
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should return true if no authenticate block exists" do
|
62
|
+
Rhosync.configure do |config|
|
63
|
+
config.uri = "http://test.rhosync.com"
|
64
|
+
config.token = "token"
|
65
|
+
end
|
66
|
+
Rhosync.configuration.authenticate.should be_nil
|
67
|
+
Rhosync::Authenticate.call(@env).should == [
|
68
|
+
200, {'Content-Type' => 'text/plain'}, [""]
|
69
|
+
]
|
70
|
+
end
|
71
|
+
|
72
|
+
it "should call authenticate block with empty params" do
|
73
|
+
Rhosync::EndpointHelpers.authenticate('text/plain', '').should == [
|
74
|
+
200, {"Content-Type"=>"text/plain"}, [""]
|
75
|
+
]
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
context "on Create/Update/Delete/Query endpoints" do
|
80
|
+
before(:each) do
|
81
|
+
@strio = mock("StringIO")
|
82
|
+
@env = mock("env")
|
83
|
+
@env.stub!(:content_type).and_return('application/json')
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should call query endpoint" do
|
87
|
+
@strio.stub!(:read).and_return(
|
88
|
+
{'partition' => 'testuser', 'resource' => 'Product'}.to_json
|
89
|
+
)
|
90
|
+
@env.stub!(:body).and_return(@strio)
|
91
|
+
Rack::Request.stub!(:new).and_return(@env)
|
92
|
+
code, content_type, body = Rhosync::Query.call(@env)
|
93
|
+
code.should == 200
|
94
|
+
content_type.should == { "Content-Type" => "application/json" }
|
95
|
+
JSON.parse(body[0]).should == { '1' => Product.new.normalized_attributes }
|
96
|
+
end
|
97
|
+
|
98
|
+
it "should fail on missing Rhosync::Resource" do
|
99
|
+
@strio.stub!(:read).and_return(
|
100
|
+
{'partition' => 'testuser', 'resource' => 'Broken'}.to_json
|
101
|
+
)
|
102
|
+
@env.stub!(:body).and_return(@strio)
|
103
|
+
Rack::Request.stub!(:new).and_return(@env)
|
104
|
+
code, content_type, body = Rhosync::Query.call(@env)
|
105
|
+
code.should == 404
|
106
|
+
content_type.should == { "Content-Type" => "text/plain" }
|
107
|
+
body[0].should == "Missing Rhosync::Resource Broken"
|
108
|
+
end
|
109
|
+
|
110
|
+
it "should fail on undefined rhosync_query method" do
|
111
|
+
@strio.stub!(:read).and_return(
|
112
|
+
{'partition' => 'testuser', 'resource' => 'BrokenResource'}.to_json
|
113
|
+
)
|
114
|
+
@env.stub!(:body).and_return(@strio)
|
115
|
+
Rack::Request.stub!(:new).and_return(@env)
|
116
|
+
code, content_type, body = Rhosync::Query.call(@env)
|
117
|
+
code.should == 404
|
118
|
+
content_type.should == { "Content-Type" => "text/plain" }
|
119
|
+
body[0].should == "error on method `rhosync_query` for BrokenResource: undefined method `rhosync_query' for BrokenResource:Class"
|
120
|
+
end
|
121
|
+
|
122
|
+
it "should fail on unknown exception" do
|
123
|
+
@strio.stub!(:read).and_return(
|
124
|
+
{'partition' => 'testuser', 'resource' => 'Product'}.to_json
|
125
|
+
)
|
126
|
+
@env.stub!(:body).and_return(@strio)
|
127
|
+
Rack::Request.stub!(:new).and_return(@env)
|
128
|
+
Product.stub!(:rhosync_receive_create).and_return { raise "error in create" }
|
129
|
+
code, content_type, body = Rhosync::Create.call(@env)
|
130
|
+
code.should == 500
|
131
|
+
content_type.should == { "Content-Type" => "text/plain" }
|
132
|
+
body[0].should == "error in create"
|
133
|
+
end
|
134
|
+
|
135
|
+
it "should call create endpoint" do
|
136
|
+
params = {
|
137
|
+
'resource' => 'Product',
|
138
|
+
'partition' => 'app',
|
139
|
+
'attributes' => {
|
140
|
+
'name' => 'iphone',
|
141
|
+
'brand' => 'apple'
|
142
|
+
}
|
143
|
+
}
|
144
|
+
@strio.stub!(:read).and_return(params.to_json)
|
145
|
+
@env.stub!(:body).and_return(@strio)
|
146
|
+
Rack::Request.stub!(:new).and_return(@env)
|
147
|
+
code, content_type, body = Rhosync::Create.call(@env)
|
148
|
+
code.should == 200
|
149
|
+
content_type.should == { "Content-Type" => "text/plain" }
|
150
|
+
body.should == ['1']
|
151
|
+
end
|
152
|
+
|
153
|
+
it "should call update endpoint" do
|
154
|
+
params = {
|
155
|
+
'resource' => 'Product',
|
156
|
+
'partition' => 'app',
|
157
|
+
'attributes' => {
|
158
|
+
'id' => '123',
|
159
|
+
'name' => 'iphone',
|
160
|
+
'brand' => 'apple'
|
161
|
+
}
|
162
|
+
}
|
163
|
+
@strio.stub!(:read).and_return(params.to_json)
|
164
|
+
@env.stub!(:body).and_return(@strio)
|
165
|
+
Rack::Request.stub!(:new).and_return(@env)
|
166
|
+
code, content_type, body = Rhosync::Update.call(@env)
|
167
|
+
code.should == 200
|
168
|
+
content_type.should == { "Content-Type" => "text/plain" }
|
169
|
+
body.should == ["123"]
|
170
|
+
end
|
171
|
+
|
172
|
+
it "should call delete endpoint" do
|
173
|
+
params = {
|
174
|
+
'resource' => 'Product',
|
175
|
+
'partition' => 'app',
|
176
|
+
'attributes' => {
|
177
|
+
'id' => '123',
|
178
|
+
'name' => 'iphone',
|
179
|
+
'brand' => 'apple'
|
180
|
+
}
|
181
|
+
}
|
182
|
+
@strio.stub!(:read).and_return(params.to_json)
|
183
|
+
@env.stub!(:body).and_return(@strio)
|
184
|
+
Rack::Request.stub!(:new).and_return(@env)
|
185
|
+
code, content_type, body = Rhosync::Delete.call(@env)
|
186
|
+
code.should == 200
|
187
|
+
content_type.should == { "Content-Type" => "text/plain" }
|
188
|
+
body.should == ["123"]
|
189
|
+
end
|
190
|
+
|
191
|
+
end
|
192
|
+
|
193
|
+
context "on Sinatra endpoints" do
|
194
|
+
class EndpointTest
|
195
|
+
include Sinatra::RhosyncHelpers
|
196
|
+
end
|
197
|
+
|
198
|
+
it "should register endpoints for authenticate and query" do
|
199
|
+
strio = mock("StringIO")
|
200
|
+
strio.stub!(:read).and_return(@creds.to_json)
|
201
|
+
req = mock("request")
|
202
|
+
req.stub!(:body).and_return(strio)
|
203
|
+
req.stub!(:env).and_return('CONTENT_TYPE' => 'application/json')
|
204
|
+
Sinatra::RhosyncEndpoints.stub!(:request).and_return(req)
|
205
|
+
Sinatra::RhosyncEndpoints.stub!(:params).and_return(@params)
|
206
|
+
Rhosync::EndpointHelpers.stub!(:query)
|
207
|
+
app = mock("app")
|
208
|
+
app.stub!(:post).and_yield
|
209
|
+
app.should_receive(:post).exactly(5).times
|
210
|
+
app.should_receive(:include).with(Sinatra::RhosyncHelpers)
|
211
|
+
Sinatra::RhosyncEndpoints.should_receive(:call_helper).exactly(5).times
|
212
|
+
Sinatra::RhosyncEndpoints.registered(app)
|
213
|
+
end
|
214
|
+
|
215
|
+
it "should call helper for authenticate" do
|
216
|
+
app = EndpointTest.new
|
217
|
+
app.should_receive(:status).with(200)
|
218
|
+
app.should_receive(:content_type).with('text/plain')
|
219
|
+
app.call_helper(
|
220
|
+
:authenticate, 'application/json', @creds.to_json
|
221
|
+
).should == ""
|
222
|
+
end
|
223
|
+
|
224
|
+
it "should call helper for query" do
|
225
|
+
app = EndpointTest.new
|
226
|
+
app.should_receive(:status).with(200)
|
227
|
+
app.should_receive(:content_type).with('application/json')
|
228
|
+
result = app.call_helper(
|
229
|
+
:query, 'application/json', @params.to_json
|
230
|
+
)
|
231
|
+
JSON.parse(result).should == { '1' => Product.new.normalized_attributes }
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
require File.join(File.dirname(__FILE__), 'spec_helper')
|
2
|
+
|
3
|
+
describe Rhosync::Resource do
|
4
|
+
|
5
|
+
context "on set partition" do
|
6
|
+
it "should set resource partition to :app" do
|
7
|
+
class TestModel1 < ActiveRecord::Base
|
8
|
+
include Rhosync::Resource
|
9
|
+
|
10
|
+
partition :app
|
11
|
+
end
|
12
|
+
|
13
|
+
TestModel1.get_partition.should == :app
|
14
|
+
end
|
15
|
+
|
16
|
+
it "should set resource partition with lambda" do
|
17
|
+
class TestModel2 < ActiveRecord::Base
|
18
|
+
include Rhosync::Resource
|
19
|
+
|
20
|
+
partition lambda{ 'helloworld' }
|
21
|
+
end
|
22
|
+
|
23
|
+
TestModel2.get_partition.should == 'helloworld'
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
context "on initialize" do
|
28
|
+
it "should raise exception if DataMapper or ActiveRecord::Base are missing" do
|
29
|
+
lambda { class TestModel3
|
30
|
+
include Rhosync::Resource
|
31
|
+
end
|
32
|
+
}.should raise_error("Rhosync::Resource only supports ActiveRecord or DataMapper at this time.")
|
33
|
+
end
|
34
|
+
|
35
|
+
it "should register callbacks for ActiveRecord::Base" do
|
36
|
+
class TestModel4 < ActiveRecord::Base
|
37
|
+
include Rhosync::Resource
|
38
|
+
end
|
39
|
+
|
40
|
+
TestModel4.create_callback.should == :rhosync_create
|
41
|
+
TestModel4.destroy_callback.should == :rhosync_destroy
|
42
|
+
TestModel4.update_callback.should == :rhosync_update
|
43
|
+
end
|
44
|
+
|
45
|
+
it "should register callbacks for DataMapper::Resource" do
|
46
|
+
class TestModel5
|
47
|
+
include DataMapper::Resource
|
48
|
+
include Rhosync::Resource
|
49
|
+
end
|
50
|
+
|
51
|
+
TestModel5.rhosync_callbacks[:create].should == :rhosync_create
|
52
|
+
TestModel5.rhosync_callbacks[:destroy].should == :rhosync_destroy
|
53
|
+
TestModel5.rhosync_callbacks[:update].should == :rhosync_update
|
54
|
+
end
|
55
|
+
|
56
|
+
it "should raise exception if dm-serializer is missing" do
|
57
|
+
class TestModel6
|
58
|
+
include DataMapper::Resource
|
59
|
+
include Rhosync::Resource
|
60
|
+
end
|
61
|
+
TestModel6.stub!(:is_defined?).and_return(false)
|
62
|
+
lambda {
|
63
|
+
TestModel6.install_callbacks
|
64
|
+
}.should raise_error("Rhosync::Resource requires dm-serializer to work with DataMapper. Install with `gem install dm-serializer` and add to your application.")
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
context "on create update delete" do
|
69
|
+
|
70
|
+
it "should call create update delete hook" do
|
71
|
+
class TestModel7 < ActiveRecord::Base
|
72
|
+
include Rhosync::Resource
|
73
|
+
partition :app
|
74
|
+
end
|
75
|
+
client = mock('Rhosync::Client')
|
76
|
+
client.stub!(:send)
|
77
|
+
Rhosync::Client.stub!(:new).and_return(client)
|
78
|
+
[:create, :update, :destroy].each do |action|
|
79
|
+
client.should_receive(:send).with(
|
80
|
+
action, "TestModel7", :app, {"name"=>"John", "created_at"=>"1299636666", "updated_at"=>"1299636666", "id"=>1}
|
81
|
+
)
|
82
|
+
TestModel7.new.send("rhosync_#{action}".to_sym)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should warn on RestClient::Exception" do
|
87
|
+
class TestModel8 < ActiveRecord::Base
|
88
|
+
include Rhosync::Resource
|
89
|
+
partition :app
|
90
|
+
end
|
91
|
+
client = mock('Rhosync::Client')
|
92
|
+
exception = RestClient::Exception.new(
|
93
|
+
RestClient::Response.create("error connecting to server", nil, nil), 500
|
94
|
+
)
|
95
|
+
exception.message = "Internal Server Error"
|
96
|
+
client.stub!(:send).and_return { raise exception }
|
97
|
+
Rhosync::Client.stub!(:new).and_return(client)
|
98
|
+
tm = TestModel8.new
|
99
|
+
tm.should_receive(:warn).with(
|
100
|
+
"TestModel8: rhosync_create returned error: Internal Server Error - error connecting to server"
|
101
|
+
)
|
102
|
+
tm.rhosync_create
|
103
|
+
end
|
104
|
+
|
105
|
+
it "should warn on Exception" do
|
106
|
+
class TestModel8 < ActiveRecord::Base
|
107
|
+
include Rhosync::Resource
|
108
|
+
partition :app
|
109
|
+
end
|
110
|
+
client = mock('Rhosync::Client')
|
111
|
+
client.stub!(:send).and_return { raise Exception.new("error connecting to server") }
|
112
|
+
Rhosync::Client.stub!(:new).and_return(client)
|
113
|
+
tm = TestModel8.new
|
114
|
+
tm.should_receive(:warn).with("TestModel8: rhosync_create returned unexpected error: error connecting to server")
|
115
|
+
tm.rhosync_create
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,103 @@
|
|
1
|
+
$:.unshift File.expand_path(File.join(File.dirname(__FILE__), '..', 'lib'))
|
2
|
+
require 'rspec'
|
3
|
+
require 'webmock/rspec'
|
4
|
+
|
5
|
+
include WebMock::API
|
6
|
+
|
7
|
+
# stub for rack
|
8
|
+
module Rack
|
9
|
+
class Request; end
|
10
|
+
end
|
11
|
+
|
12
|
+
# stubs for rails engine
|
13
|
+
module Rails
|
14
|
+
class Engine; end
|
15
|
+
end
|
16
|
+
|
17
|
+
# stubs for sinatra
|
18
|
+
module Sinatra
|
19
|
+
def self.register(mod); end
|
20
|
+
module RhosyncEndpoints
|
21
|
+
def self.content_type(c_type); end
|
22
|
+
def self.status(code); end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
require 'rhoconnect-rb'
|
27
|
+
|
28
|
+
|
29
|
+
# define ActiveRecord and DM here for testing
|
30
|
+
module ActiveRecord
|
31
|
+
class Base
|
32
|
+
|
33
|
+
def attributes
|
34
|
+
{
|
35
|
+
"name" => "John",
|
36
|
+
"created_at" => Time.parse("Wed Mar 09 02:11:06 UTC 2011"),
|
37
|
+
"updated_at" => Time.parse("Wed Mar 09 02:11:06 UTC 2011"),
|
38
|
+
"id" => 1
|
39
|
+
}
|
40
|
+
end
|
41
|
+
|
42
|
+
def attributes=(attribs); end
|
43
|
+
|
44
|
+
def id; 1 end
|
45
|
+
|
46
|
+
def warn(*args)
|
47
|
+
Kernel.warn(args)
|
48
|
+
end
|
49
|
+
|
50
|
+
def save; end
|
51
|
+
|
52
|
+
def self.find(object_id)
|
53
|
+
self.new
|
54
|
+
end
|
55
|
+
|
56
|
+
def destroy; end
|
57
|
+
|
58
|
+
class << self
|
59
|
+
attr_accessor :create_callback,:destroy_callback,:update_callback
|
60
|
+
|
61
|
+
def after_create(callback)
|
62
|
+
@create_callback = callback
|
63
|
+
end
|
64
|
+
|
65
|
+
def after_destroy(callback)
|
66
|
+
@destroy_callback = callback
|
67
|
+
end
|
68
|
+
|
69
|
+
def after_update(callback)
|
70
|
+
@update_callback = callback
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
module DataMapper
|
77
|
+
module Resource
|
78
|
+
|
79
|
+
def attributes
|
80
|
+
{
|
81
|
+
:created_at => DateTime.parse("Wed Mar 09 02:11:06 UTC 2011"),
|
82
|
+
:updated_at => DateTime.parse("Wed Mar 09 02:11:06 UTC 2011"),
|
83
|
+
:name => "John",
|
84
|
+
:id => 1
|
85
|
+
}
|
86
|
+
end
|
87
|
+
|
88
|
+
def self.included(model)
|
89
|
+
model.extend(ClassMethods)
|
90
|
+
end
|
91
|
+
|
92
|
+
module ClassMethods
|
93
|
+
attr_accessor :rhosync_callbacks
|
94
|
+
|
95
|
+
def after(action, callback)
|
96
|
+
@rhosync_callbacks ||= {}
|
97
|
+
@rhosync_callbacks[action] = callback
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
module Serialize; end
|
103
|
+
end
|
metadata
ADDED
@@ -0,0 +1,118 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rhoconnect-rb
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 27
|
5
|
+
prerelease:
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 1
|
9
|
+
- 0
|
10
|
+
version: 0.1.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Rhomobile
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2011-06-08 00:00:00 Z
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
type: :runtime
|
22
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
23
|
+
none: false
|
24
|
+
requirements:
|
25
|
+
- - ~>
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
hash: 13
|
28
|
+
segments:
|
29
|
+
- 1
|
30
|
+
- 6
|
31
|
+
- 1
|
32
|
+
version: 1.6.1
|
33
|
+
version_requirements: *id001
|
34
|
+
name: rest-client
|
35
|
+
prerelease: false
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
type: :runtime
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ~>
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
hash: 11
|
44
|
+
segments:
|
45
|
+
- 1
|
46
|
+
- 4
|
47
|
+
- 6
|
48
|
+
version: 1.4.6
|
49
|
+
version_requirements: *id002
|
50
|
+
name: json
|
51
|
+
prerelease: false
|
52
|
+
description: RhoSync rails plugin
|
53
|
+
email:
|
54
|
+
- support@rhomobile.com
|
55
|
+
executables: []
|
56
|
+
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
extra_rdoc_files: []
|
60
|
+
|
61
|
+
files:
|
62
|
+
- .autotest
|
63
|
+
- .gitignore
|
64
|
+
- .rspec
|
65
|
+
- Gemfile
|
66
|
+
- README.md
|
67
|
+
- Rakefile
|
68
|
+
- config/routes.rb
|
69
|
+
- init.rb
|
70
|
+
- lib/rhoconnect-rb.rb
|
71
|
+
- lib/rhosync/client.rb
|
72
|
+
- lib/rhosync/configuration.rb
|
73
|
+
- lib/rhosync/endpoints.rb
|
74
|
+
- lib/rhosync/resource.rb
|
75
|
+
- lib/rhosync/version.rb
|
76
|
+
- rhoconnect-rb.gemspec
|
77
|
+
- spec/client_spec.rb
|
78
|
+
- spec/endpoints_spec.rb
|
79
|
+
- spec/resource_spec.rb
|
80
|
+
- spec/spec_helper.rb
|
81
|
+
homepage: http://rhomobile.com
|
82
|
+
licenses: []
|
83
|
+
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options: []
|
86
|
+
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
hash: 3
|
95
|
+
segments:
|
96
|
+
- 0
|
97
|
+
version: "0"
|
98
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
99
|
+
none: false
|
100
|
+
requirements:
|
101
|
+
- - ">="
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
hash: 3
|
104
|
+
segments:
|
105
|
+
- 0
|
106
|
+
version: "0"
|
107
|
+
requirements: []
|
108
|
+
|
109
|
+
rubyforge_project:
|
110
|
+
rubygems_version: 1.8.5
|
111
|
+
signing_key:
|
112
|
+
specification_version: 3
|
113
|
+
summary: RhoSync rails plugin
|
114
|
+
test_files:
|
115
|
+
- spec/client_spec.rb
|
116
|
+
- spec/endpoints_spec.rb
|
117
|
+
- spec/resource_spec.rb
|
118
|
+
- spec/spec_helper.rb
|