{"id":26733462,"url":"https://github.com/amcaplan/hot_drinks","last_synced_at":"2025-07-27T10:18:04.864Z","repository":{"id":36060969,"uuid":"40359800","full_name":"amcaplan/hot_drinks","owner":"amcaplan","description":"Hot Drinks: A Rails Engine Tutorial (work-in-progress)","archived":false,"fork":false,"pushed_at":"2015-08-11T14:27:53.000Z","size":180,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":2,"default_branch":"master","last_synced_at":"2023-04-10T18:25:28.550Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Ruby","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/amcaplan.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"MIT-LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2015-08-07T12:39:19.000Z","updated_at":"2015-11-29T13:26:47.000Z","dependencies_parsed_at":"2022-09-04T19:50:34.348Z","dependency_job_id":null,"html_url":"https://github.com/amcaplan/hot_drinks","commit_stats":null,"previous_names":[],"tags_count":null,"template":null,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amcaplan%2Fhot_drinks","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amcaplan%2Fhot_drinks/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amcaplan%2Fhot_drinks/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/amcaplan%2Fhot_drinks/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/amcaplan","download_url":"https://codeload.github.com/amcaplan/hot_drinks/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":245954845,"owners_count":20699876,"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-03-28T01:49:29.192Z","updated_at":"2025-03-28T01:49:29.716Z","avatar_url":"https://github.com/amcaplan.png","language":"Ruby","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Hot Drinks\n\nThis is meant to serve as a guide to writing your own Rails engines, or at least\nunderstanding how they work.  Look through the commits and watch the Readme grow\nas the engine develops.\n\nHot Drinks is a little toy app which will exercise the full MVC architecture and\nshow how each piece might be used in a real engine.\n\n## Generating a Rails Engine\n\nWe start by using the Rails generator for plugins.  You can generate regular,\nfull, or mountable plugins.  For more information on regular plugins, you can\ncheck out [the Rails guide on the topic][Rails plugin guide].  They're usually\nmeant for smaller bits of more experimental functionality.\n\nFull or mountable plugins are full-fledged Rails Engines.  This means that they\nact as miniature applications, and have a similar directory structure.  The\nmajor difference between full and mountable plugins is that mountable plugins\nare entirely namespaced, whereas full plugins are left in the namespace of your\nwhole application.  This makes full plugins far more dangerous to use.\n\nThe command to generate a Rails Engine is `rails plugin new plugin_name` with\nthe `--full` flag for a full plugin, or - you guessed it - the `--mountable`\nflag for a mountable plugin.  For this tutorial, we'll stick with a mountable\nplugin.\n\nSince I'm a fan of RSpec, we'll skip the built-in tests and add in RSpec\nourselves.\n\n```\n$ rails plugin new hot_drinks --mountable --skip-test-unit --dummy-path=spec/test_app\n```\n\nWhat's that last bit?  The generator wants to encourage good testing practices,\nso it actually builds a full-fledged app which will have our plugin mounted into\nit.  So we can test the integration of our app with another app.  Sweet!\nUnfortunately for us, it assumes you are using the `test/` directory for\ntesting, rather than the `spec/` directory.  So we're just moving that over.\n\nBefore we continue, let's turn this into a git repository.\n\n```\n$ git init\n$ git add .\n$ git commit -m \"Initial commit\"\n```\n\nGreat!  Now let's look at our folder structure.\n\n## The Structure of a Rails Engine\n\nAt the top level, we have some familiar filenames and directories:\n```\n$ ls\nGemfile\nGemfile.lock\nMIT-LICENSE\nREADME.md\nRakefile\napp\nbin\nconfig\nhot_drinks.gemspec\nlib\nspec\n```\n\nThis looks like a regular Rails app, except that it has a `gemspec` file,\nmeaning it's all ready to be gemified and bundled in with your applications.\n\n## Getting Ready for Testing\n\nTo make sure we're ready to test with RSpec, we'll add our dependencies to the\ngemspec.  (Generally, when you're developing gems, it's best to avoid using the\nGemfile directly; see [Yehuda Katz's blog post about it][YK on gem gemfiles] for\nthe full explanation.)  We'll also add FactoryGirl for model generation.\n\n``` ruby\ns.add_development_dependency \"rspec-rails\", \"~\u003e 3.0\"\ns.add_development_dependency \"factory_girl_rails\", \"~\u003e 4.5\"\n```\n\nWhile we're there, may as well add a homepage, summary, and description.\n\nOnce that's done, we need to\n\n```\n$ bundle install\n```\n\nto get our gems ready to go, and\n\n```\n$ rails generate rspec:install\n```\n\nto get our `spec_helper.rb` file ready.\n\n### Updating Generators\n\nNext, we'll tell our engine that we're using RSpec and FactoryGirl, so the Rails\ngenerators will generate tests appropriately.\n\nLook at the file `lib/hot_drinks/engine.rb`:\n\n``` ruby\nmodule HotDrinks\n  class Engine \u003c ::Rails::Engine\n    isolate_namespace HotDrinks\n  end\nend\n\n```\n\nLet's understand this before we modify it.  The `isolate_namespace` line exists\nbecause we told Rails to make this a mountable plugin.  So everything we create\nwill exist inside the `HotDrinks` namespace.  This line is super important!\n\nWe're going to add a few lines to the file, so it should now look like:\n\n``` ruby\nmodule HotDrinks\n  class Engine \u003c ::Rails::Engine\n    isolate_namespace HotDrinks\n\n    config.generators do |g|\n      g.test_framework :rspec\n      g.fixture_replacement :factory_girl, :dir =\u003e 'spec/factories'\n    end\n  end\nend\n\n```\n\nAnd we're ready to get to work.\n\n## Adding Our Resources\n\nLet's get down to our business rules now.  Essentially, we want to have coffee\nand tea machines available.  They both basically work the same, just that one\nproduces coffee and the other produces tea.  We'll want to have a `Machine`\nmodel representing our brewing machines, a `Drink` model representing the drinks\nwe generate, and a `DrinkType` model that will associate each drink and machine\nas coffee or tea.  (We may want to add more drinks later - perhaps some apple\ncider in the autumn?)\n\nOur UX department has decided that we will have a page for each machine we\ncreate, and that page will display available cups of coffee or tea created by\nthat machine.  Users can drink what's there, deleting the record from our\ndatabase.\n\n### Generating a Model\n\nLet's begin with our `DrinkType`s - they're straightforward enough.\n\n```\n$ rails generate model drink_type name:string\n      invoke  active_record\n      create    db/migrate/20150809110816_create_hot_drinks_drink_types.rb\n      create    app/models/hot_drinks/drink_type.rb\n      invoke    rspec\n      create      spec/models/hot_drinks/drink_type_spec.rb\n      invoke      factory_girl\n      create        spec/factories/hot_drinks_drink_types.rb\n```\n\nExcellent, our engine used RSpec and FactoryGirl to test our model.  Note that\nboth the model and the spec files are namespaced.  This is reflected in the code\nitself as well:\n\n``` ruby\nmodule HotDrinks\n  class DrinkType \u003c ActiveRecord::Base\n  end\nend\n\n```\n\nIf you're extra sharp, you may also have noticed that the migration file looks a\nlittle different than you might expect:\n\n``` ruby\nclass CreateHotDrinksDrinkTypes \u003c ActiveRecord::Migration\n  def change\n    create_table :hot_drinks_drink_types do |t|\n      t.string :name\n\n      t.timestamps null: false\n    end\n  end\nend\n\n```\n\nEven the migration and table name are namespaced.  This way, they're less likely\nto conflict with your application, and you could theoretically have a separate\n`DrinkType` on the global level.\n\n### Generating a Scaffold\n\nThe next step is going to be a little crazy.  We're going to generate a scaffold\nfor `Machine`s.  You shouldn't do this in your actual code, but we're trying to\nwork through this tutorial quickly, to show how this works.\n\nSince we want to keep our Rails engine as slim as possible, we're going to trim\nmuch of the fat off of the scaffold generation, just taking the bits we need.\n\n```\n$ rails generate scaffold machine name:string drink_type:references --no-assets --no-helper --no-view-specs --no-helper-specs --integration-tool=rspec\n      invoke  active_record\n      create    db/migrate/20150809112400_create_hot_drinks_machines.rb\n      create    app/models/hot_drinks/machine.rb\n      invoke    rspec\n      create      spec/models/hot_drinks/machine_spec.rb\n      invoke      factory_girl\n      create        spec/factories/hot_drinks_machines.rb\n      invoke  resource_route\n       route    resources :machines\n      invoke  scaffold_controller\n      create    app/controllers/hot_drinks/machines_controller.rb\n      invoke    erb\n      create      app/views/hot_drinks/machines\n      create      app/views/hot_drinks/machines/index.html.erb\n      create      app/views/hot_drinks/machines/edit.html.erb\n      create      app/views/hot_drinks/machines/show.html.erb\n      create      app/views/hot_drinks/machines/new.html.erb\n      create      app/views/hot_drinks/machines/_form.html.erb\n      invoke    rspec\n      create      spec/controllers/hot_drinks/machines_controller_spec.rb\n      create      spec/routing/hot_drinks/machines_routing_spec.rb\n      invoke      rspec\n      create        spec/requests/hot_drinks/hot_drinks_machines_spec.rb\n```\n\nSee how the controller and views are namespaced?  Cool.  You'll also note that\nthere was a route added to our `config/routes.rb`.  More on that later.\n\nYou'll note that I'm not really doing much with the tests.  We'll get there\nsoon.\n\n### One More Model...\n\nNow it's time for our hot drinks!  Since they don't get views of their own, we\njust need a model, plus a controller with just a `DELETE` action.  Let's\ngenerate our hot drinks that way:\n\n```\n$ rails generate model drink machine:references\n      invoke  active_record\n      create    db/migrate/20150809114923_create_hot_drinks_drinks.rb\n      create    app/models/hot_drinks/drink.rb\n      invoke    rspec\n      create      spec/models/hot_drinks/drink_spec.rb\n      invoke      factory_girl\n      create        spec/factories/hot_drinks_drinks.rb\n```\n\nNow let's make sure our models are wired up properly in the code.\n\n``` ruby\nmodule HotDrinks\n  class Drink \u003c ActiveRecord::Base\n    belongs_to :machine\n  end\nend\n\n```\n\n`Drink` seems to be connected to `Machine`, as we'd expect.  To make sure it has\na `DrinkType` as well, we'll just add\n\n``` ruby\nhas_one :drink_type, through: :machine\n```\n\nNow to to wire up the `Machine` itself!  Just need to add\n\n``` ruby\nhas_many :drinks\n```\n\nFinally, let's set up associations for `DrinkType`:\n\n``` ruby\n  has_many :machines\n  has_many :drinks, through: :machines\n```\n\n## Starting with Specs\n\n### Getting Our Environment Right\n\nAlright, let's jump in and start testing toward the behavior we wanted. So just\nrun `rspec` on the command line...\n\n```\n$ rspec\n/Users/acaplan/Development/code/hot_drinks/spec/rails_helper.rb:3:in `require': cannot load such file -- /Users/acaplan/Development/code/hot_drinks/config/environment (LoadError)\n  from /Users/acaplan/Development/code/hot_drinks/spec/rails_helper.rb:3:in `\u003ctop (required)\u003e'\n  from /Users/acaplan/Development/code/hot_drinks/spec/controllers/hot_drinks/machines_controller_spec.rb:1:in `require'\n  from /Users/acaplan/Development/code/hot_drinks/spec/controllers/hot_drinks/machines_controller_spec.rb:1:in `\u003ctop (required)\u003e'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1327:in `load'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1327:in `block in load_spec_files'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1325:in `each'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1325:in `load_spec_files'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:102:in `setup'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:88:in `run'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:73:in `run'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:41:in `invoke'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/exe/rspec:4:in `\u003ctop (required)\u003e'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/bin/rspec:23:in `load'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/bin/rspec:23:in `\u003cmain\u003e'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/bin/ruby_executable_hooks:15:in `eval'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/bin/ruby_executable_hooks:15:in `\u003cmain\u003e'\n```\n\nOh no!  What happened?  Well, it turns out that our engine isn't really a\nfull-fledged Rails app after all.  It doesn't have the usual\n`config/environment.rb` file used to get things going.  All we have in the\n`config` directory is `routes.rb`.  What to do?\n\nMy recommendation is this.  Remember that test app we created all the way at the\nbeginning?  We'll use it as a test environment for our app.  So in\n`spec/rails_helper.rb`, change line 3 to read:\n\n``` ruby\nrequire File.expand_path('../../spec/test_app/config/environment', __FILE__)\n```\n\n### A Side Note About the Test App\n\nSince we're already talking about it, let's dive into the test app just a bit.\n\nOn its surface, it looks like a regular Rails app with no models, views, or\ncontrollers.  But there are two important differences.\n\nFirst, look at `spec/test_app/Gemfile`.  Oh, just kidding, there isn't one.  The\ndummy app isn't 100% complete; it's relying on the top-level `Gemfile`.\n\nSecond, look at `spec/test_app/config/routes.rb`.  It's there this time, scout's\nhonor!\n\n``` ruby\nRails.application.routes.draw do\n\n  mount HotDrinks::Engine =\u003e \"/hot_drinks\"\nend\n\n```\n\nThat `mount` line is what you'll be adding to any app that integrates your\nengine.  In fact, if you run `rake routes` from the `test_app` directory, this\nis what it looks like:\n\n```\n$ rake routes\n    Prefix Verb URI Pattern Controller#Action\nhot_drinks      /hot_drinks HotDrinks::Engine\n\nRoutes for HotDrinks::Engine:\n    machines GET    /machines(.:format)          hot_drinks/machines#index\n             POST   /machines(.:format)          hot_drinks/machines#create\n new_machine GET    /machines/new(.:format)      hot_drinks/machines#new\nedit_machine GET    /machines/:id/edit(.:format) hot_drinks/machines#edit\n     machine GET    /machines/:id(.:format)      hot_drinks/machines#show\n             PATCH  /machines/:id(.:format)      hot_drinks/machines#update\n             PUT    /machines/:id(.:format)      hot_drinks/machines#update\n             DELETE /machines/:id(.:format)      hot_drinks/machines#destroy\n```\n\nFamiliar but different.  Essentially, all the routes we've created are\nnamespaced to whatever we specify in `routes.rb`.  So if we wanted to move our\nengine to a different route, we could change that line to\n\n``` ruby\nmount HotDrinks::Engine =\u003e \"/sparkly_dinosaurs\"\n```\n\nand we'd see\n\n```\n$ rake routes\n    Prefix Verb URI Pattern        Controller#Action\nhot_drinks      /sparkly_dinosaurs HotDrinks::Engine\n\nRoutes for HotDrinks::Engine:\n    machines GET    /machines(.:format)          hot_drinks/machines#index\n             POST   /machines(.:format)          hot_drinks/machines#create\n new_machine GET    /machines/new(.:format)      hot_drinks/machines#new\nedit_machine GET    /machines/:id/edit(.:format) hot_drinks/machines#edit\n     machine GET    /machines/:id(.:format)      hot_drinks/machines#show\n             PATCH  /machines/:id(.:format)      hot_drinks/machines#update\n             PUT    /machines/:id(.:format)      hot_drinks/machines#update\n             DELETE /machines/:id(.:format)      hot_drinks/machines#destroy\n```\n\nNot very obvious, but you can see from up top that the `/sparkly_dinosaurs`\nroute is now our entry point into our engine.  It's up to use to realize that\n\n```\n    machines GET    /machines(.:format)          hot_drinks/machines#index\n```\nmeans, \"When you perform a `GET` request to the engine's route\n(`/sparkly_dinosaurs`) followed by `/machines`, you get to the controller action\n`hot_drinks/machines#index`.\"\n\nAnyway, make sure we're back to\n\n``` ruby\nmount HotDrinks::Engine =\u003e \"/hot_drinks\"\n```\n\nbefore we continue.\n\n### Setting up our database\n\nRunning `rspec` again, we see a new error.\n\n```\n$ rspec\n/Users/acaplan/Development/code/hot_drinks/spec/test_app/db/schema.rb doesn't exist yet. Run `rake db:migrate` to create it, then try again. If you do not intend to use a database, you should instead alter /Users/acaplan/Development/code/hot_drinks/spec/test_app/config/application.rb to limit the frameworks that will be loaded.\n/Users/acaplan/.rvm/gems/ruby-2.2.1/gems/activerecord-4.2.3/lib/active_record/migration.rb:392:in `check_pending!':  (ActiveRecord::PendingMigrationError)\n\nMigrations are pending. To resolve this issue, run:\n\n  bin/rake db:migrate RAILS_ENV=test\n\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/activerecord-4.2.3/lib/active_record/migration.rb:405:in `load_schema_if_pending!'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/activerecord-4.2.3/lib/active_record/migration.rb:411:in `block in maintain_test_schema!'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/activerecord-4.2.3/lib/active_record/migration.rb:639:in `suppress_messages'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/activerecord-4.2.3/lib/active_record/migration.rb:416:in `method_missing'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/activerecord-4.2.3/lib/active_record/migration.rb:411:in `maintain_test_schema!'\n  from /Users/acaplan/Development/code/hot_drinks/spec/rails_helper.rb:27:in `\u003ctop (required)\u003e'\n  from /Users/acaplan/Development/code/hot_drinks/spec/controllers/hot_drinks/machines_controller_spec.rb:1:in `require'\n  from /Users/acaplan/Development/code/hot_drinks/spec/controllers/hot_drinks/machines_controller_spec.rb:1:in `\u003ctop (required)\u003e'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1327:in `load'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1327:in `block in load_spec_files'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1325:in `each'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/lib/rspec/core/configuration.rb:1325:in `load_spec_files'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:102:in `setup'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:88:in `run'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:73:in `run'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/lib/rspec/core/runner.rb:41:in `invoke'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/rspec-core-3.3.2/exe/rspec:4:in `\u003ctop (required)\u003e'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/bin/rspec:23:in `load'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/bin/rspec:23:in `\u003cmain\u003e'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/bin/ruby_executable_hooks:15:in `eval'\n  from /Users/acaplan/.rvm/gems/ruby-2.2.1/bin/ruby_executable_hooks:15:in `\u003cmain\u003e'\n```\n\nAt least this error message tells us what to do!\n\n```\n$ bin/rake db:migrate RAILS_ENV=test\n-bash: bin/rake: No such file or directory\n```\n\n...or not.  So it turns out, our engine doesn't have bin/rake either.\n\nNoticing a pattern?\n\nEssentially, our engine both is and isn't a mini-app.  It's structured like a\nrun-of-the-mill Rails app, but lacks many of the standard features so it can be\nmore lightweight.  That's why the dummy app exists, to fill in that stuff for\ntesting purposes.\n\nNote, though, that we do have a `Rakefile` in the engine's top-level directory,\nand bundler installed rake.  So just remove the preface from that command.\n\n```\n$ rake db:migrate RAILS_ENV=test\n/Users/acaplan/.rvm/gems/ruby-2.2.1/gems/railties-4.2.3/lib/rails/tasks/statistics.rake:4: warning: already initialized constant STATS_DIRECTORIES\n/Users/acaplan/.rvm/gems/ruby-2.2.1/gems/railties-4.2.3/lib/rails/tasks/statistics.rake:4: warning: previous definition of STATS_DIRECTORIES was here\n== 20150809110816 CreateHotDrinksDrinkTypes: migrating ========================\n-- create_table(:hot_drinks_drink_types)\n   -\u003e 0.0011s\n== 20150809110816 CreateHotDrinksDrinkTypes: migrated (0.0012s) ===============\n\n== 20150809112400 CreateHotDrinksMachines: migrating ==========================\n-- create_table(:hot_drinks_machines)\n   -\u003e 0.0008s\n== 20150809112400 CreateHotDrinksMachines: migrated (0.0009s) =================\n\n== 20150809114923 CreateHotDrinksDrinks: migrating ============================\n-- create_table(:hot_drinks_drinks)\n   -\u003e 0.0007s\n== 20150809114923 CreateHotDrinksDrinks: migrated (0.0008s) ===================\n```\n\nNo failure this time!  Check out our schema now (`spec/test_app/db/schema.rb`):\n\n``` ruby\n# encoding: UTF-8\n# This file is auto-generated from the current state of the database. Instead\n# of editing this file, please use the migrations feature of Active Record to\n# incrementally modify your database, and then regenerate this schema definition.\n#\n# Note that this schema.rb definition is the authoritative source for your\n# database schema. If you need to create the application database on another\n# system, you should be using db:schema:load, not running all the migrations\n# from scratch. The latter is a flawed and unsustainable approach (the more migrations\n# you'll amass, the slower it'll run and the greater likelihood for issues).\n#\n# It's strongly recommended that you check this file into your version control system.\n\nActiveRecord::Schema.define(version: 20150809130712) do\n\n  create_table \"hot_drinks_drink_types\", force: :cascade do |t|\n    t.string   \"name\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  create_table \"hot_drinks_drinks\", force: :cascade do |t|\n    t.integer  \"machine_id\"\n    t.datetime \"created_at\", null: false\n    t.datetime \"updated_at\", null: false\n  end\n\n  add_index \"hot_drinks_drinks\", [\"machine_id\"], name: \"index_hot_drinks_drinks_on_machine_id\"\n\n  create_table \"hot_drinks_machines\", force: :cascade do |t|\n    t.string   \"name\"\n    t.integer  \"drink_type_id\"\n    t.datetime \"created_at\",    null: false\n    t.datetime \"updated_at\",    null: false\n  end\n\n  add_index \"hot_drinks_machines\", [\"drink_type_id\"], name: \"index_hot_drinks_machines_on_drink_type_id\"\n\nend\n\n```\n\nWe're finally ready to get started with testing.\n\n```\n$ rspec\n**F****************FFFFFFFFF\n\n# ...\n\nFinished in 0.06708 seconds (files took 1.15 seconds to load)\n28 examples, 10 failures, 18 pending\n\nFailed examples:\n\nrspec ./spec/controllers/hot_drinks/machines_controller_spec.rb:57 # HotDrinks::MachinesController GET #new assigns a new machine as @machine\nrspec ./spec/requests/hot_drinks/hot_drinks_machines_spec.rb:5 # Machines GET /hot_drinks_machines works! (now write some real specs)\nrspec ./spec/routing/hot_drinks/machines_routing_spec.rb:7 # HotDrinks::MachinesController routing routes to #index\nrspec ./spec/routing/hot_drinks/machines_routing_spec.rb:11 # HotDrinks::MachinesController routing routes to #new\nrspec ./spec/routing/hot_drinks/machines_routing_spec.rb:15 # HotDrinks::MachinesController routing routes to #show\nrspec ./spec/routing/hot_drinks/machines_routing_spec.rb:19 # HotDrinks::MachinesController routing routes to #edit\nrspec ./spec/routing/hot_drinks/machines_routing_spec.rb:23 # HotDrinks::MachinesController routing routes to #create\nrspec ./spec/routing/hot_drinks/machines_routing_spec.rb:27 # HotDrinks::MachinesController routing routes to #update via PUT\nrspec ./spec/routing/hot_drinks/machines_routing_spec.rb:31 # HotDrinks::MachinesController routing routes to #update via PATCH\nrspec ./spec/routing/hot_drinks/machines_routing_spec.rb:35 # HotDrinks::MachinesController routing routes to #destroy\n```\n\nOK, so we have a lot of failing specs.  I'm happy to live with that for now,\nsince those are all auto-generated specs.  We'll overwrite them (or delete as\nnecessary) as we go.\n\n### Seeding the database\n\nBefore we start writing tests, let's make our lives a bit easier.  We only know\nabout 2 drink types: coffee and tea.  And we want them to be always there.  So\nlet's seed our database.\n\nWe want to set up a Rake task to do this for us.  So open up\n`lib/tasks/hot_drinks_tasks.rake` and clear out the file.  Then put this in:\n\n``` ruby\ndesc \"seed the database with tea and coffee DrinkTypes\"\nnamespace :hot_drinks do\n  namespace :db do\n    task :seed do\n      HotDrinks::DrinkType.create!(name: \"tea\")\n      HotDrinks::DrinkType.create!(name: \"coffee\")\n    end\n  end\nend\n\n```\n\nWe're making sure to namespace our tasks so they don't conflict with whatever is\nin the app this ultimately gets integrated into.\n\nNow we need to drop into our test app directory and run:\n\n```\n$ rake hot_drinks:db:seed RAILS_ENV=test\n```\n\nTo confirm, let's open up the Rails console in test mode:\n\n``` ruby\n$ rails console -e test\nLoading test environment (Rails 4.2.3)\n2.2.1 :001 \u003e HotDrinks::DrinkType.all\n  HotDrinks::DrinkType Load (0.8ms)  SELECT \"hot_drinks_drink_types\".* FROM \"hot_drinks_drink_types\"\n =\u003e #\u003cActiveRecord::Relation [#\u003cHotDrinks::DrinkType id: 1, name: \"tea\", created_at: \"2015-08-09 18:44:18\", updated_at: \"2015-08-09 18:44:18\"\u003e, #\u003cHotDrinks::DrinkType id: 2, name: \"coffee\", created_at: \"2015-08-09 18:44:18\", updated_at: \"2015-08-09 18:44:18\"\u003e]\u003e\n```\n\nNow we can get started on some test-driven development.  Let's do it!\n\n## TDD to the Top!\n\n### What We Won't Test\n\nYou'll notice that earlier, we generated scaffolds and then didn't do much with\nthe test files.  This is because it's not really worth our while to test that\nRails works.  We can pretty much trust Rails to do what it does best.  Instead,\nour tests will focus on the logic specific to our application.\n\n### Brewing Functionality\n\nLet's start by asserting that a machine which receives the `#brew` command will\ngenerate a new, piping-hot cup of whatever it makes.\n\nSo let's add our first test to `spec/models/machine_spec.rb`:\n\n``` ruby\nrequire 'rails_helper'\n\nmodule HotDrinks\n  RSpec.describe Machine, type: :model do\n    subject {\n      FactoryGirl.create(:hot_drinks_machine, drink_type: drink_type)\n    }\n\n    let(:drink_type) {\n      FactoryGirl.create(:hot_drinks_drink_type, name: drink_name)\n    }\n\n    context 'a coffee machine' do\n      let(:drink_name) { 'coffee' }\n\n      before do\n        expect(subject.drinks).to be_empty\n      end\n\n      it 'brews a cup of coffee' do\n        subject.brew\n        brewed_drink = subject.drinks.first\n        expect(brewed_drink.drink_type.name).to eq(drink_name)\n      end\n    end\n  end\nend\n\n```\n\nand run the test!\n\n```\n$ rspec spec/models/hot_drinks/machine_spec.rb\nF\n\nFailures:\n\n  1) HotDrinks::Machine a coffee machine brews a cup of coffee\n     Failure/Error: subject.brew\n     NoMethodError:\n       undefined method `brew' for #\u003cHotDrinks::Machine:0x007f822ec5a578\u003e\n     # /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/activemodel-4.2.3/lib/active_model/attribute_methods.rb:433:in `method_missing'\n     # ./spec/models/hot_drinks/machine_spec.rb:21:in `block (3 levels) in \u003cmodule:HotDrinks\u003e'\n\nFinished in 0.03081 seconds (files took 0.93857 seconds to load)\n1 example, 1 failure\n\nFailed examples:\n\nrspec ./spec/models/hot_drinks/machine_spec.rb:20 # HotDrinks::Machine a coffee machine brews a cup of coffee\n\n```\n\nNow we'll add a `brew` method to our `Machine` model.\n\n``` ruby\nmodule HotDrinks\n  class Machine \u003c ActiveRecord::Base\n    belongs_to :drink_type\n    has_many :drinks\n\n    def brew\n    end\n  end\nend\n\n```\n\nand we get a more interesting failure:\n\n```\n$ rspec spec/models/hot_drinks/machine_spec.rb \nF\n\nFailures:\n\n  1) HotDrinks::Machine a coffee machine brews a cup of coffee\n     Failure/Error: expect(brewed_drink.drink_type.name).to eq('coffee')\n     NoMethodError:\n       undefined method `drink_type' for nil:NilClass\n     # ./spec/models/hot_drinks/machine_spec.rb:23:in `block (3 levels) in \u003cmodule:HotDrinks\u003e'\n\nFinished in 0.03424 seconds (files took 0.98238 seconds to load)\n1 example, 1 failure\n\nFailed examples:\n\nrspec ./spec/models/hot_drinks/machine_spec.rb:20 # HotDrinks::Machine a coffee machine brews a cup of coffee\n```\n\nWe're not actually brewing anything, so let's fix that.\n\n``` ruby\ndef brew\n  self.drinks \u003c\u003c Drink.new\nend\n```\n\nand the test passes.  It works just as well for tea:\n\n``` ruby\ncontext 'a tea machine' do\n  let(:drink_name) { 'tea' }\n\n  before do\n    expect(subject.drinks).to be_empty\n  end\n\n  it 'brews a cup of tea' do\n    subject.brew\n    brewed_drink = subject.drinks.first\n    expect(brewed_drink.drink_type.name).to eq(drink_name)\n  end\nend\n```\n\nJust to be sure, let's add a test to make sure we can access the drinks later.\n\n``` ruby\ncontext 'a coffee machine' do\n  let(:drink_name) { 'coffee' }\n\n  before do\n    expect(subject.drinks).to be_empty\n  end\n\n  it 'brews a cup of coffee' do\n    subject.brew\n    expect(brewed_drink.name).to eq(drink_name)\n  end\n\n  it 'persists the drink' do\n    expect { subject.brew }.to change{ Drink.count }.by(1)\n  end\nend\n```\n\nAnd it passes.\n\nThe truth is, though, I don't like that we need to spell out\n`brewed_drink.drink_type.name` - the brewed drink has a type, and it should know\nitself what that type is.  So let's change the tests a bit, and pull out a\nmethod while we're at it.\n\n``` ruby\ndef brewed_drink\n  subject.drinks.first\nend\n\ncontext 'a coffee machine' do\n  let(:drink_name) { 'coffee' }\n\n  before do\n    expect(subject.drinks).to be_empty\n  end\n\n  it 'brews a cup of coffee' do\n    subject.brew\n    expect(brewed_drink.name).to eq(drink_name)\n  end\n\n  it 'persists the drinks' do\n    expect { subject.brew }.to change{ Drink.count }.by(1)\n  end\nend\n\ncontext 'a tea machine' do\n  let(:drink_name) { 'tea' }\n\n  before do\n    expect(subject.drinks).to be_empty\n  end\n\n  it 'brews a cup of tea' do\n    subject.brew\n    expect(brewed_drink.name).to eq(drink_name)\n  end\nend\n```\n\nRunning the tests, they now fail:\n\n```\nundefined method `name' for #\u003cHotDrinks::Drink:0x007fe1328647a8\u003e\n```\n\nLet's define `#name` in the `Drink` model.  But not before we write a test!  So\nwe'll open up `spec/models/hot_drinks/drink_spec.rb` and change the contents to\nthe following:\n\n``` ruby\nrequire 'rails_helper'\n\nmodule HotDrinks\n  RSpec.describe Drink, type: :model do\n    subject {\n      FactoryGirl.create(:hot_drinks_drink, machine: machine)\n    }\n\n    let(:machine) {\n      FactoryGirl.create(:hot_drinks_machine, drink_type: drink_type)\n    }\n\n    let(:drink_type) {\n      FactoryGirl.create(:hot_drinks_drink_type, name: drink_name)\n    }\n\n    let(:drink_name) { 'coffee' }\n\n    describe 'getting the name of a drink' do\n      it 'pulls the name from the drink type' do\n        expect(subject.name).to eq(drink_name)\n      end\n    end\n  end\nend\n\n```\n\nAnd now we run the specs:\n\n```\n$ rspec spec/models/hot_drinks/drink_spec.rb\nF\n\nFailures:\n\n  1) HotDrinks::Drink getting the name of a drink pulls the name from the drink type\n     Failure/Error: expect(subject.name).to eq(drink_name)\n     NoMethodError:\n       undefined method `name' for #\u003cHotDrinks::Drink:0x007faf64fca880\u003e\n     # /Users/acaplan/.rvm/gems/ruby-2.2.1/gems/activemodel-4.2.3/lib/active_model/attribute_methods.rb:433:in `method_missing'\n     # ./spec/models/hot_drinks/drink_spec.rb:21:in `block (3 levels) in \u003cmodule:HotDrinks\u003e'\n\nFinished in 0.02473 seconds (files took 0.9402 seconds to load)\n1 example, 1 failure\n\nFailed examples:\n\nrspec ./spec/models/hot_drinks/drink_spec.rb:20 # HotDrinks::Drink getting the name of a drink pulls the name from the drink type\n\n```\n\nWe can fix that.\n\n``` ruby\nmodule HotDrinks\n  class Drink \u003c ActiveRecord::Base\n    belongs_to :machine\n    has_one :drink_type, through: :machine\n\n    def name\n    end\n  end\nend\n\n```\n\nAnd now a more interesting error:\n\n\n``` ruby\nFailures:\n\n  1) HotDrinks::Drink getting the name of a drink pulls the name from the drink type\n     Failure/Error: expect(subject.name).to eq(drink_name)\n\n       expected: \"coffee\"\n            got: nil\n\n       (compared using ==)\n     # ./spec/models/hot_drinks/drink_spec.rb:21:in `block (3 levels) in \u003cmodule:HotDrinks\u003e'\n```\n\nSo let's fill in the `#name` method.\n\n``` ruby\ndef name\n  drink_type.name\nend\n```\n\nwhich makes our test pass.  If we go back to our machine specs now, they pass as\nwell.\n\nDo we need any specs for `DrinkType`?  I don't think so, it's just doing all\nstandard, simple Rails stuff, and we can be pretty sure Rails works.  So let's\nclear the `pending` line from `spec/models/hot_drinks/drink_type_spec.rb`.  I\ndon't recommend deleting the file, in case we decide to add more interesting\nbehavior later.\n\nWe can now run `rspec spec/models` and see the following output:\n\n```\nrspec spec/models\n...\n\nFinished in 0.06789 seconds (files took 0.91903 seconds to load)\n4 examples, 0 failures\n```\n\nThe model specs are good to go!  At least for now.\n\n[Rails plugin guide]: http://guides.rubyonrails.org/plugins.html\n[YK on gem gemfiles]: http://yehudakatz.com/2010/12/16/clarifying-the-roles-of-the-gemspec-and-gemfile/\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famcaplan%2Fhot_drinks","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Famcaplan%2Fhot_drinks","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Famcaplan%2Fhot_drinks/lists"}