{"id":24969463,"url":"https://github.com/intrepidpursuits/intrepid-jams-workshop","last_synced_at":"2025-04-11T04:40:36.735Z","repository":{"id":151543736,"uuid":"58228114","full_name":"IntrepidPursuits/intrepid-jams-workshop","owner":"IntrepidPursuits","description":"A workshop on building Rails APIs on 5/6/2016","archived":false,"fork":false,"pushed_at":"2016-09-12T20:38:46.000Z","size":40,"stargazers_count":1,"open_issues_count":0,"forks_count":8,"subscribers_count":12,"default_branch":"master","last_synced_at":"2025-03-25T02:43:45.345Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/IntrepidPursuits.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2016-05-06T18:47:47.000Z","updated_at":"2016-08-23T14:32:19.000Z","dependencies_parsed_at":null,"dependency_job_id":"b48a25c1-6a44-4ef0-83e0-547fa10b71d3","html_url":"https://github.com/IntrepidPursuits/intrepid-jams-workshop","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IntrepidPursuits%2Fintrepid-jams-workshop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IntrepidPursuits%2Fintrepid-jams-workshop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IntrepidPursuits%2Fintrepid-jams-workshop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/IntrepidPursuits%2Fintrepid-jams-workshop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/IntrepidPursuits","download_url":"https://codeload.github.com/IntrepidPursuits/intrepid-jams-workshop/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248345274,"owners_count":21088241,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-02-03T14:38:21.056Z","updated_at":"2025-04-11T04:40:36.724Z","avatar_url":"https://github.com/IntrepidPursuits.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Intrepid jam workshop\n\n# Rails API Workshop - May 6, 2016\n\n## Pre-workshop setup\n\n* Install the following, either via Thoughtbot's [laptop script](https://github.com/thoughtbot/laptop) which will install all of these, or individually:\n  - Homebrew\n  - A Ruby version manager (we recommend [rbenv](https://github.com/rbenv/rbenv)) and Ruby 2.3.0.\n  - Bundler, Rails, and Suspenders gems\n    ```\n    $ gem install bundler\n    $ gem install rails\n    $ gem install suspenders\n    ```\n\n    - Postgres - can use Homebrew (https://www.moncefbelyamani.com/how-to-install-postgresql-on-a-mac-with-homebrew-and-lunchy/) or the Postgres app (http://postgresapp.com/)\n\n* Create a Heroku account, download the Heroku toolbelt (not necessary if you used the laptop script), and login (see instructions here https://toolbelt.heroku.com/).  This gives you the Heroku CLI which has some useful commands for deploying, interacting with your database, and running rake tasks.\n\nYou'll also want a text editor of some kind (Atom, Sublime, Vim) or IDE (RubyMine).\n\n## A few things to know about Rails\n\n* Naming conventions - casing, pluraliztion, filenames \u0026 paths matter\n* Other???\n\n## Building an API\n\n### Overview\n\nWe're going to build an app that keeps track of Intrepid Jam games and lets us record new ones.  We'll have the following endpoints:\n\n```\nGET /games # returns a list of all games\nPOST /games # create a new game\n```\n\nOur data model will look like this (note that I'm leaving off `created_at`, and `updated_at` properties on each table, which Rails gives us for free; it also gives us `id` for free but I'm listing it here so we can see how the foreign and primary keys link up):\n\n```\nGame\n-------\nid\n\nTeam\n------\nid\ngame_id\nscore\nname\nplayer_names\n```\n\n### Step 1. Generate a new Rails app\n\nThere are a few different options for generating a new Rails app:\n\n```\n$ rails new \u003capp_name\u003e\n$ rails-api new \u003capp_name\u003e\n$ suspenders \u003capp_name\u003e\n```\n\n`rails new` is the standard Rails command for generating a new app.\n`rails-api new` generates a Rails app without any of the asset pipeline components that are used for a frontend web Rails app.\n`suspenders` is basically `rails new` plus a lot of the common libraries we use.  It comes from the [suspenders](https://github.com/thoughtbot/suspenders) gem.\n\n### Commands to run\n\n```\n$ suspenders intrepid_jams\n$ cd intrepid_jams/\n$ bundle\n$ rake db:create \u0026\u0026 rake db:migrate\n$ git init \u0026\u0026 git add . \u0026\u0026 git commit -am \"Initial commit - new Suspenders app\"\n```\n\n### Review structure of a Rails app\n\n```\napp/\n- models/\n- controllers/\n\ndb/\n- migrate/\n- schema.rb\n\nconfig/\n- routes.rb\n```  \n\n### Step 2. Add our models\n\nRails provides some generators for creating different components of an app (e.g. a model, a controller, etc.).  I don't use most of them, but some are helpful, for example for generating models.\n\n```\n$ rails g model game\n      invoke  active_record\n      create    db/migrate/20160429184249_create_games.rb\n      create    app/models/game.rb\n      invoke    rspec\n      create      spec/models/game_spec.rb\n      invoke      factory_girl\n      create        spec/factories/games.rb\n```\n\nThis creates a bunch of files for you, namely: the model; a migration, which, when run, will create a `games` database table; and some stuff for testing which we won't worry about now.  [Take a look at the model \u0026 the migration \u0026 discuss.]\n\nRun `rake db:migrate` to apply the changes in the migration to the database.  You'll see the `db/schema.rb` file has changed to reflect these changes.\n\n```\n$ rake db:migrate\n```\n\nAdd the team model, which has additional attributes beyond the basic `id` and timestamps:\n\n```\n$ rails g model team name:string player_names:string score:integer game:references\n$ rake db:migrate\n```\n\n[Discuss migration \u0026 model, touch on associations.]\n\nNormally I'd consider adding validations at this point -- we might want to ensure, for example, that every team has a name and a `game_id`, but for now we'll leave as is for the sake of time.\n\n### Step 3. Add a `GET /games` endpoint\n\nThere are two main components we'll need to add to create a `GET /games` endpoint:\n- A `route` in `config/routes.rb`.  Tells Rails what to do when it gets a request to `GET /games` (i.e. which controller action to call)\n- A controller and a controller action to handle requests to that route.\n  * A controller action is really just a method or function -- I don't know why we call them actions\n  * There are Rails conventions around controller action names - discuss.  (index, show, create, update, delete)\n\nAdd a route:\n\n```ruby\nRails.application.routes.draw do\n  resources :games, only: :index\nend\n```\n\nYou can see all the routes in your app by running `rake routes`, along with the controller \u0026 action they map to:\n\n```\n$  rake routes\nPrefix Verb URI Pattern      Controller#Action\n games GET  /games(.:format) games#index\n  page GET  /pages/*id       high_voltage/pages#show\n```\n\n[Talk about the `resources` method, vs. `get '/games' =\u003e 'games#index'` for example.]\n\nLet's start up our app and go to that endpoint in our browser.  You can run `rails server` or `rails s` and then go to http://localhost:3000/games in your browser, and you'll see a nice little error message telling us we don't have a controller.\n\n```\n$ rails server\n```\n\nLet's add our controller, with an index action:\n\n```\n$ touch app/controllers/games_controller.rb\n```\n\n```ruby\nclass GamesController \u003c ApplicationController\n  def index\n  end\nend\n```\n\nIf we go to that url now, we'll see a different error -- it tells us a template is missing.  That's because what Rails does here is that it assumes you want to render HTML from a template located at `app/views/games/index.html.erb`, and there's nothing there.\n\nWe can shut it up by explicitly telling it what to render.  Let's render some empty JSON:\n\n```ruby\nclass GamesController \u003c ApplicationController\n  def index\n    render json: []\n  end\nend\n```\n\nAnd now we should see an empty array in our browser.\n\nBut we don't want to show an empty array, we want to show all the `Games` in our database. The parent class of our `Game` model, `ActiveRecord::Base`, defines a whole bunch of query methods, including `.all`, which we can call on the `Game` class to fetch all games:\n\n```ruby\nclass GamesController \u003c ApplicationController\n  def index\n    games = Game.all\n\n    render json: games\n  end\nend\n```\n\nObviously we don't have any games in our DB, so let's change that.\n\nThere are a couple of ways I can put data in my database:\n- I can run `rails console` and create some fake data in there.\n- I can add some commands to create fake data in `db/seeds.rb` and run `rake db:seed`.  Normally I would only do this for data that I needed to seed in the production DB but for now I'll do that anyway.\n\n```ruby\ngames = [{ teams: [{ name: 'Bulls', player_names: 'Danny, Benn', score: 812 },\n                   { name: 'Spurs', player_names: 'Logan, Ben', score: 56 }] },\n         { teams: [{ name: 'Celtics', player_names: 'Brian, Paul', score: 34 },\n                   { name: 'Spurs', player_names: 'Rachel, Helen', score: 10000 }] },\n         { teams: [{ name: 'Lakers', player_names: 'Nick, Liz', score: 92 },\n                   { name: 'Knicks', player_names: 'Bryant, Dave', score: 786 }] }]\n\nputs 'Seeding 3 games...'\n\ngames.each do |game_attrs|\n  game = Game.create\n  game_attrs[:teams].each do |team_attrs|\n    team = Team.new(team_attrs)\n    team.game = game\n    team.save\n  end\nend\n\nputs 'Done.'\n```\n\nNow if we go to the browser we can see some games.  You'll notice, though, that we're only seeing the properties that live on the `Game` model -- we're not seeing anything useful about the games' associated teams.  That's because when we say `render json: games`, Rails is under the hood looping over each game and calling a `to_json` method on it, which creates a JSON respresentation of the object including all of its properties.\n\nObviously for this to be useful we probably want to show information about teams also.  There are a couple of ways we could do this -- we could simply override the `to_json` method, or pass some options to it to tailor what we show.  But we usually go with a serialization library called `active_model_serializers`.\n\nLet's add that so we can tailor our JSON response.\n\nFirst we need to add a `has_many :teams` association to `Game` (I should probably move this earlier when we're talking about associations).\n\n```ruby\nclass Game \u003c ActiveRecord::Base\n  has_many :teams\nend\n```\n\nAdd ActiveModelSerializers to your project by adding this line to your application's Gemfile:\n\n```\ngem \"active_model_serializers\", \"0.8.3\"\n```\nAnd then execute:\n\n```\n$ bundle\n```\n\nGenerate a serializer:\n\n```\n$ rails g serializer game\n      create  app/serializers/game_serializer.rb\n```\n\nModify the serializer to include teams:\n\n```ruby\nclass GameSerializer \u003c ActiveModel::Serializer\n  attributes :id\n\n  has_many :teams\nend\n```\n\nNow if we go back to the browser, we'll see a couple things are different (we'll need to restart the server b/c we changed the Gemfile):\n- All the games are nested under a `games` key.\n- Timestamps are gone (only attributes specified in the serializer are displayed)\n- Teams are embedded under a `teams` key in each `game`\n  * This is the AM::S default\n  * If we wanted to sideload these instead, you could add `embed :ids` to your `GameSerializer`. Try that and see what happens.\n\n## Deploying to Heroku\n\n- Add the rails 12 factor gem\n\n- In the Heroku dashboard, create a new app (can also use `heroku create` command -- I don't b/c I have multiple Heroku accounts and it can get screwy)\n- Set environment variables, either via the Heroku dashboard or `heroku config:set \u003cVAR_NAME\u003e=\u003cvar\u003e`\n- Find the SSH URL and add it as a remote:\n\n```\n$ git remote add \u003cremote_name\u003e git@heroku.com:\u003cheroku_app_name\u003e.git\n```\n\n- Now to deploy:\n\n```\n$ git push \u003cremote_name\u003e master\n$ heroku run rake db:migrate\n$ heroku run rake db:seed\n```\n\nNow you should be able to navigate to `\u003cheroku_app_name\u003e.herokuapp.com/games` (can run `heroku open` to open the root URL in the browser)\n\n## Other helpful tidbits\n\n- View heroku logs: `heroku logs --tail`\n- `binding.pry`\n\n# Next Exercises\n\n1. Add a `Player` model\n\nA `Team` should have many `Player`s, and should no longer have a `player_names` attribute.\n\nA `Player` should have a first name, last name, and jersey number.\n\n2. Change the association between `Game` and `Team`\n\nRight now, if a `Team` plays multiple games, they'll show up twice in our database.  This isn't optimal because duplicated data provides more room for error.\n\nLet's change the data model so that `Team`s can play in multiple games.  Our model should look like this:\n\n```\nGame\n-------\nid\n\nCompetingTeam\n--------------\nid\ngame_id\nteam_id\nscore\n\nTeam\n------\nid\nname\n\nPlayer\n-------\nid\nfirst_name\nlast_name\njersey_number\n```\n\nUpdate the `GET /games` endpoint to return information on the competing teams.\n\n```json\n{\n  \"games\": [\n    {\n      \"id\": 1,\n      \"competing_teams\": [\n        {\n          \"id\": 3,\n          \"name\": \"Rams\",\n          \"score\": 90\n        },\n        {\n          \"id\": 7,\n          \"name\": \"Lakers\",\n          \"score\": 72\n        }\n      ]\n    }\n  ]\n}\n```\n\n3. Add a `GET /teams` endpoint\n\nThis endpoint should return a list of teams with each team's ID and name.\n\n```json\n{\n  \"teams\": [\n    {\n      \"id\": 3,\n      \"name\": \"Rams\"\n    },\n    {\n      \"id\": 7,\n      \"name\": \"Lakers\"\n    }\n  ]\n}\n```\n\n4. Add a `GET /teams/:id` endpoint\n\nThis endpoint should return all the information we have about a `Team`, including its name, a list of players, and a list of the games they've played.\n\n```json\n{\n  \"team\": {\n    \"id\": 3,\n    \"name\": \"Rams\",\n    \"players\": [\n      {\n        \"id\": 17,\n        \"first_name\": \"David\",\n        \"last_name\": \"Rodriguez\",\n        \"jersey_number\": 25\n      }\n    ],\n    \"games\": [\n      {\n        \"id\": 1,\n        \"score\": 90,\n        \"opposing_team_name\": \"Lakers\",\n        \"opposing_team_score\": 72\n      }\n    ]\n  }\n}\n```\n\n# Basic Suspenders stuff\n\n## Getting Started\n\nAfter you have cloned this repo, run this setup script to set up your machine\nwith the necessary dependencies to run and test this app:\n\n    % ./bin/setup\n\nIt assumes you have a machine equipped with Ruby, Postgres, etc. If not, set up\nyour machine with [this script].\n\n[this script]: https://github.com/thoughtbot/laptop\n\nAfter setting up, you can run the application using [Heroku Local]:\n\n    % heroku local\n\n[Heroku Local]: https://devcenter.heroku.com/articles/heroku-local\n\n## Guidelines\n\nUse the following guides for getting things done, programming well, and\nprogramming in style.\n\n* [Protocol](http://github.com/thoughtbot/guides/blob/master/protocol)\n* [Best Practices](http://github.com/thoughtbot/guides/blob/master/best-practices)\n* [Style](http://github.com/thoughtbot/guides/blob/master/style)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fintrepidpursuits%2Fintrepid-jams-workshop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fintrepidpursuits%2Fintrepid-jams-workshop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fintrepidpursuits%2Fintrepid-jams-workshop/lists"}