The overall goal of this assignment is to assess your ability to implement and use Active Record model relationships. This includes:
- Creating Active Record models and relationships between the models using a rails-provided generator (
rails g model
orrails g scaffold
) - Providing validations for models (using built-in Active Record validations as well as custom validations)
- Implementing a grandparent relationship with a
:through
option - Providing bootstrap data using a
seeds.rb
file - Implementing
default_scope
queries - Implementing aggregation queries
- Implementing advanced queries (e.g., SQL snippets)
- Implementing model/database (DB) cascades
The functional goal of this assignment is to implement relationships and query behavior for the following four (4) model classes
- User
- Profile
- TodoList
- TodoItem
-
Create several Active Record model classes - some of which will have relationships defined when they are created
- User
- Profile
- TodoList
- TodoItem
-
Create database migrations for relationships not present when the model classes were created
-
Create a DB schema for each model class and their relationships
-
Populate the DB with bootstrap data
-
Implement relationship features
-
Implement model class features
-
Create a new Rails application called
todolists
. This will look quite a bit like what you did in the module 1 assignment. -
Add the following specification to your Gemfile.
group :test do gem 'rspec-rails', '~> 3.0' end
-
Run the
bundle
command to resolve new gems -
From the
todolists
application root directory, initialize the rspec tests usingrails generate rspec:install
command.[todolists]$ rails generate rspec:install create .rspec create spec create spec/spec_helper.rb create spec/rails_helper.rb
Add the following line to
.rspec
to add verbose output to test results.--format documentation
-
Download and extract the starter set of boostrap files.
|-- Gemfile `-- spec `-- assignment_spec.rb
-
overwrite your existing Gemfile with the Gemfile from the bootstrap fileset. They should be nearly identical, but this is done to make sure the gems and versions you use in your solution can be processed by the automated Grader when you submit. Any submission should be tested with this version of the file.
-
add the
spec/assignment_spec.rb
file provided with the bootstrap fileset to the correspondingspec
directory that already exists under your application root directory (e.g.,application-root-directory/spec/assignment_spec.rb
). This file contains tests that will help determine whether you have completed the assignment.
-
-
Run the rspec test(s) to receive feedback.
rspec
must be run from the root directory of your application. All tests will (obviously) fail until you complete the specified solution.$ rspec ... Finished in 0.02831 seconds (files took 1.39 seconds to load) 56 examples, 1 failure, 54 pending
To focus test feedback on a specific step of the requirements, add "-e rq##" to the rspec command line to only evaluate that requirement. Pad all step numbers to two digits.
$ rspec -e rq01 Run options: include {:full_description=>/rq01/} Assignment rq01 Generate Rails application must have top level structure of a rails application Finished in 0.00356 seconds (files took 1.3 seconds to load) 1 example, 0 failures
$ rspec -e rq02 ... Finished in 0.00422 seconds (files took 2.16 seconds to load) 6 examples, 1 failure, 5 pending Failed examples: rspec ./spec/assignment_spec.rb:50 # Assignment rq02 User Model: User class created
-
Implement your Models and use the rspec tests to help verify your completed solution.
-
Submit your Rails app solution for grading.
-
Create a new Rails app called
todolists
. Use the Gemfile provided in the boostrap files. Do not change the Gemfile from what is provided or your submitted solution may not be able to be processed by the grader (i.e., do not add any additional gems or change gem versions).An Entity Relationship (ER) diagram is provided below to help depict each Model's relationship:
|------| 1 1 |----------| | User |----------| Profile | |------| |----------| \ \ \ 1 * |----------| 1 * |----------| \------| TodoList |----------| TodoItem | |----------| |----------|
-
Re-use the
User
model class and database table creation commands implemented in Assignment 1. (i.e., you may re-use the exactrails generate
command you used to generate this class from the previous assignment)- User
- username - a string to hold account identity
- password_digest - a string to hold password information
Reminder: Ruby/Rails conventions are that class names are CamelCase and file and method names are snake_case.
You must migrate the database schema at this time and in between each of the following steps to be able to pass the rspec tests incrementally.
$ rake db:migrate $ rspec -e rq02 Run options: include {:full_description=>/rq02/} Assignment rq02 User Model: User class created User database structure in place User class properties added should respond to #username should respond to #password_digest should respond to #created_at should respond to #updated_at Finished in 0.19157 seconds (files took 1.27 seconds to load) 6 examples, 0 failures
- User
-
Re-use the
Profile
model class and database table creation commands implemented in Assignment 1, except this time create it with a reference toUser
when you create the model class. The grader will look for the relationship to be included in the initial create table migration for this model class.- Profile
- gender - a string to hold the words "male" or "female"
- birth_year - a number to hold the year the individual was born
- first_name - a string with given name of user
- last_name - a string with family name of user
- user - a 1:1 relationship with User (i.e., Profile
belongs_to
User)
Also define the 1:1
has_one
relationship in theUser
model class.- User
- profile - a 1:1 relationship with
Profile
(i.e., Userhas_one
profile). Add appropriate options to have theUser
model class delete aProfile
in a cascading fashion
- profile - a 1:1 relationship with
Reminder: Relationships are only generated or migrated in the class forming the relationship (e.g., the many-side of a many:1). The inverse side of a relationship (e.g., the one-side of a 1:many) need only have the relationship declared in the model class and not in the database migration. In this case,
Profile
is forming the relationship in the database andUser
is the inverse side.$ rake db:migrate $ rspec -e rq03
- Profile
-
Re-use the
TodoList
model class implemented in Assignment 1 (i.e., you may re-use the exactrails generate
command you used to generate this class from the previous assignment). The grader will look for no relations in the initial create table migration for this model class.- TodoList
- list_name - a string name assigned to the list
- list_due_date - a date when todo items in the list are to be complete. This is a date. We are not concerned with the time of day.
$ rake db:migrate $ rspec -e rq04
- TodoList
-
Create a database migration (hint:
rails g migration
) that adds a database reference from theTodoLists
table to theUsers
table. The grader will look for this relationship to be formed by a database table migration.Also define the many:1
belongs_to
relationship in theTodoList
model class.- TodoList
- user - a many:1 relationship with
User
(i.e., TodoListbelongs_to
User)
- user - a many:1 relationship with
Also define the 1:many
has_many
relationship in theUser
model class.- User
- todo_lists - a 1:many relationship with
TodoList
(i.e., Userhas_many
todo_lists). Add appropriate options to have theUser
model class delete aTodoList
in a cascading fashion
- todo_lists - a 1:many relationship with
$ rake db:migrate $ rspec -e rq05
- TodoList
-
Re-use the
TodoItem
model class implemented in Assignment 1, except this time create it with a many:1belongs_to
relationship withTodoList
when you create the model class. The grader will look for the relationship to be included in the initial create table migration for this model class.- TodoItem
- due_date - date when the specific task is to be complete
- title - a string with short name for specific task
- description - a text field with narrative text for specific task
- completed - a boolean value (default=false), indicating whether item is complete
- todo_list - a many:1 relationship with TodoList - TodoItem belongs_to TodoList
Also define the 1:many
has_many
relationship in theTodoList
model class.- TodoList
- todo_items - a 1:many relationship with
TodoItem
(i.e., TodoListhas_many
todo_items). Add appropriate options to have theTodoList
model class delete aTodoItem
in a cascading fashion
- todo_items - a 1:many relationship with
$ rake db:migrate $ rspec -e rq06
- TodoItem
-
Migrate all database schema changes at this time if you have not yet done so. At this point you should have:
- four (4) create table (Users, Profiles, TodoLists, and TodoItems)
- one (1) update table migrations to add reference (TodoLists to Users)
$ rake db:migrate $ rspec -e rq07
-
Implement a 1:many
:through
relationship fromUser
toTodoItem
by using the 1:many relationship fromUser
toTodoLists
as a source.- User
- todo_items - a 1:many through relationship with TodoItem through TodoLists
(i.e., User
has_many
todo_items)
- todo_items - a 1:many through relationship with TodoItem through TodoLists
(i.e., User
$ rspec -e rq08
- User
-
Create a seeds.rb file that will clear the existing data from the model tables and load the database with
- The four users below with their birth years:
- Carly Fiorina, 1954
- Donald Trump, 1946
- Ben Carson, 1951
- Hillary Clinton, 1947
- Usernames (e.g., their last names) and a password for each
User
- A
Profile
for eachUser
- Exactly one
TodoList
perUser
that is due one year from the date the database is loaded- (hint:
Date.today
provides today's date and1.year
can be used to define one year)
- (hint:
- Each
TodoList
contains five (5)TodoItems
(there must be 20 total) - Each
TodoItem
having a due date of one year from the time the database is loaded - Each
TodoItem
must have an arbitrarytitle
anddescription
s
(Hint: you may want to consider using loops)
Once the seeds.rb file is created, populate the database using
rake db:seed
$ rake db:seed $ rspec -e rq09
- The four users below with their birth years:
-
Add
default_scope
to bothTodoList
andTodoItem
models to always return collectons from the database ordered by due dates with earliest dates first.$ rspec -e rq10
-
Add validation to
User
andProfile
models
-
User
- Define a validation for
username
to enforce that username be supplied by using a built-in validator
- Define a validation for
-
Profile
- Define custom validator that permits
first_name
orlast_name
to be null but not both - Define a validation for
gender
to be either "male" or "female" by using a built-in validator - Define custom validator that prevents anyone that is
male
(gender) from having thefirst_name
"Sue" ;)
$ rspec -e rq11
- Define custom validator that permits
-
Add a cascade of deletes that will remove
Profile
,TodoList
, andTodoItem
rows for anyUser
removed.$ rspec -e rq12
-
Add a method to the
User
model class calledget_completed_count
, which:- determines the number of
TodoItems
theUser
has completed using an aggregate query function- (Hint: You are looking for the count of
TodoItems
associated with a specificUser
wherecompleted:true
)
- (Hint: You are looking for the count of
- returns the count
$ rspec -e rq13
- determines the number of
-
Add a class method to the
Profile
class, calledget_all_profiles
, which:- accepts a min and max for the
birth year
- issues a
BETWEEN
SQL clause in awhere
clause to locateProfiles
with birth years that are between min year and max year - defends itself against SQL injection when applying the parameters to the SQL clauses
- returns a collection of Profiles in ASC birth year order
$ rspec -e rq14
- accepts a min and max for the
Some unit tests have been provided in the bootstrap files and provide examples of tests the grader will be evaluating for when you submit your solution. They must be run from the project root directory.
$ rspec
...
Finished in 9.56 seconds (files took 1.41 seconds to load)
56 examples, 0 failures
You can run as many specific tests you wish be adding -e rq## -e rq##
$ rspec -e rq01 -e rq02
Submit an .zip archive (other archive forms not currently supported) with your solution root directory as the top-level (e.g., your Gemfile and sibling files must be in the root of the archive and not in a sub-folder. The grader will replace the spec files with fresh copies and will perform a test with different query terms.
|-- app
| |-- assets
| |-- controllers
| |-- helpers
| |-- mailers
| |-- models
| `-- views
|-- bin
|-- config
|-- config.ru
|-- db
|-- Gemfile
|-- Gemfile.lock
|-- lib
|-- log
|-- public
|-- Rakefile
|-- README.rdoc
|-- test
`-- vendor
The overall goal of this assignment is to assess your ability to implement:
- Nested resources
- Authenication
- Authorization
- Pagination
The functional goal of this assignment to implement a web application to manage TodoItems.
-
Start with the application completed in module 2. This will have defined all the models and relationships required for this assignment:
- User
- TodoList
- TodoItem
An Entity Relationship (ER) diagram is provided below to help depict each Model's relationship:
+------+ 1 * +----------+ 1 * +----------+ | User |--------| TodoList |--------| TodoItem | +------+ +----------+ +----------+
We will not be using the optional
Profile
model class in this assignment. -
Implement access to
TodoItem
as a nested resource ofTodoList
-
Lock down the application to only authenticated users.
-
Limit access to resources associated with the logged in user.
-
Access unbounded collections of resources using pagination.
-
Start with a copy of your
todolists
solution from the module 2 assignment -
From your
todolists
application root directory, remove the unit test from module 2.`-- spec `-- assignment_spec.rb
$ rm spec/assignment_spec.rb
-
Download and extract the starter set of boostrap files for this assignment.
|-- Gemfile |-- db | `-- seeds.rb `-- spec -- start.rb -- nested_resources.rb -- security.rb -- authorization.rb -- authentication.rb -- pagination.rb -- end2end.rb
-
Overwrite your existing Gemfile with the Gemfile from the bootstrap fileset. They should be nearly identical, but this is done to make sure the gems and versions you use in your solution can be processed by the automated Grader when you submit. Any submission should be tested with this version of the file.
NOTE the Gemfile includes a section added for the last assignment:
group :test do gem 'rspec-rails', '~> 3.0' gem 'capybara' end
as well as a new definition for the following items:
-
bcrypt
gem uncommented for use withhas_secure_password
-
tzinfo-data
gem conditionally included on Windows platforms -
will_paginate
added for implementing paging# Use ActiveModel has_secure_password gem 'bcrypt', '~> 3.1.7' # Windows does not include zoneinfo files, so bundle the tzinfo-data gem gem 'tzinfo-data', platforms: [:mingw, :mswin, :x64_mingw, :jruby] gem 'will_paginate', '~> 3.0.6'
-
-
Overwrite your existing
db/seeds.rb
file from the bootstrap fileset. This file contains some test data that will be useful during development and unit tests. -
Add the
spec/*.rb
files provided with the bootstrap fileset to the correspondingspec/
directory within yourtodolists
application. These files contain tests that will help determine whether you have completed the assignment.
-
-
Run the
bundle
command to make sure all gems are available.$ bundle
-
Run the rspec test(s) to receive feedback.
rspec
must be run from the root directory of your application. There are several test files provided for this assignment. Many of those files are designed to test your code at specific points as you proceed through the technical requirements of this assignment. As such, many tests will fail if executed after additional technical requirements have been completed. Initially, majority of tests will (obviously) fail until you complete the requirements necessary for them to pass.$ rspec ... (N) examples, 1 failure, (N) pending
To focus test feedback on a specific step of the requirements, add the specific file (path included) with the tests along with "-e rq##" to the rspec command line to only evaluate a specific requirement. Pad all step numbers to two digits.
$ rspec spec/start_spec.rb -e rq1.0 ... (N) example, 0 failures
-
Implement your solution to the technical requirements and use the rspec tests to help verify your completed solution.
-
Submit your Rails app solution for grading.
-
Starting with a copy of your module 2 solution, this solution should already have
User
,TodoList
, andTodoItem
models defined with the following properties and relationships. This assignment does not use theProfile
model class but it will not hurt to include it.-
User
- username - a string to hold account identity
- password_digest - a string to hold password information
- todo_lists - a 1:many relationship with
TodoList
(i.e., Userhas_many
todo_lists).
Add appropriate options to have the
User
model class delete aTodoList
in a cascading fashion -
TodoList
- list_name - a string name assigned to the list
- list_due_date - a date when todo items in the list are to be complete. This is a date. We are not concerned with the time of day.
- user - a many:1 relationship with
User
(i.e., TodoListbelongs_to
User) - todo_items - a 1:many relationship with
TodoItem
(i.e., TodoListhas_many
todo_items).
Add appropriate options to have the
TodoList
model class delete aTodoItem
in a cascading fashion -
TodoItem
- due_date - date when the specific task is to be complete
- title - a string with short name for specific task
- description - a string with narrative text for specific task
- completed - a boolean value (default=false), indicating whether item is complete
- todo_list - a many:1 relationship with TodoList - TodoItem belongs_to TodoList
$ rake db:migrate $ rspec spec/start_spec.rb
-
-
Add
has_secure_password
to theUser
model class. This will define apassword
property that will get processed into an encrypted hash stored in thepassword_digest
database column. We won't use this capability immediately -- but it is necessary to define it early in the assignment so that the data model works with thedb/seeds.rb
file in the next step.$ rspec spec/security_spec.rb -e rq02
-
Seed the database with the
db/seeds.rb
file. This will load sampleUsers
,TodoLists
, andTodoItems
. If this loads correctly -- your models and database are setup correctly and you are ready to start accessing the data through web pages produced by the controller and views.$ rake db:seed $ rails c > User.first.todo_lists.count => (N>0)
-
Use the
rails g scaffold_controller
command to create controller and view artifacts forTodoLists
andTodoItems
.$ rails g scaffold_controller TodoList list_name list_due_date:date $ rails g scaffold_controller TodoItem title due_date:date description:text completed:boolean
Update
config/routes.rb
to- Access
:todo_list
resources at URI/todo_lists
- Access
:todo_item
resources at URI/todo_lists/:todo_list_id/todo_items
- Make the
todo_list#index
action the root of the application
(Hint: refer to module 4, lesson 1, lecture:
Nested Resources: Part1
for details on how this is done)At this point,
TodoList
is defined as a global resource (with a root-level URI) andTodoItem
is defined as a nested resource, always scoped below theTodoList
it belongs to. Our application is not written to work that way, so expect some errors as we begin the modifications.If you have not yet done so -- please start the server and also take a look at your defined URI routes.
$ rails s #in separate console
$ rake routes #in original console $ rspec spec/nested_resources_spec.rb -e rq04
- Access
-
Update the
TodoList
to displayTodoItems
as a nested resource in thetodo_list#show
page (todo_lists/show.html.erb
).a. Copy the table from the
todo_items#index
page (index.html.erb
) and paste the table into thetodo_lists#show
page (todo_lists/show.html.erb
)b. Change global
@todo_items
references to scoped@todo_list.todo_items
references below:from: <% @todo_items.each do |todo_item| %> to: <% @todo_list.todo_items.each do |todo_item| %>
c. Remove the
Edit
link forTodoItems
d. Change the link_to parameters from global
todo_item
references to provide fully qualified[@todo_list, todo_item]
references as an array.from: <td><%= link_to 'Show', todo_item %></td> to: <td><%= link_to 'Show', [@todo_list, todo_item] %></td>
from: <td><%= link_to 'Destroy', todo_item, method: :delete, data: ... to: <td><%= link_to 'Destroy', [@todo_list, todo_item], method: :delete, data: ...
$ rspec spec/nested_resources_spec.rb -e rq05b
NOTE: This test case is for incremental testing only and WILL FAIL after authentication infrastructure is in place later in this assignment.
$ rspec spec/nested_resources_spec.rb -e rq05d
NOTE: This test case is for incremental testing only and WILL FAIL after authentication infrastructure is in place later in this assignment.
e. Add a link to create a 'New Todo Item'. (hint: Use the
link_to
andnew_todo_list_todo_item_path(@todo_list)
helpers to produce a link tag)$ rspec spec/nested_resources_spec.rb -e rq05e
Note how the
new_todo_list_todo_item_path(@todo_list)
is formed from what is produced inrake routes
.$ rake routes new_todo_list_todo_item GET /todo_lists/:todo_list_id/todo_items/new(.:format) todo_items#new
- we want to invoke
todo_items#new
when we create a newTodoItem
- that action is mapped to using the
/todo_lists/:todo_list_id/todo_items/new(.:format)
URI and GET method. We are required to supply a :todo_list_id new_todo_list_todo_item_path
is formed by adding_path
tonew_todo_list_todo_item
- the :todo_list_id is filled in by passing in a
@todo_list
when calling it - GET is provided by
new_todo_list_todo_item_path
Notice that the
TodoItems
now display on thetodo_list#show
page by navigating to a specificTodoList
. However, theTodoItem
URIs are not yet implemented in theTodoItem
controller (next step).$ rspec spec/nested_resources_spec.rb -e rq05
- we want to invoke
-
Modify the
TodoItem
controller to work as a nested resource by implementing the following. Note that your views withTodoItem
URI references will not work until these changes are made and the links and forms are updated to include the scopingTodoList
for each referencedTodoItem
. The unit tests, however, will be able to make calls into your back-end to determine all URIs are implemented properly -- prior to moving on to the views.a. Remove the old URI comments or replace them to have the following form since all calls to a
TodoItem
will now be scoped below aTodoList
. Use the todo_item output ofrake routes
to give you a head start.$ rake routes #METHOD /todo_list/:todo_list_id/todo_items #METHOD /todo_list/:todo_list_id/todo_items/:id
b. Remove the
todo_item#index
method andviews/todo_items/index
pages. This will no longer be called since allTodoItem
displays will be scoped to a particularTodoList
. We will get theTodoList
and calltodo_list.todo_items()
instead.c. Add a private helper method called
set_todo_list
that sets the@todo_list
instance variable from the:todo_list_id
property passed in via the params. (Hint: try the following in therails console
if you need practice locating a TodoList by id)$ rails c > list_id=TodoList.first.id > @todo_list=TodoList.find(list_id)
d. Update the private helper method called
set_todo_item
to scope its find command to thetodo_items
of a specific@todo_list
list. (Hint: try the following in therails console
if you need practice locating aTodoItem
by id scoped to aTodoList
)$ rails c > list_id=TodoList.first.id > @todo_list=TodoList.find(list_id) > item_id=@todo_list.todo_items.first.id > @todo_item=@todo_list.todo_items.find(item_id)
e. Invoke the
set_todo_list
method before each method in the controller is executed using abefore_action
f. Update the
todo_item#new
action to return a newTodoItem
instance that is initialized to reference its parent@todo_list
, which is provided byset_todo_list
.
(Hint: try the following therails console
if you need practice creating a new instance of aTodoItem
associated with aTodoList
. Notice the new TodoItem is never saved to the database during this call. However, what is passed back to the form is a TodoItem prototype that has its foreign key reference set to theTodoList
so thatTodoList
can be referenced when theTodoItem
is finally created in a follow-on POST)$ rails c > @todo_list=TodoList.first > @todo_item=@todo_list.todo_items.new
g. Update the
todo_item#create
to create a newTodoItem
instance based on thetodo_item_params
as before. Except now create this instance associated with the@todo_list
provided byset_todo_list
. (Hint: try the following in therails console
if you need practice creating a new instance of aTodoItem
associated with aTodoList
. Notice that in this case --save()
is being called on thetodo_list
, causing the newTodoItem
to be inserted into the database.)$ rails c > @todo_list=TodoList.first > @todo_item=@todo_list.todo_items.new(title:"my item") > @todo_list.save()
h. Update the HTML redirect of the
todo_item#create
,todo_item#update
, andtodo_item#destroy
methods to do to thetodo_list#index
page. (Hint: use the@todo_list
variable withinredirect_to
to express thetodo_list#index
page URI) -
Update
TodoList
andTodoItem
views to adjust the links and forms in these views to work with the updated URIs andTodoItem
controller.a. Update the links on the
todo_items#show
page (todo_items/show.html.erb
) to include theTodoList
theTodoItem
is a member of.-
Change the
Edit
link_to path parameter from the globaledit_todo_item_path
(that no longer exists) to the newedit_todo_list_todo_item_path
. This new method requires both@todo_list
and `todo_item' passed in as separate arguments (not as an array -- as in previous requirement).from: <%= link_to 'Edit', edit_todo_item_path(@todo_item) %> | to: <%= link_to 'Edit', edit_todo_list_todo_item_path(@todo_list, @todo_item) %> |
-
Change the
Back
link_to path parameter from to globaledit_items_path
(that no longer exists) to thetodo_listi#show
page it is a member of. This requires using the@todo_list
.from: <%= link_to 'Back', todo_items_path %> to: <%= link_to 'Back', @todo_list %>
$ rspec spec/nested_resources_spec.rb -e rq07a
b. Update the links on the
todo_items#edit
page (todo_items/edit.html.erb
) to include theTodoList
theTodoItem
is a member of.-
Change the
Show
link_to path parameter from a global@todo_item
reference to include its@todo_list
. This requires using both@todo_list
and `@todo_item' passed in as separate arguments as an array.from: <%= link_to 'Show', @todo_item %> | to: <%= link_to 'Show', [@todo_list, @todo_item] %> |
-
Change the
Back
link_to path parameter from a globaltodo_items_path
(that no longer exists) to reference theTodoList
it is a member of. This new method requires the@todo_list
passed in as a single argument.from: <%= link_to 'Back', todo_items_path %> to: <%= link_to 'Back', @todo_list %>
c. Update the form parameters on the
TodoItems
form partial page (todo_items/_form.html.erb
) to include theTodoList
theTodoItem
is a member of.-
Change the link_to parameters from global
todo_item
references to provide fully qualified `[@todo_list, @todo_item]' references as an array.from: <%= form_for(@todo_item) do |f| %> to: <%= form_for([@todo_list, @todo_item]) do |f| %>
$ rspec spec/nested_resources_spec.rb -e rq07c
d. Update the links on the
todo_items#new
page (todo_items/new.html.erb
) to include theTodoList
.-
change the
Back
link_to path parameter from a globaltodo_items_path
(that no longer exists) to reference theTodoList
it is a member of. This new method requires the@todo_list
passed in as a single argument.from: <%= link_to 'Back', todo_items_path %> to: <%= link_to 'Back', @todo_list %>
$ rspec spec/nested_resources_spec.rb -e rq07d
e. Make the display of
completed
conditional on thetodo_item
being edited versus new. Users should not be allowed to see/change thecompleted
property for a newTodoItem
. (Hint: edited objects are persisted and can be tested usingpersisted?
. Objects can also be tested with.new_record?
)$ rspec spec/nested_resources_spec.rb -e rq07e
$ rspec spec/nested_resources_spec.rb -e rq07
-
-
Verify that you have implemented a password login capability for the
User
model. You implemented this in an earlier step to allow the provideddb/seeds.rb
to immediately work with passwords. This this should just be a sanity check and review of howhas_secure_password
works.-
Using the
rails console
, verify that you fail authentication when using the wrong password for a specificUser
. You can locate theusername
and assignedpassword
in thedb/seeds.rb
file.$ rails c > user=User.where(username:"rich").first > user.authenticate("wrongpassword") => false
-
Using the
rails console
, verify that you can authenticate using a valid password for a specificUser
.> user=User.where(username:"rich").first > user.authenticate("123abc") => #<User id: 277, username: "rich", password_digest: "$2a...
-
Using the
rails console
, verify that you can authenticate and get theTodoLists
for an authenticatedUser
.> user=User.where(username:"rich").first > user.authenticate("123abc").todo_lists.count => 49 #seed data randomly generated
$ rspec spec/security_spec.rb -e rq08
-
-
Create a new controller to manage the user's
session
when interacting with the server.a. Use the
rails g controller
command to create aSessions
controller with the following actions:- new
- create
- destroy
b. Clean up the
config/routes.rb
file edited by therails g controller
command to be the following:-
generated:
get 'sessions/new' get 'sessions/create' get 'sessions/destroy'
-
change to:
resources :sessions, only: [:new, :create, :destroy]
c. Map the GET
/login
action tosessions#new
inconfig/routes.rb
. Have this be referred to as thelogin
resource sorake routes
generates alogin
_path helper.get "/login" => "sessions#new", as: "login"
d. Map the DELETE
/login
action tosessions#destroy
inconfig/routes.rb
. Have this referred to as thelogout
resource sorake routes
reports alogout
_path helper.delete "/logout" => "sessions#destroy", as: "logout"
$ rspec spec/security_spec.rb -e rq09
-
Implement the
Sessions
controller class and view. This should permit a caller to willingly navigate to the/login
page, login with a correct password, and proceed to the root URI. Nothing will stop an un-authenticated user from accessing the same list at this time. (Hint: the information to complete this step is contained in module 4, lesson 2, lecture:Sessions and Controller View
)a. Leave the
new
method in its default state. This will cause the route to continue straight toviews/sessions/new.html.erb
.b. Update the
sessions#new
page (views/sessions/new.html.erb
) to declare a form:-
for a
User
model type :user; this will cause the properties of the form to be assigned to an instance of aUser
-
with a
sessions_path
URI; this will cause a POST to the sessions#create URI to be invoked when a submit is called -
with a :username text_field; this will assign the user input to the
user[username]
property -
with a :password password_field; this will obfuscate the user's password while being typed and assign the user input to the
user[password]
property -
with a submit action; this will submit the form to the server when pressed
<h1>Login</h1> <%= form_for(:user, url: sessions_path) do |f| %> <div class="field"> <%= f.label :username %> <br/> <%= f.text_field :username %> </div> <p/> <div class="field"> <%= f.label :password %> <br/> <%= f.password_field :password %> </div> <p/> <div class="actions"> <%= f.submit "Login" %> </div> <% end %>
$ rspec spec/security_spec.rb -e rq10b
c. Implement the
create
method as follows:- get the user's
username
andpassword
from the submitted form - find the user based on
username
- authenticate the user using the supplied
password
- if authenticated
- store the user.id in the
session
- redirect the caller to the
root_path
of the application and supply a flash.notice message announcing successful login
- store the user.id in the
- if not authenticated
-
redirect the caller to the
login_path
and supply an flash.alert message$ rspec spec/security_spec.rb -e rq10c
-
d. Implement the
destroy
method as follows:-
reset the
session
, wiping out the user's session and everything in it -
redirect the caller to the
login_path
with a flash.notice message announcing successful logout$ rspec spec/security_spec.rb -e rq10d
e. Remove the
destroy
andcreate
pages in the view, generated byrails g controller
since they are not being used.$ rspec spec/security_spec.rb -e rq10d
-
-
Require users to authenticate with your application prior to accessing anything except the login page. At the completion of these steps, no one should be able to access anything except the login page -- until they successfully authenticate. (Hint: the information to complete this step is contained within module 4, lesson 2, lecture:
Authorization
)a. Define a
logged_in? helper method in the
ApplicationControllerclass that evaluates to
true` if the there is a user associated with the session.b. Define a
current_user
helper method in theApplicationController
class that finds and returns theUser
instance associated with the session.c. Expose
logged_in?
andcurrent_user
as helper methods outside of the controller usinghelper_method
. Note these methods were already available to all controller sub-classes. This designation makes them available to the views as well.d. Define a
ensure_login
helper method in theApplicationController
class that redirects the caller to thelogin_path
if they are not logged in. Note this method is available to all controller sub-classes in the application.e. Define that all methods perform
ensure_login
before they are called usingbefore_action
.f. Create an exception to the above rule so that
sessions#new
andsessions#create
can be accessed by an unauthenticated user -- otherwise no one will be able to access the login page.class SessionsController < ApplicationController skip_before_action :ensure_login, only: [:new, :create]
g. Update the
views/layouts/application.html.erb
page to include user/logout information based on the current session state. With this snippet in place -- you should be able to login and see the current_user.username displayed in the right, top corner of the display.<% if logged_in? %> <div style='float: right;'> Logged in as <%= current_user.username %> | <%= link_to "Logout", logout_path, method: :delete %> </div> <% end %>
$ rspec spec/authentication_spec.rb -e rq11
-
Update the application so that authenticated users can only have access to
TodoLists
associated with their specific user. This mostly involves updating theTodoListController
to change all globalTodoList
commands to be scopedcurrent_user.todo_lists
commands. (Hint: If you need some practice accessingTodoLists
for an authenticated user, try the following commands in therails console
.$ rails c > user_id=User.where(username:"rich").first.id > current_user=User.find(user_id) > current_user.todo_lists.count => 49 #random assignment -- some number greater than 0
This mostly involves changing the following from/to.
from: TodoList.x to: current_user.todo_lists.x
At this point, logged in users should only be able to see their
TodoList
s$ rspec spec/authentication_spec.rb -e rq12
-
Add pagination to your application to help scale and manage methods that can return unbounded collections of information.
a. Verify the
will_paginate
gem is added to your Gemfile.b. Update the
todolist#index
action to return a page ofTodoLists
associated with thecurrent_user
that are up to 8 objects per_page. (Hint: If you are not familiar with howwill_paginate
works, you can get some familiarity using therails c
and Active Record commands.will_paginate
adds an additional method to all model classes to be able to break find command results into pages and page results.)$ rails c > 3.times {|n| p TodoList.paginate(page:n+1, per_page:1)} > p TodoList.paginate(page:1, per_page:1).total_pages => 101
The page number will be available in the
params[:page]
property of the call.c. Add
will_paginate
to yourtodolists#index
page and apply it to your@todo_lists
result from the controller.At this point, logged in users should only be able to see their
TodoList
s and$ rspec spec/pagination_spec.rb
-
Perform an end-to-end check of your work. Before you do, you must remove the confirmation dialogs from your Destroy links since we are not using a webdriver that supports javascript for this assignment. Inside the todo_list show.html.erb file you will need to change the destroy link to eliminate the confirmation dialog:
from: <%= link_to 'Destroy', [@todo_list, todo_item], method: :delete, data: { confirm: 'Are you sure?' } %> to: <%= link_to 'Destroy', [@todo_list, todo_item], method: :delete %>
Do the same for todo_list index.html.erb
from: <%= link_to 'Destroy', todo_list, method: :delete, data: { confirm: 'Are you sure?' } %> to: <%= link_to 'Destroy', todo_list, method: :delete %>
a. Login to the application as
rich
b. Access the first
TodoList
on the second pagec. Complete the first
TodoItem
in thatTodoList
if not completedd. Create a new
TodoItem
for thatTodoList
e. Delete a
TodoItem
from thatTodoList
f. Create a new
TodoList
g. Delete a
TodoList
rspec spec/end2end_spec.rb
Some unit tests have been provided in the bootstrap files and provide examples of tests the grader will be evaluating for when you submit your solution. They must be run from the project root directory.
$ rspec (file)
...
(N) examples, 0 failures
You can run as many specific tests you wish be adding -e rq## -e rq##
$ rspec (file) -e rq01 -e rq02
Note that some of the earlier specs cannot be run once security has
been fully enabled. Use the end2end
test when complete. Each of the
individual requirements list specific specs that can be used during
the time of that development.
Submit an .zip archive (other archive forms not currently supported) with your solution root directory as the top-level (e.g., your Gemfile and sibling files must be in the root of the archive and not in a sub-folder. The grader will replace the spec files with fresh copies and will perform a test with different query terms.
|-- app
| |-- assets
| |-- controllers
| |-- helpers
| |-- mailers
| |-- models
| `-- views
|-- bin
|-- config
|-- config.ru
|-- db
|-- Gemfile
|-- Gemfile.lock
|-- lib
|-- log
|-- public
|-- Rakefile
|-- README.rdoc
|-- test
`-- vendor