Contributing

Make your first open source contribution with Spotify Ruby.

Reading time: 16 minutes

Introduction API Reference

1 contributor

  • GitHub user: bih

Introduction

We’re building a SDK to programmatically interact with Spotify using Ruby. It focuses on being as familiar and native to Ruby developers, from hobbyists to experts, and allowing them to be creative with music technology.

Even more importantly, we’re focusing on building software that isn’t just expected to be abandoned; but rather help to experiment and define better standards for consistently maintained community software.

Read more about the standards we’re aiming to achieve on GitHub.

Objectives

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

  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.

  2. ☁️ 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.

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

  4. ✨ 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. We care about Semantic Versioning for clear version changes.

Getting Started

There’s multiple ways you can contribute, and we’ll cover all of them:

Website

This refers to the website you are currently viewing. It is bundled in the master branch of bih/spotify-ruby in the docs/ folder, even though it is not part of the source code itself. It is not compiled inside of the gem itself.

The source code is compiled using Ruby and Jekyll. It is kindly hosted for free through GitHub Pages.

Setting up Jekyll

You’ll want to run these commands in your Terminal:

$ git clone ssh://git@github.com/bih/spotify-ruby.git
$ cd spotify-ruby/docs/
$ gem install bundler
$ bundle install
$ bundle exec jekyll serve --watch --open-url

The last command creates a local server and reloads whenever you make changes. Don’t forget that if you edit the _config.yml you will need to restart the server by pressing Ctrl + C and then re-running the last command.

More information and troubleshooting can be found in Jekyll’s Installation guide.

Folder Structure

In the docs/ folder, see all the respective content:

Type Description Location
Markdown Website Documentation docs/assets/**/*
Images Content Assets docs/assets/**/*
Images Layout Assets docs/theme/assets/**/*
CSS SASS Stylesheets docs/theme/_sass/*.scss
HTML HTML Components docs/theme/_includes/*.html
HTML HTML Layouts docs/theme/_layouts/*.html

SDK

This covers all of the code in spotify-ruby, excluding the docs/ folder (which was covered in Website above).

Versioning

We follow the Semantic Versioning convention in our versioning. Here’s a excerpt that will explain the convention better:

Given a version number MAJOR.MINOR.PATCH, increment the:

MAJOR version when you make incompatible API changes,
MINOR version when you add functionality in a backwards-compatible manner, and
PATCH version when you make backwards-compatible bug fixes.
Additional labels for pre-release and build metadata are available as extensions to the MAJOR.MINOR.PATCH format.

As a general rule of thumb, we always preference backwards compatibility as much as possible. It helps us achieve our 🌈 Delightful for Developers objective for this SDK.

Configuration

In our project, we have the core files in the root folder /:

Type File Description
Local Config .rspec Our config for running our testing framework, RSpec.
Local Config .rubocop.yml Our config for our Ruby static code analyzer tool, Rubocop.
Local Config .ruby-version The Ruby version we’re using to build spotify-ruby.
Local Config .rvm-version Our RVM-specific Ruby version we’re using to build spotify-ruby.
Local Config Gemfile Used for running bundler install during installation.
External Config .travis.yml Our config for our continuous integration provider, Travis CI.
External Config spotify-ruby.gemspec Used for configuring the spotify-ruby gem.
Tooling Rakefile Used for running rake helper commands.
Documentation LICENSE A distributed excerpt of our source code license.
Documentation CODE_OF_CONDUCT.md Our official Code of Conduct policy.
Documentation COVERAGE.md Our SDK coverage status of all Spotify Developer APIs.

SDK Folder Structure

In the lib/spotify/ and spec/ folders, we have a specific folder structure that we’ll explain:

lib/spotify/

Type File Description
Ruby version.rb Stores the Spotify::VERSION constant. See Versioning for more details.
Ruby accounts.rb The source file for Spotify::Accounts and authenticating with the SDK.
Ruby accounts/session.rb ↳ A subclass of Spotify::Accounts that deals with session management.
Ruby sdk.rb The source file for Spotify::SDK and interfacing with the Spotify Platform.
Ruby sdk/base.rb ↳ The core class for creating a SDK Component.
Ruby sdk/model.rb ↳ The core class for creating a SDK Model.

All other files in lib/spotify/sdk/ are either components or models. It is possible to identify what type of object they are by seeing where they inherit from.

# frozen_string_literal: true

module Spotify
  class SDK
    class Me < Base # This is a component.
    end

    class User < Model # This is a model.
    end
  end
end

spec/

Type File Description
Ruby spec_helper.rb The core Ruby file which configures our RSpec, WebMock and testing environment.
Mocks factories.rb The source of truth for generating “mock” Ruby objects for the SDK. Powered by FactoryBot.
Fixtures support/fixtures/ The folder containing sample Spotify API responses to be used with WebMock.

All files in the spec/ with the *_spec.rb filename are RSpec tests. They depend on WebMock for mocking API responses and FactoryBot for mocking Ruby objects.

To see a full list of all RSpec tests, run this in your Terminal:

$ ls -l spec/**/*_spec.rb

