Creating a basic model and corresponding database is fundamental to creating many interactive websites, unfortunately, doing so is not straightforward. It involves creating files in Ruby on Rails and running a command to use that model to create the actual SQL-flavored database structure … the M of the MVC design pattern. You also need to actually add values to the dabase (aka, you need to seed it), create web pages that show that data (the V of MVC) and create methods that link the two (the C of MVC). And while there are many references for how to do any one of these steps, I have found it difficult to find a single resource that pulls it all together.
Hence, I have created this tutorial as an example.
If you want to change the name of the model, remember that you will need to be consistent throughout!!
References
I include these for you to review … possibly after you have created one model and database and can put the instructions in perspective.
- Hartl 2.3 unfortunately uses the scaffolding to do too much work, and it depends upon the relationship between a micropost and the user that created it.
- Hartl 13 depends upon the micropost being associated with the user that creates it
- Hartl 6 and 7 and 13 have the information you need, but there’s a lot more detail than required for a simple model and database
- Step-wise instructions, that are actually quite good, for how to seed a database from a .csv file: https://gist.github.com/arjunvenkat/1115bc41bf395a162084
Goals
- Create a simple model that has no associations with any other model (for now)
- Run the migration to create the database
- Seed the database from a .csv file
- Display the resulting database on a page
- Do the migration and seed on Heroku too
- Change controller and view to put the data where I want it to appear
Main Dish Model
This is an extremely simple model that will create a single table in the database and let you display that information on a page.
maindish | |
id | integer |
name | string |
created_at | datetime |
updated_at | datetime |
0) Create a New Test App
It is not required, but you might want to create a new app to use as a sandbox/playground for this exercise. Alternatively, you can simply add it the new table and views to your existing individual project. You should create a new branch in git so that you can ignore (and possibly delete) it after this exercise.
1) Generate the Model & Create Database
To start: create a new branch in git!!!
Then, you can generate the model:
rails generate model Maindish name:string
This creates the first migration, the model file (maindish.rb), a model test file, and a fixtures file. No controller or view is created automatically.
Since at this point, I am not relating tables in the database, I’ll just do the migration to create the database:
rails db:migrate
2) Initial Automated Testing
For now, validations are minimal and so are tests. We only need to test that the maindish has a name, really, but just to make sure that the web pages look good, let’s make the maindish name no more than 80 characters (following Hartl 13.1). Both of these tests are confirming that our model’s data will be valid when stored in the database, so we need to flesh out the tests in test/models/maindish_test.rb, which should have been generated when we created the maindish model itself.
We will first write tests that should fail (because we haven’t added any constraints to the model yet). So open test/models/maindish_test.rb and within class MaindishTest add the code for these two tests :
test "should not save maindish without a name" do maindish = Maindish.new assert_not maindish.save, "Saved a maindish without a name" end test "name should not be longer than 80 characters" do maindish = Maindish.new maindish.name = "a" * 81 assert_not maindish.valid?, "Maindish name too long" end
Note that each test must start with the word “test”. I did that wrong the first time I tried writing tests ….
If you run rails test at this point, the tests should fail because the model allows what we don’t want to happen!! This is actually a good thing because we know that the tests will catch errors. Now, we fix the code so that it won’t let the mistakes through to the database.
In app/models/maindish.rb, add a line to the model that will both validate that the name has some content and that it is short enough:
class Maindish < ApplicationRecord validates :name, presence: true, length: {maximum: 80} end
Once that is saved, run rails test again. This time, the tests should pass because the model validation code won’t let the mistakes through.
(See https://guides.rubyonrails.org/testing.html#your-first-failing-test for more information on writing tests. )
3) Have Data Show on a Webpage
The next important step for the simple menu is to have the maindishes, if any, appear on the a page. For now. Eventually, I envision a menu.html page that displays records from several fields, but this is a start toward that functionality. This is a simplification of Hartl 13.2.
We don’t need to reset and reseed the database at this point, but we need to create a controller for Maindishes. (I took a gamble and pluralized it since that seems to be the convention.)
rails generate controller Maindishes
Which created a controller, a set of views, a test, a helper file, javascript, and a stylesheet
What we want to do is 1) seed the database with a few maindishes so that I can, 2) display all maindishes as a test and then 3) make the random menu page show a single, randomly chosen maindish
In the db/seeds.rb file, I created three maindishes …. Such as Maindish.create(name: ‘Green eggs and ham’)
Then, I ran rails db:seed
You can verify that the seeding worked either by starting the rails console or by looking at the database with the SQLite3 database inspector. If you want to use the rails console, you need to be sure you have gem ‘rb-readline’ installed in the Gemfile.
To display all the maindishes on a page, I modified the controller to create an index method that gets all of the maindishes from the database and puts them into an instance variable.
class MaindishesController < ApplicationController def index maindish_list = Maindish.all @maindishes = maindish_list end end
I picked index as the method name because I plan to put this on the index.html page in the maindishes view.
I created the index.html.erb file to show the whole list. It’s very primitive at the moment …
<% provide(:title, ‘Maindishes’) %> <h1>Main Dishes</h1> <ul> <% @maindishes.each do | maindish | %> <li><%= maindish.name %></li> <% end %> </ul>
I modified the routes.rb file to include a line to let it know that the standard REST file mappings should happen for the maindishes table/model/resource
resources :maindishes
Run the test suite to make sure you haven’t broken anything along the way, then start the server and navigate to the index file for maindishes …..
localhost:3000/maindishes
And it should work!!
4) More Realistic Dummy Data
Next step is to seed the database from a .csv file (the original instructions are at: https://gist.github.com/arjunvenkat/1115bc41bf395a162084)
Create a directory/folder called seeds inside of the lib directory/folder and create a .csv with your data inside of that directory. Make sure you have columns for each data field that will be in your database. You can use column headers and those will not become part of the database, but should help identify which fields for each row in the spreadsheet should map to the row in the database.
The seeds.rb file should end up looking like this:
require 'csv' csv_text = File.read(Rails.root.join('lib', 'seeds', 'maindishes.csv')) csv = CSV.parse(csv_text, :headers => true, :encoding => 'ISO-8859-1') csv.each do |row| m = Maindish.new m.name = row['name'] m.save puts "#{m.name} saved" end puts "There are now #{Maindish.count} rows in the Maindishes table."
For testing, I just had one column, but multiple columns should work. Also, if you want to know what each line means, refer to the original instructions.
When you have created the file, do rails db:seed to seed the database.
To check that this actually worked, I went into the rails console to query the Maindish.count and check what Maindish.first was in the database. To test it repeatedly, I dropped the database with rails db:drop and then rails db:migrate to rerun the latest migration file. If you do rails db:reset, it will drop, migrate and rerun your seed in one step, which I didn’t want since I wanted to be sure that my database information was gone entirely before I seeded it again.
After you have it running on your local machine and have run your automated tests (which should pass), you can push it to both GitHub and Heroku. Remember, after pushing to Heroku, you will need to run the database commands AGAIN on Heroku to create (or modify) the database and seed the database from your seed.rb file:
rails db:migrate rails db:seed
Note: If you don’t want to generate any data on your own, you can use the Faker gem to generate bogus content (see instructions at: https://www.railscarma.com/blog/technical-articles/faker-gem-fake-data-generation-ruby/). This gem can create realistic data for a wide range of data types and situations!!
5) Display a Single, Random Main Dish on a Different Page
Ok, this is all a good start, but what I want is to get a single, random main dish and display it on the home page. Or maybe on a menu page as a precursor to having multiple dishes displayed to create a random menu. I want to break free of the automagic and put information from the database where I want it. This is good practice to confirm that you understand the ideas.
First step is to change the controller that is associated with the page where I want the data to appear and change the method that runs that particular page. In my case, I changed the static_pages_controller.rb and the home method to create an instance variable that contained the information that I wanted to display:
def home maindish_list = Maindish.all dishNum = (0..maindish_list.length-1).to_a.shuffle.first @maindish = maindish_list[dishNum] end
Next, change the view where you want to display the data (home.html.erb) by adding a line such as:
<p>A random main dish for your consideration: <%= @maindish.name %></p>
NOTE: I ran into trouble with Heroku when I tried to migrate and then reseed the database. Since it is a “production” database, it didn’t want to lose the values currently in the database. It would let me do rails db:seed, but that duplicated the values in the Maindishes table. You need to make it reset the database using a pg command to do so with these instructions: https://gist.github.com/zulhfreelancer/ea140d8ef9292fa9165e. Since I am not using the command line tools for Heroku, I used the web based Heroku tools:
- Log into Heroku and pick the app that you are working with
- Under “Installed add ons”, you should see Heroku Postgress. Click that add on to get to the configuration menu.
- Pick the “settings” tab
- Pick “reset the database”
- It will ask you if you’re really, really sure you want to do this. Confirm. This will dump all of the data in the database
- Get back to the dashboard for the app you are working on
- Under the “More” button, pick “run console”
- Run “rails db:migrate”
- If applicable, run “rails db:seed” to re-seed the database.
The command line commands can be found in Hartl Chapter 13.