meteor-motion 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.
- checksums.yaml +7 -0
- data/.gitignore +21 -0
- data/.repl_history +0 -0
- data/Gemfile +6 -0
- data/LICENSE.txt +22 -0
- data/README.md +166 -0
- data/Rakefile +17 -0
- data/app/app_delegate.rb +11 -0
- data/app/controllers/book_controller.rb +92 -0
- data/app/controllers/book_list_controller.rb +105 -0
- data/app/controllers/connection_controller.rb +83 -0
- data/app/controllers/login_controller.rb +35 -0
- data/lib/meteor-motion.rb +12 -0
- data/lib/meteor-motion/version.rb +3 -0
- data/meteor-motion.gemspec +27 -0
- data/motion/adapters/motion_model.rb +61 -0
- data/motion/client.rb +179 -0
- data/motion/collection.rb +50 -0
- data/motion/collections/default.rb +56 -0
- data/motion/collections/motion_model.rb +52 -0
- data/motion/ddp.rb +161 -0
- data/motion/srp/securerandom.rb +248 -0
- data/motion/srp/srp.rb +250 -0
- data/spec/adapters/motion_model_spec.rb +38 -0
- data/spec/client_spec.rb +104 -0
- data/spec/collection_spec.rb +63 -0
- data/spec/collections/default_spec.rb +46 -0
- data/spec/collections/motion_model_spec.rb +69 -0
- data/spec/ddp_spec.rb +123 -0
- data/spec/server/.meteor/.gitignore +1 -0
- data/spec/server/.meteor/packages +9 -0
- data/spec/server/.meteor/release +1 -0
- data/spec/server/collections/books.js +11 -0
- data/spec/server/server/fixtures.js +28 -0
- data/spec/server/server/publications.js +3 -0
- data/spec/server/smart.json +3 -0
- data/vendor/SocketRocket/NSData+SRB64Additions.h +24 -0
- data/vendor/SocketRocket/NSData+SRB64Additions.m +39 -0
- data/vendor/SocketRocket/SRWebSocket.h +114 -0
- data/vendor/SocketRocket/SRWebSocket.m +1757 -0
- data/vendor/SocketRocket/SocketRocket-Prefix.pch +27 -0
- data/vendor/SocketRocket/SocketRocket.bridgesupport +160 -0
- data/vendor/SocketRocket/base64.c +314 -0
- data/vendor/SocketRocket/base64.h +34 -0
- metadata +190 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a15bd2d5261b5c3cd00cf2892d0cb31e4fb3c72f
|
4
|
+
data.tar.gz: cb93ae359474741e4d91781d56b353a762345e99
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 843330aa37a6cd2c95e903258bb199a75cdd5e75b97d344af3a89ac1c26dae39a5e2a9f98b25d2dd212458fc8de86fb105561065fceaf9d3f95ce1dc67aaaad2
|
7
|
+
data.tar.gz: 4d9c4a63b4ecb45c8456348a87e1a6154192ede3437209576a8101cda88c9fd4f0df270125558254854e5d90ea7097035ebc92a902e82f1ece68c3a3897447ac
|
data/.gitignore
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
*.gem
|
2
|
+
*.rbc
|
3
|
+
.bundle
|
4
|
+
.config
|
5
|
+
.yardoc
|
6
|
+
Gemfile.lock
|
7
|
+
InstalledFiles
|
8
|
+
_yardoc
|
9
|
+
coverage
|
10
|
+
doc/
|
11
|
+
lib/bundler/man
|
12
|
+
pkg
|
13
|
+
rdoc
|
14
|
+
spec/reports
|
15
|
+
test/tmp
|
16
|
+
test/version_tmp
|
17
|
+
tmp
|
18
|
+
build
|
19
|
+
private-docs
|
20
|
+
vendor/**/build*
|
21
|
+
**/.DS_Store
|
data/.repl_history
ADDED
File without changes
|
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2013 TODO: Write your name
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
# MeteorMotion
|
2
|
+
|
3
|
+
Rubymotion Wrapper for communication with Meteor apps via DDP, with SRP authentication capabilities.
|
4
|
+
|
5
|
+
## Installation
|
6
|
+
|
7
|
+
If using Bundler, add to your application's Gemfile:
|
8
|
+
|
9
|
+
gem 'meteor-motion'
|
10
|
+
|
11
|
+
And then execute:
|
12
|
+
|
13
|
+
$ bundle
|
14
|
+
|
15
|
+
Or install it yourself as:
|
16
|
+
|
17
|
+
$ gem install meteor-motion
|
18
|
+
|
19
|
+
And in your application's Rakefile add:
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
require 'meteor-motion'
|
23
|
+
```
|
24
|
+
|
25
|
+
## Usage
|
26
|
+
|
27
|
+
### Initialization
|
28
|
+
|
29
|
+
Create a new MeteorMotion client and connect to your Meteor app:
|
30
|
+
|
31
|
+
```ruby
|
32
|
+
client = MeteorMotion::Client.new
|
33
|
+
client.connect('localhost', 3000, self.method(:on_connect) )
|
34
|
+
|
35
|
+
def on_connect status
|
36
|
+
# Handler for the connection attempt - optional
|
37
|
+
# status - either true or false
|
38
|
+
end
|
39
|
+
|
40
|
+
def error_handler code, reason, details
|
41
|
+
# Handler for general connection errors, malformed messages and failed subscriptions
|
42
|
+
end
|
43
|
+
|
44
|
+
client.on_error( self.method(:error_handler) )
|
45
|
+
```
|
46
|
+
|
47
|
+
By default, it will expand the hostname to ```http://hostname:port/websocket``` per the current Meteor specifications.
|
48
|
+
|
49
|
+
### Collections and subscriptions
|
50
|
+
|
51
|
+
In order to receive data, you need to first create a local collection to handle the data. You should add an observer to this collection, that will be called whenever the data in the collection is changed.
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
def collection_handler action, id
|
55
|
+
# action - will be one of [:added, :changed, :removed]
|
56
|
+
# id - the id of the element of the collection affected
|
57
|
+
end
|
58
|
+
|
59
|
+
collection = client.add_collection('collection_name')
|
60
|
+
collection.add_observer( self.method(:collection_handler) )
|
61
|
+
```
|
62
|
+
|
63
|
+
To remove an observer, simply call ```collection.remove_observer( method )```. With a collection setup, you can subscribe/unsubscribe to data published on the server with:
|
64
|
+
|
65
|
+
```ruby
|
66
|
+
client.subscribe('subscription_name', params)
|
67
|
+
client.unsubscribe('subscription_name')
|
68
|
+
```
|
69
|
+
|
70
|
+
When there is data available, you can access objects on the collection:
|
71
|
+
|
72
|
+
```ruby
|
73
|
+
object = collection.find(id)
|
74
|
+
att1 = object['att1']
|
75
|
+
att2 = object['att2']
|
76
|
+
```
|
77
|
+
|
78
|
+
### Model Adapters
|
79
|
+
Meteor-Motion supports, for the time being, and adapter to be used with the [MotionModel gem](https://github.com/sxross/MotionModel). After installing it, simply define your model classes as such:
|
80
|
+
|
81
|
+
```ruby
|
82
|
+
class Objects
|
83
|
+
include MotionModel::Model
|
84
|
+
include MotionModel::ArrayModelAdapter
|
85
|
+
include MeteorMotion::Adapters::MotionModel
|
86
|
+
|
87
|
+
columns :id, :string
|
88
|
+
#...
|
89
|
+
end
|
90
|
+
|
91
|
+
client.add_collection(Objects, name='objects')
|
92
|
+
|
93
|
+
```
|
94
|
+
|
95
|
+
The ```id```column is mandatory, so that MeteorMotion does not auto-generate and id column with an integer type, which is incompatible with Meteor standard. Also, take care that if you ommit the ```name``` parameter when adding the collection, the collection name will default to the downcased name of your class. After this setup, enjoy MotionModel as usual.
|
96
|
+
|
97
|
+
### Method calls
|
98
|
+
|
99
|
+
```ruby
|
100
|
+
def method_callback action, result
|
101
|
+
# action - one of the following [:result, :updated, :error]
|
102
|
+
# result - either the return value of the method (when action == :result)
|
103
|
+
# or error details (when action == :error)
|
104
|
+
end
|
105
|
+
|
106
|
+
client.call('some_method_name', self.method(:method_callback), params)
|
107
|
+
```
|
108
|
+
|
109
|
+
### Authentication
|
110
|
+
|
111
|
+
Right now, MeteorMotion provides support for authentication using Meteor's built-in SRP. To authenticate with a username/password combination use the following:
|
112
|
+
|
113
|
+
```ruby
|
114
|
+
def login_handler action, details
|
115
|
+
# action - either :success or :error
|
116
|
+
# details - nil on :success, Hash with error details on :error
|
117
|
+
end
|
118
|
+
|
119
|
+
client.login_with_username('username', 'password', self.method(:login_handler))
|
120
|
+
```
|
121
|
+
|
122
|
+
## Testing
|
123
|
+
|
124
|
+
To run tests first start-up the included sample Meteor app:
|
125
|
+
|
126
|
+
```bash
|
127
|
+
cd spec/server
|
128
|
+
meteor run
|
129
|
+
```
|
130
|
+
|
131
|
+
Then run the tests with rake:
|
132
|
+
|
133
|
+
```bash
|
134
|
+
rake spec
|
135
|
+
```
|
136
|
+
|
137
|
+
### Example app
|
138
|
+
|
139
|
+
You can find a working example in the ```app``` directory. Remember to run the meteor server prior to starting the application. To login, use the credentials ```user/pass```. To run the application in the simulator simply run:
|
140
|
+
|
141
|
+
```bash
|
142
|
+
rake
|
143
|
+
```
|
144
|
+
|
145
|
+
## Credits
|
146
|
+
### Developed by
|
147
|
+
<a href="http://whitesmith.co/">
|
148
|
+
<img src="http://www.whitesmith.co/assets/logo-whitesmith-4109176a79f86b9ca4b8022d6dcab3bb.png" alt="http://whitesmith.co/" height=100px />
|
149
|
+
</a>
|
150
|
+
|
151
|
+
Lead Developer: [mtavaresOS](https://github.com/mtavaresOS)
|
152
|
+
|
153
|
+
### Sponsored by
|
154
|
+
<a href="http://www.revokom.com/">
|
155
|
+
<img src="http://www.revokom.com/img/logo_banner.png" alt="http://www.revokom.com/"/>
|
156
|
+
</a>
|
157
|
+
|
158
|
+
|
159
|
+
|
160
|
+
## Contributing
|
161
|
+
|
162
|
+
1. Fork it
|
163
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
164
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
165
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
166
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
#!/usr/bin/env rake
|
2
|
+
$:.unshift("/Library/RubyMotion/lib")
|
3
|
+
require 'motion/project/template/ios'
|
4
|
+
require 'bundler/gem_tasks'
|
5
|
+
require 'bubble-wrap/test'
|
6
|
+
require 'bubble-wrap/core'
|
7
|
+
require 'motion_model'
|
8
|
+
require 'motion-redgreen'
|
9
|
+
Bundler.setup
|
10
|
+
Bundler.require
|
11
|
+
|
12
|
+
Motion::Project::App.setup do |app|
|
13
|
+
# Use `rake config' to see complete project settings.
|
14
|
+
app.name = 'MeteorMotion Example'
|
15
|
+
app.delegate_class = "AppDelegate"
|
16
|
+
app.redgreen_style = :full
|
17
|
+
end
|
data/app/app_delegate.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
class AppDelegate
|
2
|
+
def application(application, didFinishLaunchingWithOptions:launchOptions)
|
3
|
+
@window = UIWindow.alloc.initWithFrame(UIScreen.mainScreen.bounds)
|
4
|
+
@window.makeKeyAndVisible
|
5
|
+
|
6
|
+
@window.rootViewController = ConnectionController.alloc.initWithNibName(nil, bundle: nil)
|
7
|
+
|
8
|
+
true
|
9
|
+
end
|
10
|
+
|
11
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
class BookController < Formotion::FormController
|
2
|
+
attr_accessor :meteor, :_id
|
3
|
+
|
4
|
+
def viewDidLoad
|
5
|
+
super
|
6
|
+
self.title = "Details"
|
7
|
+
end
|
8
|
+
|
9
|
+
def viewWillAppear animated
|
10
|
+
super
|
11
|
+
@books = @meteor.collections['books']
|
12
|
+
@books.add_observer( self.method(:update_form) )
|
13
|
+
|
14
|
+
self.form.on_submit do |form|
|
15
|
+
data = form.render
|
16
|
+
self.view.endEditing(true)
|
17
|
+
|
18
|
+
if @_id != nil
|
19
|
+
@meteor.call('/books/update', self.method(:handle_update), [{_id: @_id},{title: data[:title], author: data[:author],year: data[:year]}])
|
20
|
+
else
|
21
|
+
new_id = SecureRandom.hex
|
22
|
+
@meteor.call('/books/insert', self.method(:handle_update), [{_id: new_id,title: data[:title], author: data[:author],year: data[:year]}])
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
if @_id != nil
|
28
|
+
right_button = UIBarButtonItem.alloc.initWithTitle("Remove", style: UIBarButtonItemStyleDone, target:self, action:'remove')
|
29
|
+
self.navigationItem.rightBarButtonItem = right_button
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def viewWillDisappear animated
|
34
|
+
super
|
35
|
+
@books.remove_observer( self.method(:update_form) )
|
36
|
+
end
|
37
|
+
|
38
|
+
|
39
|
+
def update_form action, id
|
40
|
+
if id == @_id
|
41
|
+
if action == :removed
|
42
|
+
self.navigationController.popViewControllerAnimated(true)
|
43
|
+
elsif action == :changed
|
44
|
+
book = @books.find(id)
|
45
|
+
self.form.row(:title).value = book['title']
|
46
|
+
self.form.row(:author).value = book['author']
|
47
|
+
self.form.row(:year).value = book['year']
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
51
|
+
|
52
|
+
def handle_update action, result
|
53
|
+
if action == :error
|
54
|
+
alert = UIAlertView.new
|
55
|
+
alert.message = "Error: #{result['reason'].to_s}"
|
56
|
+
alert.addButtonWithTitle "OK"
|
57
|
+
alert.show
|
58
|
+
elsif action == :result
|
59
|
+
alert = UIAlertView.new
|
60
|
+
if @_id != nil
|
61
|
+
alert.message = "Book updated sucessfully"
|
62
|
+
alert.addButtonWithTitle "OK"
|
63
|
+
alert.show
|
64
|
+
else
|
65
|
+
alert.message = "Book added sucessfully"
|
66
|
+
alert.addButtonWithTitle "OK"
|
67
|
+
alert.show
|
68
|
+
self.navigationController.popViewControllerAnimated(true)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def handle_remove action, result
|
74
|
+
if action == :error
|
75
|
+
alert = UIAlertView.new
|
76
|
+
alert.message = "Error: #{result['reason'].to_s}"
|
77
|
+
alert.addButtonWithTitle "OK"
|
78
|
+
alert.show
|
79
|
+
elsif action == :result
|
80
|
+
alert = UIAlertView.new
|
81
|
+
alert.message = "Book removed sucessfully"
|
82
|
+
alert.addButtonWithTitle "OK"
|
83
|
+
alert.show
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
|
88
|
+
def remove
|
89
|
+
@meteor.call('/books/remove', self.method(:handle_remove), [{_id: @_id}])
|
90
|
+
end
|
91
|
+
|
92
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
class BookListController < UIViewController
|
2
|
+
attr_accessor :meteor
|
3
|
+
|
4
|
+
def viewDidLoad
|
5
|
+
super
|
6
|
+
self.view.backgroundColor = UIColor.whiteColor
|
7
|
+
self.title = "Books"
|
8
|
+
|
9
|
+
@table = UITableView.alloc.initWithFrame(self.view.bounds)
|
10
|
+
self.view.addSubview @table
|
11
|
+
|
12
|
+
@table.dataSource = self
|
13
|
+
@table.delegate = self
|
14
|
+
|
15
|
+
@books = @meteor.collections['books']
|
16
|
+
@books.add_observer(@table.method(:reloadData))
|
17
|
+
|
18
|
+
right_button = UIBarButtonItem.alloc.initWithTitle("Add Book", style: UIBarButtonItemStyleDone, target:self, action:'add_book')
|
19
|
+
self.navigationItem.rightBarButtonItem = right_button
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_book
|
23
|
+
book = {}
|
24
|
+
form = build_book_form( book )
|
25
|
+
|
26
|
+
bookController = BookController.alloc.initWithForm(form)
|
27
|
+
bookController.meteor = @meteor
|
28
|
+
bookController._id = nil
|
29
|
+
|
30
|
+
self.navigationController.pushViewController(bookController, animated: true)
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def tableView(tableView, cellForRowAtIndexPath: indexPath)
|
35
|
+
@reuseIdentifier ||= "CELL_IDENTIFIER"
|
36
|
+
|
37
|
+
cell = tableView.dequeueReusableCellWithIdentifier(@reuseIdentifier) || begin
|
38
|
+
UITableViewCell.alloc.initWithStyle(UITableViewCellStyleDefault, reuseIdentifier:@reuseIdentifier)
|
39
|
+
end
|
40
|
+
|
41
|
+
cell.textLabel.text = @books.all[indexPath.row]['title']
|
42
|
+
|
43
|
+
cell
|
44
|
+
end
|
45
|
+
|
46
|
+
|
47
|
+
def tableView(tableView, numberOfRowsInSection: section)
|
48
|
+
@books.size
|
49
|
+
end
|
50
|
+
|
51
|
+
|
52
|
+
def tableView(tableView, didSelectRowAtIndexPath:indexPath)
|
53
|
+
tableView.deselectRowAtIndexPath(indexPath, animated: true)
|
54
|
+
|
55
|
+
book = @books.all[indexPath.row]
|
56
|
+
form = build_book_form( book )
|
57
|
+
|
58
|
+
bookController = BookController.alloc.initWithForm(form)
|
59
|
+
bookController.meteor = @meteor
|
60
|
+
bookController._id = book[:_id]
|
61
|
+
|
62
|
+
self.navigationController.pushViewController(bookController, animated: true)
|
63
|
+
end
|
64
|
+
|
65
|
+
|
66
|
+
def build_book_form book
|
67
|
+
form = Formotion::Form.new
|
68
|
+
|
69
|
+
form.build_section do |section|
|
70
|
+
section.title = "Book Details"
|
71
|
+
|
72
|
+
section.build_row do |row|
|
73
|
+
row.title = "Title"
|
74
|
+
row.key = :title
|
75
|
+
row.type = :string
|
76
|
+
row.placeholder = 'Book title'
|
77
|
+
row.value = book['title']
|
78
|
+
end
|
79
|
+
|
80
|
+
section.build_row do |row|
|
81
|
+
row.title = "Author"
|
82
|
+
row.key = :author
|
83
|
+
row.type = :string
|
84
|
+
row.placeholder = 'Book author'
|
85
|
+
row.value = book['author']
|
86
|
+
end
|
87
|
+
|
88
|
+
section.build_row do |row|
|
89
|
+
row.title = "Year"
|
90
|
+
row.key = :year
|
91
|
+
row.type = :string
|
92
|
+
row.placeholder = 'Book year'
|
93
|
+
row.value = book['year']
|
94
|
+
end
|
95
|
+
|
96
|
+
section.build_row do |row|
|
97
|
+
row.title = 'Save Changes'
|
98
|
+
row.type = :submit
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
return form
|
103
|
+
end
|
104
|
+
|
105
|
+
end
|