teamsnap_rb 0.0.8 → 1.0.0.beta1

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.
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