Adding a Component

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.

In the bin/ folder, we have provided a Rails-like generator that will generate the relevant files for you to add a new component:

Generating Component

For example, if you’d like to generate a component called Friends run the following command:

$ bin/generate_component friends

It will then generate the following files for you:

$ cat lib/spotify/sdk/friends.rb
$ cat spec/lib/spotify/sdk/friends_spec.rb

Mounting Component

Then you’ll need to do two manual steps in lib/spotify/sdk.rb:

  • Include the component at the top:

    # Components
    require "spotify/sdk/friends"
    
  • Then mount the component as friends in Spotify::SDK::SDK_COMPONENTS:

    SDK_COMPONENTS = {
      ...,
      friends: Spotify::SDK::Friends
    }.freeze
    

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

Writing Component Logic

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

# frozen_string_literal: true

module Spotify
  class SDK
    class Friend < Base
      ##
      # Hello world!
      #
      # @see https://bih.github.io/spotify-ruby/documentation/contributing/
      #
      # @param [Class] param_name Description
      # @return [String] text Description
      #
      def hi
        "Hello world!"
      end
    end
  end
end

The comments above are used by YARD to generate our SDK Reference on https://rubydoc.info.

Debugging Component Logic

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:

$ bin/console
[1] pry(main)> @accounts = Spotify::Accounts.new(client_id: "[client id]", client_secret: "[client secret]", redirect_uri: "[redirect_uri]")
=> #<Spotify::Accounts:0x007fb1ac8fba28>
[2] pry(main)> @session = Spotify::Accounts::Session.from_refresh_token(@accounts, "[refresh token]")
=> #<Spotify::Accounts::Session:0x007fb1ac8d9360>
[3] pry(main)> @sdk = Spotify::SDK.new(@session)
=> #<Spotify::SDK:0x007fb1abbafd10>
[4] pry(main)> @sdk.friends
=> #<Spotify::SDK::Friends:0x007fd7d31784e8>
[5] pry(main)> @sdk.friends.hi
=> "Hello world"

Testing Component Logic

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

# frozen_string_literal: true

require "spec_helper"

RSpec.describe Spotify::SDK::Friends do
  let(:session) { build(:session, access_token: "access_token") }
  subject { Spotify::SDK.new(session).friends }

  describe "#hi" do
    it "returns 'Hello world'" do
      expect(subject.hi).to eq "Hello world"
    end
  end
end

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

Sample Component Implementation

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

Adding a Model

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.

Protip: The models we write use Ruby’s OpenStruct data structure; it is designed for flexible Hash objects. It fits in our second objective of “being agnostic to minor changes”; in cases where Spotify may return null or contain additional fields, our codebase will not break.

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:

Generating Model

For example, if you’d like to generate a model called Device run the following command:

$ bin/generate_model device

It will then generate the following files for you:

$ cat lib/spotify/sdk/device.rb
$ cat spec/lib/spotify/sdk/device_spec.rb
$ cat spec/factories/device.rb

Mounting a Model

Then you’ll need to do one step in lib/spotify/sdk.rb:

  • Include the model at the top:
    # Models
    require "spotify/sdk/device"
    

Writing Model Logic

In our model, we can create a method called pause!:

# frozen_string_literal: true

module Spotify
  class SDK
    class Device < Model
      ##
      # Pause this device.
      #
      # @see https://bih.github.io/spotify-ruby/documentation/contributing/
      #
      # @param [Class] param_name Description
      # @return [String] text Description
      #
      def pause!
        parent.send_http_request(:put, "/v1/me/player/play?device_id=#{id}", {
          http_options: {
            expect_nil: true # This API returns a blank response. This is OK.
          }
        })
        self # Let's return itself, so we can support chaining methods.
      end
    end
  end
end

And then we can initialize the SDK with two parameters:

  • A Hash payload. For example { device_id: "id of device here" }
  • An instance of a valid Component (see Friends above)

You can see an example in Debugging Model Logic below.

Debugging Model Logic

With our new Spotify::SDK::Device model, we can now run this in bin/console:

