Ruby on Rails: Suggested Project Flow April 26th, 2015
-
Sketch Use Cases, Page Flow, & Basic Data
- Identify basic use cases, sketch page flow on paper (or simple drawing app), and identify basic data (e.g. product which will have a name, description, image, and price)
- Ambiguity is ok at this stage since Ruby on Rails is great for agile, iterative development
- Pick a part of the app to start developing which makes the most sense as a starting point (e.g. the web interface for maintaining product information)
- Develop in small iterations which involve multiple steps
- Identify basic use cases, sketch page flow on paper (or simple drawing app), and identify basic data (e.g. product which will have a name, description, image, and price)
-
Create a New App
- run rails new appname
- Open in Sublime Text 2 and add the following to the .gitignore
# Ignore Sublime project and workspace files *.sublime-*
- Create empty repo on GitHub
- cd into project and run the following
git init git remote add origin git@github.com:username/appname.git git add . git commit git push origin master
-
Create the Scaffold
- The scaffold is the model, views, controller, and migration for a table
- In Rails, a model is automatically mapped to a database table whose name is the plural form of the model’s class
- rails generate scaffold Product title:string description:text image_url:string price:decimal
- You can reverse a generation with rails d scaffold Product
- Run rake db:migrate to get Rails to apply the migration to the development database
- A migration represents a change we want to make to the data, expressed in a source file in database-independent terms
- If you edit the migration (a.k.a. modify the schema)(e.g. 20150426201937_create_products.rb), run rake db:migrate again
- Note: At this point you can start the server with bin/rails server and navigate to the page created with the controller name used in the scaffold generation (e.g. from step two it was Product, so now you can see it at http://localhost:3000/products)
- Database fun…
- rake db:rollback … to rollback the migration
- rake db:migrate … to redo the migration
- rake db:seed … to reseed the database
- Run rake test to run the tests
-
Populate Test Data
- Modify seeds.rb with test data
- seeds.rb is used for populating a table by running the create!() method on the model with the same name
- In general, methods that end in ! (Ruby’s “dangerous methods”) indicate that the method will modify the object’s state
- seeds.rb is used for populating a table by running the create!() method on the model with the same name
- You can use %{…} in place of “…” for long strings
- Run rake db:seed to populate the development database
- If your seeds.rb starts with (for example) Product.delete_all ANY and ALL data you may have in that table/class will be replaced
- Modify seeds.rb with test data
-
Edit Styles
- app/views/layouts/application.html.erb is the main layout file
- Adding class='<%= controller.controller_name %>’ to the body tag of the main layout file will dynamically set a CSS class for each controller
- Some useful helper methods for views…
- Alternate class names in a table with class=”<%= cycle(‘list_line_odd’, ‘list_line_even’) %>”
- Remove HTML markup from descriptions and truncate them to n characters …
- <%= truncate(strip_tags(product.description), length: 80) %>
- Set a browser alert to confirm data deletion … <%= link_to ‘Destroy’, product, method: :delete, data: { confirm: ‘Are you sure?’ } %>
- The method: :delete parameter is used to indicate that an HTTP DELETE method should be used for this hyperlink, routing the request to the proper action in the controller
-
Validation and Unit Testing
- Add validations to the model (the gatekeeper to the database) and test data to the test controller
- e.g. validate that certain fields are not empty … validates :title, :description, :image_url, presence: true
- e.g. validate a price of at least one penny … validates :price, numericality: {greater_than_or_equal_to: 0.01}
- e.g. validate uniqueness … validates :title, uniqueness: true
- e.g. validate against a regex …
validates :image_url, allow_blank: true, format: { with: %r{\.(gif|jpg|png)\Z}i, message: 'must be a URL for GIF, JPG or PNG image.' } - Running rake test now will throw errors, so you need to add test data to the test controller (e.g. /test/controllers/products_controller_test.rb)
- rake test runs against the dummy data in the controller test
- Add test data to the setup method with references to it in the create and update tests
- Unit tests are stored in test/models
- Use assertions for pass/fail (via the assert method) to tell the framework what we expect to be true
- We can use the model’s errors() and invalid?() methods to see whether it validates, and we can use the any?() method of the error list to see whether there is an error associated with a particular attribute
- Run unit testing with rake test:models … runs tests from the test model against the validations in the “real” model
- Unit test with dummy data in the test model…
test "product price must be positive" do product = Product.new(title: "My Book Title", description: "yyy", image_url: "zzz.jpg") product.price = -1 assert product.invalid? assert_equal ["must be greater than or equal to 0.01"], product.errors[:price] product.price = 0 assert product.invalid? assert_equal ["must be greater than or equal to 0.01"], product.errors[:price] product.price = 1 assert product.valid? end
- In the world of testing, a fixture is an environment in which you can run a test
- You specify fixture data in files in the test/fixtures directory in YAML data object files
- Each fixture file contains the data for a single model
- Indentation and order matters
- Specify the fixture in the test model
- Association-based stories are key to remembering large worlds of fixtures with ease … e.g. fred is paying for his christmas_order with his invalid_credit_card first, then paying with his valid_credit_card, and finally choosing to ship it all off to aunt_mary
- Fixtures put data into the test database (not development)
- Each test method gets a freshly initialized table in the test database, loaded from the fixtures
- Automatically done by the rake test command but can be done separately by running rake db:test:prepare
- Add validations to the model (the gatekeeper to the database) and test data to the test controller
-
Views and Layouts
- Create additional controllers as needed (e.g. rails generate controller Store index … or … rails generate controller Say hello goodbye)
- You can change the root of your site in routes.rb (e.g. root ‘store#index’, as: ‘store’ … the as: ‘store’ tells Rails to create a store_path accessor method)
- Reference data in the controllers …
def index @products = Product.order(:title) end
… and output in the views …
<% @products.each do |product| %> <%= product.title %> <% end %>
- Spit out an image in a view … e.g. <%= image_tag(product.image_url) %>
- Use the require_tree directive in the SCSS to automatically include all stylesheets available in this directory and in any subdirectory
- There’s a number_to_currency() helper method (e.g. turn 36.0 into $36.00)
-
Functional Tests
- Create HTML/CSS element tests in controller … for example…
test "should get index" do get :index assert_response :success assert_select '#columns #side a', minimum: 4 assert_select '#main .entry', 3 assert_select 'h3', 'Programming Ruby 1.9' assert_select '.price', /\$[,\d]+\.\d\d/ end
- Both validation and functional tests will test the behavior of controllers only; they will not retroactively affect any objects that already exist in the database or in fixtures
- run just functional tests with rake test:controllers
- Create HTML/CSS element tests in controller … for example…
Resources
- Agile Web Development with Rails 4 (course book)
- Rails Generate Differences