Skip to content
This repository was archived by the owner on Mar 5, 2025. It is now read-only.

Commit 7eb3c58

Browse files
committed
Updated guide
1 parent e47d787 commit 7eb3c58

File tree

2 files changed

+207
-20
lines changed

2 files changed

+207
-20
lines changed

README.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,13 @@ The developer-friendly, opinionated Ruby SDK for [Spotify]. Works on Ruby 2.4+
3636

3737
Hey! I'm a Developer Advocate at [Spotify], and I wrote this Ruby SDK to explore how to build a SDK that is TADA:
3838

39-
- **🧒 Thoughtfully inclusive for beginners.** Everything we do should think about beginners from the start. From having an enforced [Code of Conduct] policy to building great documentation, tooling, and an empathetic feedback process. Designing for beginners is designing for longevity.
39+
1. **🧒 Thoughtfully inclusive for beginners.** Everything we do should think about beginners from the start. From having an enforced [Code of Conduct] policy to building great documentation, tooling, and an empathetic feedback process. Designing for beginners is designing for longevity.
4040

41-
- **☁️ Agnostic to minor changes.** APIs change all the time. We should be opinionated enough that our software should break with major changes, but flexible enough to work perfectly fine with minor changes. Our code should only depend on critical data, such as IDs.
41+
1. **☁️ Agnostic to minor changes.** APIs change all the time. We should be opinionated enough that our software should break with major changes, but flexible enough to work perfectly fine with minor changes. Our code should only depend on critical data, such as IDs.
4242

43-
- **🌈 Delightful for developers.** Writing the SDK and using the SDK should be equally delightful. Granted, this is a challenging goal; but with solid information architecture, well-crafted opinions, clear and helpful error messages, and software that doesn't get in your way - we will create quite lovely software.
43+
1. **🌈 Delightful for developers.** Writing the SDK and using the SDK should be equally delightful. Granted, this is a challenging goal; but with solid information architecture, well-crafted opinions, clear and helpful error messages, and software that doesn't get in your way - we will create quite lovely software.
4444

45-
- **✨ A maintained production-level.** It doesn't take experts to write production-level code; all it takes is considerate guidance from the community. We should write software that we and others [trust to do what it is intended to do](https://hackernoon.com/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5). We care about [Semantic Versioning] for clear version changes.
45+
1. **✨ A maintained production-level.** It doesn't take experts to write production-level code; all it takes is considerate guidance from the community. We should write software that we and others [trust to do what it is intended to do](https://hackernoon.com/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5). We care about [Semantic Versioning] for clear version changes.
4646

4747
_Disclaimer: This SDK is NOT owned or supported by Spotify. It remains a personal project of mine. If you are a commercial partner of Spotify and wish to use this SDK, be aware you are using it at your own risk._
4848

docs/documentation/contributing/index.md

Lines changed: 203 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -38,13 +38,13 @@ Even more importantly, we're focusing on building software that isn't just expec
3838

3939
As mentioned in our `README.md`, we have four objectives with this SDK:
4040

41-
- **🧒 Thoughtfully inclusive for beginners.** Everything we do should think about beginners from the start. From having an enforced [Code of Conduct] policy to building great documentation, tooling, and an empathetic feedback process. Designing for beginners is designing for longevity.
41+
1. **🧒 Thoughtfully inclusive for beginners.** Everything we do should think about beginners from the start. From having an enforced [Code of Conduct] policy to building great documentation, tooling, and an empathetic feedback process. Designing for beginners is designing for longevity.
4242

43-
- **☁️ Agnostic to minor changes.** APIs change all the time. We should be opinionated enough that our software should break with major changes, but flexible enough to work perfectly fine with minor changes. Our code should only depend on critical data, such as IDs.
43+
1. **☁️ Agnostic to minor changes.** APIs change all the time. We should be opinionated enough that our software should break with major changes, but flexible enough to work perfectly fine with minor changes. Our code should only depend on critical data, such as IDs.
4444

45-
- **🌈 Delightful for developers.** Writing the SDK and using the SDK should be equally delightful. Granted, this is a challenging goal; but with solid information architecture, well-crafted opinions, clear and helpful error messages, and software that doesn't get in your way - we will create quite lovely software.
45+
1. **🌈 Delightful for developers.** Writing the SDK and using the SDK should be equally delightful. Granted, this is a challenging goal; but with solid information architecture, well-crafted opinions, clear and helpful error messages, and software that doesn't get in your way - we will create quite lovely software.
4646

47-
- **✨ A maintained production-level.** It doesn't take experts to write production-level code; all it takes is considerate guidance from the community. We should write software that we and others [trust to do what it is intended to do](https://hackernoon.com/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5). We care about [Semantic Versioning] for clear version changes.
47+
1. **✨ A maintained production-level.** It doesn't take experts to write production-level code; all it takes is considerate guidance from the community. We should write software that we and others [trust to do what it is intended to do](https://hackernoon.com/im-harvesting-credit-card-numbers-and-passwords-from-your-site-here-s-how-9a8cb347c5b5). We care about [Semantic Versioning] for clear version changes.
4848

