teamsnap_rb 0.0.8 → 1.0.0.beta1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +13 -0
  3. data/Gemfile +2 -1
  4. data/LICENSE +1 -1
  5. data/README.md +31 -79
  6. data/Rakefile +19 -1
  7. data/lib/teamsnap/version.rb +3 -0
  8. data/lib/teamsnap.rb +270 -0
  9. data/spec/cassettes/apiv3-init.yml +1963 -0
  10. data/spec/cassettes/teamsnap_rb/adds_find_if_search_is_available.yml +56 -0
  11. data/spec/cassettes/teamsnap_rb/can_follow_plural_links.yml +111 -0
  12. data/spec/cassettes/teamsnap_rb/can_follow_singular_links.yml +105 -0
  13. data/spec/cassettes/teamsnap_rb/can_handle_an_empty_bulk_load.yml +52 -0
  14. data/spec/cassettes/teamsnap_rb/can_handle_an_error_with_bulk_load.yml +50 -0
  15. data/spec/cassettes/teamsnap_rb/can_handle_errors_generated_by_command.yml +50 -0
  16. data/spec/cassettes/teamsnap_rb/can_handle_links_with_no_data.yml +101 -0
  17. data/spec/cassettes/teamsnap_rb/can_use_bulk_load.yml +61 -0
  18. data/spec/cassettes/teamsnap_rb/handles_executing_an_action_via_commands.yml +54 -0
  19. data/spec/cassettes/teamsnap_rb/handles_fetching_data_via_queries.yml +56 -0
  20. data/spec/cassettes/teamsnap_rb/handles_queries_with_no_data.yml +53 -0
  21. data/spec/cassettes/teamsnap_rb/raises_an_exception_if_find_returns_nothing.yml +53 -0
  22. data/spec/spec_helper.rb +18 -13
  23. data/spec/teamsnap_spec.rb +125 -0
  24. data/teamsnap_rb.gemspec +17 -15
  25. metadata +100 -84
  26. data/lib/teamsnap_rb/client.rb +0 -8
  27. data/lib/teamsnap_rb/collection.rb +0 -179
  28. data/lib/teamsnap_rb/command.rb +0 -39
  29. data/lib/teamsnap_rb/commands.rb +0 -40
  30. data/lib/teamsnap_rb/config.rb +0 -14
  31. data/lib/teamsnap_rb/exceptions.rb +0 -3
  32. data/lib/teamsnap_rb/item.rb +0 -136
  33. data/lib/teamsnap_rb/link.rb +0 -24
  34. data/lib/teamsnap_rb/links_proxy.rb +0 -40
  35. data/lib/teamsnap_rb/models/event.rb +0 -4
  36. data/lib/teamsnap_rb/models/user.rb +0 -4
  37. data/lib/teamsnap_rb/queries_proxy.rb +0 -68
  38. data/lib/teamsnap_rb/request_builder.rb +0 -67
  39. data/lib/teamsnap_rb/template.rb +0 -37
  40. data/lib/teamsnap_rb/version.rb +0 -3
  41. data/lib/teamsnap_rb.rb +0 -22
  42. data/spec/client_spec.rb +0 -39
  43. data/spec/collection_spec.rb +0 -175
  44. data/spec/command_spec.rb +0 -60
  45. data/spec/commands_spec.rb +0 -35
  46. data/spec/config_spec.rb +0 -16
  47. data/spec/fixtures/cassettes/production_root.yml +0 -59
  48. data/spec/fixtures/cassettes/root.yml +0 -573
  49. data/spec/fixtures/cassettes/team.yml +0 -4858
  50. data/spec/fixtures/cassettes/teams.yml +0 -160
  51. data/spec/item_spec.rb +0 -133
  52. data/spec/link_spec.rb +0 -43
  53. data/spec/links_proxy_spec.rb +0 -32
  54. data/spec/queries_proxy_spec.rb +0 -31
  55. data/spec/template_spec.rb +0 -57
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: b7d2fa31f938ab3823bb36c4c6d1717afe5f88a1
4
- data.tar.gz: d2573216d36f51ed5d9c791fbafaac27729e7f5e
3
+ metadata.gz: 2ec7ccaac56d2dbb9377c7d191c93f1e95f72787
4
+ data.tar.gz: 3e99f2b6972f7a893678697b4f7e140040f39860
5
5
  SHA512:
6
- metadata.gz: 37202379f9ec80a10cc4e351c419962b3db2094077a33cfc7ecbd29aa0cc6d8473afeed3640b68453b1004439a48e5582864b32f20d8c817474d48a4b277fcd0
7
- data.tar.gz: 44c0684500659687b8e230ebbd844b941970e42a86cd506b7ab46b149a87cf0df2c81191fa18775358d2f9e1ce84e428a3a0c9bdcf4d50fdec57e23a1a67711d
6
+ metadata.gz: c6e8e9529786679bc1b332e8cc652156bb4455260c9236fdbcc6190a1f7bc5a9596ca5d829121ad6274b7778ac539a5b6c7ebc55b14470c883067f75654de711
7
+ data.tar.gz: c3d57d806335350eb1cd2d98bf3e491cdb2a6dd8b298fe0f3b8de48f1fb91689ccadc08d281e1b0c9a2682e3412ae5ffb29df284bb5dace4a8d6a444d90cf7ad
data/.travis.yml ADDED
@@ -0,0 +1,13 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.9.3
4
+ - 2.0.0
5
+ - 2.1.5
6
+ - 2.2.0
7
+ - rbx-2
8
+ - ruby-head
9
+ notifications:
10
+ email:
11
+ recipients:
12
+ - shane@teamsnap.com
13
+ - kyle.ries@teamsnap.com
data/Gemfile CHANGED
@@ -1,5 +1,6 @@
1
- source 'https://rubygems.org'
1
+ source "https://rubygems.org"
2
2
 
3
+ gem "simplecov", :require => false
3
4
  gem "coveralls", :require => false
4
5
  gem "pry", :require => false
5
6
 
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2014 TeamSnap
1
+ Copyright (c) 20l5 TeamSnap, Shane Emmons, Kyle Ries
2
2
 
3
3
  MIT License
4
4
 
data/README.md CHANGED
@@ -1,88 +1,40 @@
1
- # TeamSnapRb
1
+ # teamsnap_rb
2
2
 