$ bin/console
[1] pry(main)> @accounts = Spotify::Accounts.new(client_id: "[client id]", client_secret: "[client secret]", redirect_uri: "[redirect_uri]")
=> #<Spotify::Accounts:0x007fb1ac8fba28>
[2] pry(main)> @session = Spotify::Accounts::Session.from_refresh_token(@accounts, "[refresh token]")
=> #<Spotify::Accounts::Session:0x007fb1ac8d9360>
[3] pry(main)> @sdk = Spotify::SDK.new(@session)
=> #<Spotify::SDK:0x007fb1abbafd10>
[4] pry(main)> @base = Spotify::SDK::Base.new(@sdk)
=> #<Spotify::SDK::Base:0x007fb1ac11dd10>
[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)
=> #<Spotify::SDK::Connect::Device id=1234, is_active=true, is_private_session=false, is_restricted=false, name="Device Name", type="Smartphone", volume_percent=24>
[6] pry(main)> @device.pause!
=> #<Spotify::SDK::Connect::Device id=1234, is_active=true, is_private_session=false, is_restricted=false, name="Device Name", type="Smartphone", volume_percent=24>

Testing Model Logic

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.

Usually we’d need to add the following:

  • Mock Ruby model object: FactoryBot

    • Typically adding this Ruby code to spec/factories.rb:

      factory :device, class: Spotify::SDK::Device do
        association :parent, factory: :base
      
        skip_create
        initialize_with { new(attributes, parent) }
      end
      
  • Mock API response: Fixtures

    • Typically adding the expected JSON response for the model in spec/support/fixtures/ with the criteria:
    • Filename format convention: spec/support/fixtures/{method_type}/{version}/{endpoint}/{custom_name}.json
    • Example: spec/support/fixtures/v1/get/me/player/devices/active-list.json

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

# frozen_string_literal: true

require "spec_helper"

RSpec.describe Spotify::SDK::Device do
  let(:raw_data) { read_fixture("get/v1/me/player/devices/active-list") }
  let(:session)  { build(:session, access_token: "access_token") }
  let(:sdk_base) { Spotify::SDK::Base.new(Spotify::SDK.new(session)) }
  subject        { Spotify::SDK::Device.new(raw_data, sdk_base) }

  describe "#to_h" do
    it "returns the correct value" do
      expect(subject.to_h).to eq raw_data
    end
  end

  describe "#pause!" do
    it "should make an api call" do
      stub = stub_request(:put, "https://api.spotify.com/v1/me/player/pause?device_id=#{raw_data[:id]}")
             .with(headers: {Authorization: "Bearer access_token"})

      subject.pause!
      expect(stub).to have_been_requested
    end

    it "should return itself" do
      expect(subject.pause!).to be subject
    end
  end
end

And you can run all of the tests by running rake ci in the root directory.

Sample Model Implementation

For an example of a good implementation, see the following files for Spotify::SDK::Connect::Device model:

Testing

On top of writing tests for Components and Models, we have additional tools & tests for quality assurance in other areas:

Type Tool Command Purpose
Linter Rubocop rake rubocop To lint Ruby syntax for anti-patterns, readability, and more.
Test Coverage SimpleCov rake spec To calculate test coverage (how much of our code has tests).
Documentation YARD rake yard To generate API Reference documentation. Note: it has been mentioned in this guide.
Debugger Pry bin/console Improved console-based debugging.

Creating a Pull Request

We’d love to see you suggest changes and make a pull request! In order to make a pull request, you’ll need to fork the bih/spotify-ruby repository. Then make the relevant changes and create a PR to bih/spotify-ruby:master on GitHub.

Protip: First time making a pull request? Check out this guide from GitHub!

We will then review your changes, and then approve/merge it for you.

  • If the change is with docs/, once merged GitHub Pages will automatically deploy your changes to bih.github.io/spotify-ruby. It can sometimes take up to 15 minutes to propagate.
  • If the change is with the SDK, we will publish the changes in the next MAJOR/MINOR release. Urgent changes will result in an expedited PATCH release, as per the Semantic Versioning convention.

If we have reviewed it, we will most likely suggest changes for you. The feedback will need to be addressed by you or another community member. We may close PRs that may be inactive, unclear, or controversial.

Add Your Profile as a Contributor

If you’re contributing changes to any file in docs/documentation/, it is possible to mention yourself as an official contributor!

It’s possible to see at the top of this page, or in the screenshot below:

To add yourself, in the relevant .md file, at the top you should see:

contributors:
  - https://github.com/bih

All you need to do is to create a new line with your GitHub username:

contributors:
  - https://github.com/bih
  - https://github.com/[your github username here]

That’s all - we’ll use your public GitHub avatar and give you some 💖!