4949
# Getting Started
5050

@@ -60,7 +60,6 @@ There's multiple ways you can contribute, and we'll cover all of them:
6060
- [Adding a Component](#adding-a-component)
6161
- [Adding a Model](#adding-a-model)
6262
- [Testing](#testing)
63-
- [Documentation](#documentation)
6463

6564
## Website
6665

@@ -212,9 +211,11 @@ $ ls -l spec/**/*_spec.rb
212211

213212
### Adding a Component
214213

214+
Components are subclasses of `Spotify::SDK` and typically represent a category of features for the [Spotify Platform]. For example, `Connect` represents a category of features - listing devices, controlling them, reading current playback, etc. Another example would be `Me` - listing my information, my top tracks, my saved tracks, etc.
215+
215216
In the `bin/` folder, we have provided a Rails-like generator that will generate the relevant files for you to add a new component:
216217

217-
#### Generate
218+
#### Generating Component
218219

219220
For example, if you'd like to generate a component called `Friends` run the following command:
220221

@@ -229,7 +230,7 @@ $ cat lib/spotify/sdk/friends.rb
229230
$ cat spec/lib/spotify/sdk/friends_spec.rb
230231
```
231232

232-
#### Mounting
233+
#### Mounting Component
233234

234235
Then you'll need to do two manual steps in `lib/spotify/sdk.rb`:
235236

@@ -250,7 +251,7 @@ Then you'll need to do two manual steps in `lib/spotify/sdk.rb`:
250251

251252
That's it! We have setup a component. You can go ahead and write some fun logic! 🙂
252253

253-
#### Writing Logic
254+
#### Writing Component Logic
254255

255256
In our component, we can create a method called `hi`:
256257

@@ -278,20 +279,25 @@ end
278279

279280
The comments above are used by [YARD] to generate our [SDK Reference] on <https://rubydoc.info>.
280281

281-
#### Debugging Logic
282+
#### Debugging Component Logic
282283

283284
As we've mounted our component as `friends` already, we can use the following code to test it in our console by running `bin/console`:
284285

285-
```
286+
```sh
286287
$ bin/console
287-
[1] pry(main)> @sdk = Spotify::SDK.new(@session)
288-
[2] pry(main)> @sdk.friends
288+
[1] pry(main)> @accounts = Spotify::Accounts.new(client_id: "[client id]", client_secret: "[client secret]", redirect_uri: "[redirect_uri]")
289+
=> #<Spotify::Accounts:0x007fb1ac8fba28>
290+
[2] pry(main)> @session = Spotify::Accounts::Session.from_refresh_token(@accounts, "[refresh token]")
291+
=> #<Spotify::Accounts::Session:0x007fb1ac8d9360>
292+
[3] pry(main)> @sdk = Spotify::SDK.new(@session)
293+
=> #<Spotify::SDK:0x007fb1abbafd10>
294+
[4] pry(main)> @sdk.friends
289295
=> #<Spotify::SDK::Friends:0x007fd7d31784e8>
290-
[3] pry(main)> @sdk.friends.hi
296+
[5] pry(main)> @sdk.friends.hi
291297
=> "Hello world"
292298
```
293299

294-
#### Testing Logic
300+
#### Testing Component Logic
295301

296302
In our generated `spec/lib/spotify/sdk/friends_spec.rb` file, we can write some [RSpec] tests:
297303

@@ -314,7 +320,7 @@ end
314320

315321
And then you can execute tests by running `rake ci` in the root directory.
316322

317-
#### Sample Implementation
323+
#### Sample Component Implementation
318324

319325
For an example of a good implementation, see the following files for `Spotify::SDK::Me` component:
320326

@@ -328,9 +334,186 @@ For an example of a good implementation, see the following files for `Spotify::S
328334

329335
### Adding a Model
330336

337+
Models are typically classes that hold data. They often contain functions to manipulate or perform actions with that data. An example is a `Device` model, it would contain a method like `pause!` which will take the `id` and send a `PUT /v1/me/player/pause?device_id={id}` HTTP command.
338+
339+
**Protip:** The models we write use Ruby's [OpenStruct] data structure; it is designed for flexible Hash objects. It fits in our [second objective](#objectives) of "being agnostic to minor changes"; in cases where Spotify may return `null` or contain additional fields, our codebase will not break.
340+
341+
In our `bin/` folder, similarly to components, we have a Rails-like generator that will generate the relevant files for you to add a new model:
342+
343+
#### Generating Model
344+
345+
For example, if you'd like to generate a model called `Device` run the following command:
346+
347+
```sh
348+
$ bin/generate_model device
349+
```
350+
351+
It will then generate the following files for you:
352+
353+
```sh
354+
$ cat lib/spotify/sdk/device.rb
355+
$ cat spec/lib/spotify/sdk/device_spec.rb
356+
$ cat spec/factories/device.rb
357+
```
358+
359+
#### Mounting a Model
360+
361+
Then you'll need to do one step in `lib/spotify/sdk.rb`:
362+
363+
- Include the model at the top:
364+
```ruby
365+
# Models
366+
require "spotify/sdk/device"
367+
```
368+
369+
#### Writing Model Logic
370+
371+
In our model, we can create a method called `pause!`:
372+
373+
```ruby
374+
# frozen_string_literal: true
375+
376+
module Spotify
377+
class SDK
378+
class Device < Model
379+
##
380+
# Pause this device.
381+
#
382+
# @see https://bih.github.io/spotify-ruby/documentation/contributing/
383+
#
384+
# @param [Class] param_name Description
385+
# @return [String] text Description
386+
#
387+
def pause!
388+
parent.send_http_request(:put, "/v1/me/player/play?device_id=#{id}", {
389+
http_options: {
390+
expect_nil: true # This API returns a blank response. This is OK.
391+
}
392+
})
393+
self # Let's return itself, so we can support chaining methods.
394+
end
395+
end
396+
end
397+
end
398+
```
399+
400+
And then we can initialize the SDK with two parameters:
401+
402+
- A `Hash` payload. For example `{ device_id: "id of device here" }`
403+
- An instance of a valid Component (see Friends above)
404+
405+
You can see an example in [Debugging Model Logic](#debugging-model-logic) below.
406+
407+
#### Debugging Model Logic
408+
409+
With our new `Spotify::SDK::Device` model, we can now run this in `bin/console`:
410+
411+
```sh
412+
$ bin/console
413+
[1] pry(main)> @accounts = Spotify::Accounts.new(client_id: "[client id]", client_secret: "[client secret]", redirect_uri: "[redirect_uri]")
414+
=> #<Spotify::Accounts:0x007fb1ac8fba28>
415+
[2] pry(main)> @session = Spotify::Accounts::Session.from_refresh_token(@accounts, "[refresh token]")
416+
=> #<Spotify::Accounts::Session:0x007fb1ac8d9360>
417+
[3] pry(main)> @sdk = Spotify::SDK.new(@session)
418+
=> #<Spotify::SDK:0x007fb1abbafd10>
419+
[4] pry(main)> @base = Spotify::SDK::Base.new(@sdk)
420+
=> #<Spotify::SDK::Base:0x007fb1ac11dd10>
421+
[5] pry(main)> @device = Spotify::SDK::Device.new({ id: 1234, is_active: true, is_private_session: false, is_restricted: false, name: "Device Name", type: "Smartphone", volume_percent: 24 }, @base)
422+
=> #<Spotify::SDK::Connect::Device id=1234, is_active=true, is_private_session=false, is_restricted=false, name="Device Name", type="Smartphone", volume_percent=24>
423+
[6] pry(main)> @device.pause!
424+
=> #<Spotify::SDK::Connect::Device id=1234, is_active=true, is_private_session=false, is_restricted=false, name="Device Name", type="Smartphone", volume_percent=24>
425+
```
426+
427+
#### Testing Model Logic
428+
429+
For models, we have a slightly different approach to testing them. As they handle API response data from [Spotify], we would need to mock what those responses look like to ensure the SDK is able to respond in the way we expect it to.
430+
431+
Usually we'd need to add the following:
432+
433+
- Mock Ruby model object: [FactoryBot]
434+
435+
- Typically adding this Ruby code to `spec/factories.rb`:
436+
437+
```ruby
438+
factory :device, class: Spotify::SDK::Device do
439+
association :parent, factory: :base
440+
441+
skip_create
442+
initialize_with { new(attributes, parent) }
443+
end
444+
```
445+
446+
- Mock API response: [Fixtures](https://stackoverflow.com/questions/12071344/what-are-fixtures-in-programming)
447+
- Typically adding the expected JSON response for the model in `spec/support/fixtures/` with the criteria:
448+
- Filename format convention: `spec/support/fixtures/{method_type}/{version}/{endpoint}/{custom_name}.json`
449+
- Example: `spec/support/fixtures/v1/get/me/player/devices/active-list.json`
450+
451+
And then similarly to Components, we'll need to add tests for the `Spotify::SDK::Device` object we created in `spec/lib/spotify/sdk/device_spec.rb`
452+
453+
```ruby
454+
# frozen_string_literal: true
455+
456+
require "spec_helper"
457+
458+
RSpec.describe Spotify::SDK::Device do
459+
let(:raw_data) { read_fixture("get/v1/me/player/devices/active-list") }
460+
let(:session) { build(:session, access_token: "access_token") }
461+
let(:sdk_base) { Spotify::SDK::Base.new(Spotify::SDK.new(session)) }
462+
subject { Spotify::SDK::Device.new(raw_data, sdk_base) }
463+
464+
describe "#to_h" do
465+
it "returns the correct value" do
466+
expect(subject.to_h).to eq raw_data
467+
end
468+
end
469+
470+
describe "#pause!" do
471+
it "should make an api call" do
472+
stub = stub_request(:put, "https://api.spotify.com/v1/me/player/pause?device_id=#{raw_data[:id]}")
473+
.with(headers: {Authorization: "Bearer access_token"})
474+
475+
subject.pause!
476+
expect(stub).to have_been_requested
477+
end
478+
479+
it "should return itself" do
480+
expect(subject.pause!).to be subject
481+
end
482+
end
483+
end
484+
```
485+
486+
And you can run all of the tests by running `rake ci` in the root directory.
487+
488+
#### Sample Model Implementation
489+
490+
For an example of a good implementation, see the following files for `Spotify::SDK::Connect::Device` model:
491+
492+
- Implementation: [lib/spotify/sdk/connect/device.rb]
493+
- RSpec Tests: [spec/lib/spotify/sdk/connect/device_spec.rb]
494+
- Ruby Object Mock: [spec/factories.rb]
495+
- Spotify API Response Mocks / Fixtures:
496+
- Response w/ active list: [spec/support/fixtures/get/v1/me/player/devices/active-list.json]
497+
- Response w/ inactive list: [spec/support/fixtures/get/v1/me/player/devices/inactive-list.json]
498+
- Response w/ empty list: [spec/support/fixtures/get/v1/me/player/devices/empty-list.json]
499+
500+
[lib/spotify/sdk/connect/device.rb]: https://github.com/bih/spotify-ruby/blob/master/lib/spotify/sdk/connect/device.rb
501+
[spec/lib/spotify/sdk/connect/device_spec.rb]: https://github.com/bih/spotify-ruby/blob/master/spec/lib/spotify/sdk/connect/device_spec.rb
502+
[spec/factories.rb]: https://github.com/bih/spotify-ruby/blob/master/spec/factories.rb
503+
[spec/support/fixtures/get/v1/me/player/devices/active-list.json]: https://github.com/bih/spotify-ruby/blob/master/spec/support/fixtures/get/v1/me/player/devices/active-list.json
504+
[spec/support/fixtures/get/v1/me/player/devices/inactive-list.json]: https://github.com/bih/spotify-ruby/blob/master/spec/support/fixtures/get/v1/me/player/devices/inactive-list.json
505+
[spec/support/fixtures/get/v1/me/player/devices/empty-list.json]: https://github.com/bih/spotify-ruby/blob/master/spec/support/fixtures/get/v1/me/player/devices/empty-list.json
506+
331507
### Testing
332508
333-
### Documentation
509+
On top of writing tests for Components and Models, we have additional tools & tests for quality assurance in other areas:
510+
511+
| Type | Tool | Command | Purpose |
512+
| ------------- | ----------- | -------------- | ------------------------------------------------------------------------------------- |
513+
| Linter | [Rubocop] | `rake rubocop` | To lint Ruby syntax for anti-patterns, readability, and more. |
514+
| Test Coverage | [SimpleCov] | `rake spec` | To calculate test coverage (how much of our code has tests). |
515+
| Documentation | [YARD] | `rake yard` | To generate API Reference documentation. _Note: it has been mentioned in this guide._ |
516+
| Debugger | [Pry] | `bin/console` | Improved console-based debugging. |
334517
335518
# Creating a Pull Request
336519
@@ -377,11 +560,15 @@ That's all - we'll use your public GitHub avatar and give you some 💖!
377560
[yard]: https://yardoc.org
378561
[factorybot]: https://github.com/thoughtbot/factory_bot
379562
[rspec]: http://rspec.info
563+
[openstruct]: https://ruby-doc.org/stdlib-2.5.1/libdoc/ostruct/rdoc/OpenStruct.html
380564
[webmock]: https://github.com/bblimke/webmock
381565
[rubocop]: https://github.com/rubocop-hq/rubocop
566+
[simplecov]: https://github.com/colszowka/simplecov
382567
[travis ci]: https://travis-ci.org
383568
[bih/spotify-ruby]: https://github.com/bih/spotify-ruby
384569
[spotify]: https://developer.spotify.com
570+
[pry]: https://github.com/pry/pry
571+
[spotify platform]: https://developer.spotify.com
385572
[markdown]: https://daringfireball.net/projects/markdown/syntax
386573
[ruby]: https://ruby-lang.org
387574
[jekyll]: https://jekyllrb.com

0 commit comments

Comments
 (0)