3
- [ ![Codeship Status for teamsnap/teamsnap_rb](https://codeship.com/projects/535cff20-4bf3-0132-5b73-3612ac61d213/status)](https://codeship.com/projects/46910)
4
-
5
- [![Code Climate](https://codeclimate.com/github/teamsnap/teamsnap_rb/badges/gpa.svg)](https://codeclimate.com/github/teamsnap/teamsnap_rb)
6
- [![Coverage Status](https://coveralls.io/repos/teamsnap/teamsnap_rb/badge.png)](https://coveralls.io/r/teamsnap/teamsnap_rb)
7
-
8
- # EARLY ACCESS
9
-
10
- Please be aware there may be bugs, gremlins, and horrible issues; and you could be eaten by a grue. We are not responsible for any loss of life or limb (especially if you are eaten by a grue).
11
-
12
- # TeamsnapRb
13
-
14
- TeamsnapRb is a convenient wrapper around the faraday and the conglomerate gem that helps you talk to TeamSnap's APIv3.
15
-
16
- ## Installation
17
-
18
- Add this line to your application's Gemfile:
19
-
20
- gem 'teamsnap_rb'
21
-
22
- And then execute:
23
-
24
- $ bundle
25
-
26
- Or install it yourself as:
27
-
28
- $ gem install teamsnap_rb
3
+ [![Dependencies](http://img.shields.io/gemnasium/teamsnap/teamsnap_rb.svg)](https://gemnasium.com/teamsnap/teamsnap_rb)
4
+ [![Quality](http://img.shields.io/codeclimate/github/teamsnap/teamsnap_rb.svg)](https://codeclimate.com/github/teamsnap/teamsnap_rb)
5
+ [![Coverage](http://img.shields.io/coveralls/teamsnap/teamsnap_rb.svg)](https://https://coveralls.io/r/teamsnap/teamsnap_rb)
6
+ [![Build](http://img.shields.io/travis-ci/teamsnap/teamsnap_rb.svg)](https://travis-ci.org/teamsnap/teamsnap_rb)
7
+ [![Version](http://img.shields.io/gem/v/teamsnap_rb.svg)](https://rubygems.org/gems/teamsnap_rb)
8
+ [![License](http://img.shields.io/badge/license-MIT-blue.svg)](http://opensource.org/licenses/MIT)
29
9
 
30
10
  ## Usage
31
11
 
32
- Needs to be updated; coming soon.
33
-
34
- ## Contributing
35
-
36
- ### Git Workflow
37
-
38
- 1. Fork it ( http://github.com/teamsnap/teamsnap_rb/fork )
39
- 2. Create your feature branch (`git checkout -b my-new-feature`)
40
- 3. Commit your changes (`git commit -am 'Add some feature'`)
41
- 4. Push to the branch (`git push origin my-new-feature`)
42
- 5. Create new Pull Request
43
-
44
- ### Testing teamsnap_rb
12
+ _Note: You'll need an OAuth2 Token from TeamSnap. Checkout our API docs
13
+ [here](http://developer.teamsnap.com/documentation/apiv3/)_
45
14
 
46
- `./bin/rspec` will run the test suite.
47
-
48
- ### teamsnap_rb in IRB
49
-
50
- ```ruby
51
- $ irb
52
- > require 'teamsnap_rb'
53
- => true
54
- > client_config = TeamsnapRb::Config.new(
55
- > client_id: "your_client_id_here",
56
- > client_secret: "ssshhhhhh!"
57
- > )
58
- > client = TeamsnapRb::Client.new("http://apiv3.teamsnap.com/", config: client_config)
59
- => # client object
60
- > team = client.teams.search(:id => "your_team_id_here").first
61
- => # team object
62
15
  ```
63
-
64
- You can also access the API via OAuth2, which is handled outside of this gem.
65
- Simply pass the authorization token to access:
66
-
67
- ```ruby
68
- $ irb
69
- > require 'teamsnap_rb'
70
- => true
71
- > client_config = TeamsnapRb::Config.new(
72
- > authorization: "authorization key here",
73
- > )
74
- > client = TeamsnapRb::Client.new("http://apiv3.teamsnap.com/", config: client_config)
75
- => # client object
76
- > client.me.first.teams.inject({}){|a, t| a[t.name] = t.href; a }
77
- => {"Authorized User Team"=>"https://apiv3.teamsnap.com/teams/82308"}
16
+ λ bundle install
17
+ Fetching gem metadata from https://rubygems.org/..........
18
+ ...
19
+ Your bundle is complete!
20
+ Use `bundle show [gemname]` to see where a bundled gem is installed.
21
+ λ bundle exec rake console
22
+ [1] pry(main)> TeamSnap.init("abc123...", :url => "https://apiv3.teamsnap.com")
23
+ [2] pry(main)> t = TeamSnap::Team.find(1)
24
+ => #<TeamSnap::Team::...>
25
+ [3] pry(main)> t.name
26
+ => "TeamSnap"
27
+ [4] pry(main)> rs = client.bulk_load(:team_id => 1, :types => "team,member")
28
+ => [#<TeamSnap::Team:...>,
29
+ #<TeamSnap::Member:...>,
30
+ # ...
31
+ #<TeamSnap::Member:...>]
32
+ [5] pry(main)> rs[1].first_name
33
+ => "Andrew"
78
34
  ```
79
35
 
36
+ ## Todo
80
37
 
81
- In order to work on teamsnap_rb most effectively and efficiently, it'll he helpful
82
- to have a TeamSnap account and teams so you can work with that data. You can
83
- create a TeamSnap account [here](https://go.teamsnap.com) and you can then
84
- authorize a client application using our [Cogsworth
85
- service](https://auth.teamsnap.com).
86
-
87
- With a client ID and secret in hand, you can begin interacting with the gem
88
- using real data.
38
+ - Literate style docs?
39
+ - Cache items with threadsafe Hash (https://github.com/headius/thread_safe).
40
+ - Implement create, update and delete.
data/Rakefile CHANGED
@@ -1,4 +1,22 @@
1
+ require "rubygems"
1
2
  require "bundler/gem_tasks"
2
- require 'coveralls/rake/task'
3
+ require "rake/clean"
4
+ require "rspec/core/rake_task"
5
+ require "coveralls/rake/task"
6
+
7
+ CLOBBER.include("coverage")
8
+
9
+ RSpec::Core::RakeTask.new(:spec) do |t|
10
+ t.fail_on_error = false
11
+ end
12
+ task :default => :spec
3
13
 
4
14
  Coveralls::RakeTask.new
15
+
16
+ desc "start an IRB session with teamsnap loaded"
17
+ task :console do
18
+ require "pry"
19
+ require "./lib/teamsnap"
20
+ ARGV.clear
21
+ Pry.start
22
+ end
@@ -0,0 +1,3 @@
1
+ module TeamSnap
2
+ VERSION = "1.0.0.beta1"
3
+ end
data/lib/teamsnap.rb ADDED
@@ -0,0 +1,270 @@
1
+ require "faraday"
2
+ require "typhoeus"
3
+ require "typhoeus/adapters/faraday"
4
+ require "oj"
5
+ require "inflecto"
6
+ require "virtus"
7
+ require "date"
8
+
9
+ require_relative "teamsnap/version"
10
+
11
+ Oj.default_options = {
12
+ :mode => :compat,
13
+ :symbol_keys => true,
14
+ :class_cache => true
15
+ }
16
+
17
+ Faraday::Request.register_middleware(
18
+ :teamsnap_auth_middleware => -> { TeamSnap::AuthMiddleware }
19
+ )
20
+
21
+ Inflecto.inflections do |inflect|
22
+ inflect.irregular "member_preferences", "members_preferences"
23
+ inflect.irregular "opponent_results", "opponents_results"
24
+ inflect.irregular "team_preferences", "teams_preferences"
25
+ inflect.irregular "team_results", "teams_results"
26
+ end
27
+
28
+ module TeamSnap
29
+ EXCLUDED_RELS = ["me", "apiv2_root", "root", "self"]
30
+ DEFAULT_URL = "http://localhost:3000"
31
+ Error = Class.new(StandardError)
32
+ NotFound = Class.new(TeamSnap::Error)
33
+
34
+ class AuthMiddleware < Faraday::Middleware
35
+ def initialize(app, options)
36
+ super(app)
37
+ @options = options
38
+ end
39
+
40
+ def call(env)
41
+ token = @options.fetch(:token)
42
+ env[:request_headers].merge!({"Authorization" => "Bearer #{token}"})
43
+
44
+ @app.call(env)
45
+ end
46
+ end
47
+
48
+ class << self
49
+ def collection(href, resp)
50
+ Module.new do
51
+ define_singleton_method(:included) do |descendant|
52
+ descendant.send(:include, TeamSnap::Item)
53
+ descendant.extend(TeamSnap::Collection)
54
+ descendant.instance_variable_set(:@href, href)
55
+ descendant.instance_variable_set(:@resp, resp)
56
+ end
57
+ end
58
+ end
59
+
60
+ def hashify(arr)
61
+ arr.to_h
62
+ rescue NoMethodError
63
+ arr.inject({}) { |hash, (key, value)| hash[key] = value; hash }
64
+ end
65
+
66
+ def init(opts = {})
67
+ opts[:url] ||= DEFAULT_URL
68
+ opts.fetch(:token) {
69
+ raise ArgumentError.new("You must provide a :token to '.init'")
70
+ }
71
+
72
+ self.client = Faraday.new(
73
+ :url => opts.fetch(:url),
74
+ :parallel_manager => Typhoeus::Hydra.new
75
+ ) do |c|
76
+ c.request :teamsnap_auth_middleware, {:token => opts[:token]}
77
+ c.adapter :typhoeus
78
+ end
79
+
80
+ collection = TeamSnap.run(:get, "/")
81
+
82
+ classes = []
83
+ client.in_parallel do
84
+ classes = collection
85
+ .fetch(:links)
86
+ .map { |link| classify_rel(link) }
87
+ .compact
88
+ end
89
+
90
+ classes.each { |cls| cls.parse_collection }
91
+
92
+ apply_endpoints(self, collection) && true
93
+ end
94
+
95
+ def run(via, href, args = {})
96
+ resp = client.send(via, href, args)
97
+
98
+ if resp.status == 200
99
+ Oj.load(resp.body)
100
+ .fetch(:collection)
101
+ else
102
+ error_message = parse_error(resp)
103
+ raise TeamSnap::Error.new(error_message)
104
+ end
105
+ end
106
+
107
+ def parse_error(resp)
108
+ Oj.load(resp.body)
109
+ .fetch(:collection)
110
+ .fetch(:error)
111
+ .fetch(:message)
112
+ end
113
+
114
+ def load_items(collection)
115
+ collection
116
+ .fetch(:items) { [] }
117
+ .map { |item|
118
+ data = parse_data(item)
119
+ type = type_of(item)
120
+ cls = load_class(type, data)
121
+
122
+ cls.new(data).tap { |obj|
123
+ obj.send(:load_links, item.fetch(:links))
124
+ }
125
+ }
126
+ end
127
+
128
+ def apply_endpoints(obj, collection)
129
+ collection
130
+ .fetch(:queries) { [] }
131
+ .each { |endpoint| register_endpoint(obj, endpoint, :via => :get) }
132
+
133
+ collection
134
+ .fetch(:commands) { [] }
135
+ .each { |endpoint| register_endpoint(obj, endpoint, :via => :post) }
136
+ end
137
+
138
+ private
139
+
140
+ attr_accessor :client
141
+
142
+ def classify_rel(link)
143
+ return if EXCLUDED_RELS.include?(link.fetch(:rel))
144
+
145
+ rel = link.fetch(:rel)
146
+ href = link.fetch(:href)
147
+ name = Inflecto.classify(rel)
148
+ resp = client.get(href)
149
+
150
+ TeamSnap.const_set(
151
+ name, Class.new { include TeamSnap.collection(href, resp) }
152
+ ) unless TeamSnap.const_defined?(name)
153
+ end
154
+
155
+ def register_endpoint(obj, endpoint, opts)
156
+ rel = endpoint.fetch(:rel)
157
+ href = endpoint.fetch(:href)
158
+ valid_args = endpoint.fetch(:data) { [] }
159
+ .map { |datum| datum.fetch(:name).to_sym }
160
+ via = opts.fetch(:via)
161
+
162
+ obj.define_singleton_method(rel) do |*args|
163
+ args = Hash[*args]
164
+
165
+ unless args.all? { |arg, _| valid_args.include?(arg) }
166
+ raise ArgumentError.new(
167
+ "Invalid argument(s). Valid argument(s) are #{valid_args.inspect}"
168
+ )
169
+ end
170
+
171
+ TeamSnap.load_items(
172
+ TeamSnap.run(via, href, args)
173
+ )
174
+ end
175
+ end
176
+
177
+ def parse_data(item)
178
+ data = item
179
+ .fetch(:data)
180
+ .map { |datum|
181
+ name = datum.fetch(:name)
182
+ value = datum.fetch(:value)
183
+ type = datum.fetch(:type) { :default }
184
+
185
+ value = DateTime.parse(value) if value && type == "DateTime"
186
+
187
+ [name, value]
188
+ }
189
+ TeamSnap.hashify(data)
190
+ end
191
+
192
+ def type_of(item)
193
+ item
194
+ .fetch(:data)
195
+ .find { |datum| datum.fetch(:name) == "type" }
196
+ .fetch(:value)
197
+ end
198
+
199
+ def load_class(type, data)
200
+ TeamSnap.const_get(Inflecto.classify(type)).tap { |cls|
201
+ unless cls.include?(Virtus::Model::Core)
202
+ cls.class_eval do
203
+ include Virtus.value_object
204
+
205
+ values do
206
+ attribute :href, String
207
+ data.each { |name, value| attribute name, value.class }
208
+ end
209
+ end
210
+ end
211
+ }
212
+ end
213
+ end
214
+
215
+ module Item
216
+ private
217
+
218
+ def load_links(links)
219
+ links.each do |link|
220
+ next if EXCLUDED_RELS.include?(link.fetch(:rel))
221
+
222
+ rel = link.fetch(:rel)
223
+ href = link.fetch(:href)
224
+ is_singular = rel == Inflecto.singularize(rel)
225
+
226
+ define_singleton_method(rel) {
227
+ coll = TeamSnap.load_items(
228
+ TeamSnap.run(:get, href)
229
+ )
230
+ coll.size == 1 && is_singular ? coll.first : coll
231
+ }
232
+ end
233
+ end
234
+ end
235
+
236
+ module Collection
237
+ def href
238
+ self.instance_variable_get(:@href)
239
+ end
240
+
241
+ def resp
242
+ self.instance_variable_get(:@resp)
243
+ end
244
+
245
+ def parse_collection
246
+ if resp.status == 200
247
+ collection = Oj.load(resp.body)
248
+ .fetch(:collection)
249
+
250
+ TeamSnap.apply_endpoints(self, collection)
251
+ enable_find if respond_to?(:search)
252
+ else
253
+ error_message = TeamSnap.parse_error(resp)
254
+ raise TeamSnap::Error.new(error_message)
255
+ end
256
+ end
257
+
258
+ private
259
+
260
+ def enable_find
261
+ define_singleton_method(:find) do |id|
262
+ search(:id => id).first.tap do |object|
263
+ raise TeamSnap::NotFound.new(
264
+ "Could not find a #{self} with an id of '#{id}'."
265
+ ) unless object
266
+ end
267
+ end
268
+ end
269
+ end
270
+ end