rhoconnect-rb 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|