- https://rubygems.org hosts the Ruby gem for Ruby on Rails.
- The current stable version number of Rails is 5.1.4, with 5.2.0.beta2 being the overall latest version.
- As of this writing, Ruby on Rails has been downloaded 118,011,892 across all versions.
- The current version of Ruby on my system in 2.5.0
(
ruby -v
reportsruby 2.5.0p0 (2017-12-25 revision 61468) [x86_64-darwin17]
). - The current version of Rails on my system is 5.1.4.
- Doing so changes the contents of the hello method to be
render html: "hola, mundo!"
. - Doing so changes the contents of the hello method to be
render html: "¡Hola, mundo!"
. - Doing so adds a goodbye method with the contents
render html: "goodbye, world"
and modifies the root route line inrouter.rb
to readroot 'application#goodbye'
.
- In the current state of the project, getting the production version of the app to show "hola, mundo!" simply required
the text to be altered from "¡Hola, mundo!" in the
hello
method inside the application controller (remove the inverted question mark and change the H in Hola to lowercase) and modifying the root route to point to thehello
method in theApplicationController
. After these changes had been committed, running$ git push heroku
updated the live site with these changes. - Switching the root route in
routes.rb
to point back to thegoodbye
method in the application controller (application#goodbye
), committing the change, and pushing it to heroku was all that was required for this exercise.
- The command to display logs is
$ heroku logs
. - The most recent event was (line breaks added for readability):
2018-01-07T04:55:46.209028+00:00 app[web.1]: I, [2018-01-07T04:55:46.208969 #4]
INFO -- : [11747d4f-ddad-45be-b5a5-51a288465c2e]
Completed 200 OK in 8ms (Views: 0.9ms)
- The CSS id for the text “User was successfully created.” is
notice
. When you refresh the browser, the text goes away. - When you try to create a user with a name but no email address, the creation of the user is successful, with no email address being set for the user.
- When you try create a user with an invalid email address, the user is successfully created with the invalid email address populating the email field of the user.
- By default, Rails displays "User was successfully destroyed." when a user is destroyed.
- The steps for visiting
/users/1/edit
are as follows:- The browser issues a request for the /users/1/edit URL.
- Rails routes /users/1/edit to the edit action in the Users controller.
- The edit action asks the User model to retrieve the user with an id of 1 (User.find(params[:id])).
- The User model pulls the specified user from the database.
- The User model returns the single users to the controller.
- The controller captures the user in the @user variable, which is passed to the edit view.
- The view uses embedded Ruby to render the page as HTML.
- The controller passes the HTML back to the browser.
- The line in the scaffolding code that retrieves the user from the database is
@user = User.find(params[:id])
. - The name of the view file for the user edit page is
edit.html.erb
.
- The CSS id for the text “Micropost was successfully created.” is
notice
. When you refresh the browser, the text goes away. - When you try to create a micropost with empty content and no user id, the creation of the micropost is successful, with no content or user id being set for the micropost.
- The micropost with the content length set to over 140 characters successfully creates.
- Destroying these microposts removes them and displays "Micropost was successfully destroyed." in the flash.
- Microposts with a content length longer than 140 trigger a validation error and fail to create.
- The CSS id of the error message produced by the previous exercise is
error_explanation
.
-
To complete this exercise, I added the following to the bottom of
app/views/users/edit.html.erb
:<h2>First Micropost</h2> <%= @user.microposts.first.content %>
-
The validation error message that shows when
presence: true
is included on the Micropost's model content validation is "Content can't be blank". -
The user model reads as follows after completing this exercise: To complete this exercise, I added the following to the bottom of
app/views/users/edit.html.erb
:class User < ApplicationRecord has_many :microposts validates :name, presence: true validates :email, presence: true end
-
The line that causes ApplicationController to inherit from ActionController::Base is
class ApplicationController < ActionController::Base
. -
The analogous file containing a line where ApplicationRecord inherits from ActiveRecord::Base is
app/models/application_record.rb
:class ApplicationRecord < ActiveRecord::Base self.abstract_class = true end
- Create 2nd and third users
Foo Bar (foo@bar.com)
andBaz Quux (baz@quux.com)
. - Create
Second post
andThird post
for the first user. - Confirmed that the validation from Listing 2.13 works on the production app.
- I did not use bitbucket, but the page does correctly show here on GitHub.
- The deployment to Heroku did succeed.
- Output:
$ rails g controller FooController bar baz
Running via Spring preloader in process 51437
create app/controllers/foo_controller_controller.rb
...
create app/assets/stylesheets/foo_controller.scss
- Output:
$ rails d controller FooController bar baz
Running via Spring preloader in process 51465
...
remove app/assets/stylesheets/foo_controller.scss
- Changing the
StaticPageController
tests to use the @base_title instance variable still correctly shows all tests passing.
- Work for this exercise can be viewed in this commit: 2a31cef.
>> city = 'Charleston'
=> "Charleston"
>> state = "South Carolina"
=> "South Carolina
>> puts "#{city}, #{state}"
"Charleston, South Carolina"
=> nil
>> puts "#{city}\t#{state}"
Charleston South Carolina
=> nil
>> puts '#{city}\t#{state}'
#{city}\t#{state}
=> nil
>> "racecar".length
=> 7
>> "racecar".reverse
=> "racecar"
>> s = "racecar"
=> "racecar"
>> s == s.reverse
=> true
>> s = "onomatopoeia"
=> "onomatopoeia"
>> s == s.reverse
=> false
>> def palindrome_tester(s)
>> if s == s.reverse
>> puts "It's a palindrome!"
>> else
>> puts "It's not a palindrome."
>> end
>> end
>> palindrome_tester("racecar")
It's a palindrome!
=> nil
>> palindrome_tester("onomatopoeia")
It's not a palindrome.
=> nil
>> palindrome_tester("racecar").nil?
It's a palindrome!
=> true
>> a = "A man, a plan, a canal, Panama".split(', ')
=> ["A man", "a plan", "a canal", "Panama"]
>> s = a.join
=> "A mana plana canalPanama"
>> s = a.join
=> "A mana plana canalPanama"
>> s = s.split.join
=> "AmanaplanacanalPanama"
>> s == s.reverse
=> false
>> s.downcase == s.reverse.downcase
=> true
>> ('a'..'z').to_a[7]
=> "h"
>> ('a'..'z').to_a.reverse[7]
=> "s"
>> (0..16).each { |i| puts i ** 2 }
0
1
4
...
256
=> 0..16
>> def yeller(char_arr)
>> char_arr.map(&:upcase).join
>> end
=> :yeller
>> yeller(['o', 'l', 'd'])
=> "OLD"
>> def random_subdomain
>> ('a'..'z').to_a.sample(8).join
>> end
=> :random_subdomain
>> random_subdomain
=> "rsbpxjgf"
>> def random_subdomain
>> ('a'..'z').to_a.sample(8).join
>> end
=> :random_subdomain
>> random_subdomain
=> "rsbpxjgf"
>> hash = { 'one' => 'uno', 'two' => 'dos', 'three' => 'tres' }
=> {"one"=>"uno", "two"=>"dos", "three"=>"tres"}
>> hash.each do |key, value|
?> puts "'#{key}' in Spanish is '#{value}'"
>> end
'one' in Spanish is 'uno'
'two' in Spanish is 'dos'
'three' in Spanish is 'tres'
=> {"one"=>"uno", "two"=>"dos", "three"=>"tres"}
>> person1 = { first: "Dwayne", last: "Lusk" }
=> {:first=>"Dwayne", :last=>"Lusk"}
>> person2 = { first: "Susan", last: "Lusk" }
=> {:first=>"Susan", :last=>"Lusk"}
>> person3 = { first: "Josh", last: "Lusk" }
=> {:first=>"Josh", :last=>"Lusk"}
>> params = { father: person1, mother: person2, child: person3 }
=> {:father=>{:first=>"Dwayne", :last=>"Lusk"}, :mother=>{:first=>"Susan", :last=>"Lusk"}, :child=>{:first=>"Josh", :last=>"Lusk"}}
>> params[:father][:first]
=> "Dwayne"
>> user = { name: "Josh Lusk", email: "josh@example.com", password_digest: "lybfcdsopjbcxsdf" }
=> {:name=>"Josh Lusk", :email=>"josh@example.com", :password_digest=>"lybfcdsopjbcxsdf"}
>> { "a" => 100, "b" => 200 }.merge({ "b" => 300 })
=> {"a"=>100, "b"=>300}
(0..10)
is the literal constructor for the range of integers from 1 to 10Range.new(0, 10)
is the constructor using theRange
class and thenew
method.
>> (0..10) == Range.new(0, 10)
=> true
- The class hierarchy for a range is
Range -> Class -> Module -> Object -> BasicObject
, the class hierarchy for a hash isHash -> Class -> Module -> Object -> BasicObject
, and the class hierarchy for a symbol isSymbol -> Class -> Module -> Object -> BasicObject
.
>> class Word < String
>> def palindrome?
>> self == reverse
>> end
>> end
=> :palindrome?
>> Word.new('level').palindrome?
=> true
>> "racecar".palindrome?
=> true
>> "onomatopoeia".palindrome?
=> false
>> "Malayalam".downcase.palindrome?
=> true
>> class String
>> def shuffle
>> self.split('').shuffle.join
>> end
>> end
=> :shuffle
>> "foobar".shuffle
=> "rbfooa"
>> class String
>> def shuffle
>> split('').shuffle.join
>> end
>> end
=> :shuffle
>> "foobar".shuffle
=> "oabfro"
>> user = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
user.class
=> User(id: integer, name: string, email: string, created_at: datetime, updated_at: datetime)
>> user.class.superclass
=> ApplicationRecord(abstract)
>> user.class.superclass.superclass
=> ActiveRecord::Base
>> user.class.superclass.superclass.superclass
=> Object
class User
attr_accessor :first_name, :last_name, :email
def initialize(attributes = {})
@first_name = attributes[:first_name]
@last_name = attributes[:last_name]
@email = attributes[:email]
end
def full_name
"#{@first_name} #{@last_name}"
end
def formatted_email
"#{full_name} <#{@email}>"
end
end
def alphabetical_name
"#{@last_name}, #{@first_name}"
end
>> require './example_user'
=> true
>> user = User.new(first_name: "Josh", last_name: "Lusk")
=> #<User:0x00007f855acf1958 @first_name="Josh", @last_name="Lusk", @email=nil>
>> user.full_name.split == user.alphabetical_name.split(', ').reverse
=> true
$ curl -OL cdn.learnenough.com/kitten.jpg
$ mv kitten.jpg app/assets/images/kitten.jpg
<%= image_tag("kitten.png", alt: "Kitten") %>
<%#= image_tag("kitten.jpg", alt: "Kitten") %>
img {
display: none;
}
- Adding the
rails_default
partial acted as expected.
StaticPagesControllerTest#test_should_get_contact:
ActionView::Template::Error: Missing partial layouts/_rails_default with {:locale=>[:en], :formats=>[:html], :variants=>[], :handlers=>[:raw, :erb, :html, :builder, :ruby, :coffee, :jbuilder]}. Searched in:
* "/Users/joshualusk/Code/sample_app/app/views"
5 runs, 9 assertions, 0 failures, 0 errors, 0 skips
- The CSS for the footer has been converted to SCSS.
get '/help', to: 'static_pages#help', as: 'helf'
test "should get help" do
get helf_path
assert_response :success
assert_select "title", "Help | #{@base_title}"
end
- Changes successfully reverted and tests passing.
<li><%= link_to "Help", helf_path %></li>
- Changes successfuly reverted.
<li><%= link_to "About", contact_path %></li>
<li><%= link_to "Contact", contact_path %></li>
test "full title helper" do
assert_equal full_title, "Ruby on Rails Tutorial Sample App"
assert_equal full_title("Help"), "Help | Ruby on Rails Tutorial Sample App"
end
- In
config/routes.rb
:
get 'users/new', as: 'signup'
In test/controllers/users_controller_test.rb
:
test "should get new" do
get signup_path
assert_response :success
end
- The temporary
config/routes.rb
change does allow the tests to pass.
- I did solve the exercise in Section 5.4.1.1.
- Test verified to work by commenting out
/signup
route.
get signup_path
assert_select "title", full_title("Sign up")
db/schema.db
contains a similarcreate_table
command to the migration file, along with other lines of code:
ActiveRecord::Schema.define(version: 20180117172914) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "users", force: :cascade do |t|
t.string "name"
t.string "email"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
end
- Code contents of
db/schema.db
after running$ rails db:rollback
:
ActiveRecord::Schema.define(version: 0) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
end
- After running
$ rails db:migrate
,db/schema.rb
looks the same as listed in exercise 1.
>> user = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
>> user.class
=> User(id: integer, name: string, email: string, created_at: datetime, updated_at: datetime)
>> user.class.superclass
=> ApplicationRecord(abstract)
>> user.class.superclass.superclass
=> ActiveRecord::Base
>> user.name.class
=> String
>> user.email.class
=> String
>> user.created_at.class
=> ActiveSupport::TimeWithZone
>> user.updated_at.class
=> ActiveSupport::TimeWithZone
>> User.find_by(name: 'Josh Lusk')
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."name" = $1 LIMIT $2 [["name", "Josh Lusk"], ["LIMIT", 1]]
=> #<User id: 1, name: "Josh Lusk", email: "josh@example.com", created_at: "2018-01-17 17:52:01", updated_at: "2018-01-17 17:52:01">
>> User.find_by_name('Josh Lusk')
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."name" = $1 LIMIT $2 [["name", "Josh Lusk"], ["LIMIT", 1]]
=> #<User id: 1, name: "Josh Lusk", email: "josh@example.com", created_at: "2018-01-17 17:52:01", updated_at: "2018-01-17 17:52:01">
>> User.all.class
=> User::ActiveRecord_Relation
>> User.all.length
User Load (0.5ms) SELECT "users".* FROM "users"
=> 2
>> user.name = "Josh Lusk"
=> "Josh Lusk"
>> user.save
(0.3ms) SAVEPOINT active_record_1
SQL (0.5ms) UPDATE "users" SET "name" = $1, "updated_at" = $2 WHERE "users"."id" = $3 [["name", "Josh Lusk"], ["updated_at", "2018-01-17 18:09:58.956613"], ["id", 1]]
(0.2ms) RELEASE SAVEPOINT active_record_1
=> true
>> user.update_attributes(email: 'josh@example.com')
(0.3ms) SAVEPOINT active_record_1
SQL (0.5ms) UPDATE "users" SET "email" = $1, "updated_at" = $2 WHERE "users"."id" = $3 [["email", "josh@example.com"], ["updated_at", "2018-01-17 18:10:25.000923"], ["id", 1]]
(0.2ms) RELEASE SAVEPOINT active_record_1
=> true
>> user.created_at = 1.year.ago
=> Tue, 17 Jan 2017 18:10:51 UTC +00:00
>> user.save
(0.3ms) SAVEPOINT active_record_1
SQL (0.6ms) UPDATE "users" SET "created_at" = $1, "updated_at" = $2 WHERE "users"."id" = $3 [["created_at", "2017-01-17 18:10:51.168168"], ["updated_at", "2018-01-17 18:10:53.677700"], ["id", 1]]
(0.3ms) RELEASE SAVEPOINT active_record_1
=> true
>> User.new(name: "Example User", email: "user@example.com").valid?
=> true
>> User.new.valid?
=> true
>> u = User.new
=> #<User id: nil, name: nil, email: nil, created_at: nil, updated_at: nil>
>> u.valid?
=> false
>> u.errors.full_messages
=> ["Name can't be blank", "Email can't be blank"]
>> u.errors.messages
=> {:name=>["can't be blank"], :email=>["can't be blank"]}
>> u.errors.messages.class == Hash
=> true
>> u.errors[:email]
=> ["can't be blank"]
>> user = User.new(name: "a" * 51, email: "a" * 244 + "@example.com")
=> #<User id: nil, name: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...", email: "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa...", created_at: nil, updated_at: nil>
>> user.valid?
=> false
>> user.errors.full_messages
=> ["Name is too long (maximum is 50 characters)", "Email is too long (maximum is 255 characters)"]
$ rails test:models
Started with run options --seed 41153
Run options: --seed 41153--=---=---=---=---=--] 0% Time: 00:00:00, ETA: ??:??:??
# Running:
..... 7/6: [=============================== ] 85% Time: 00:00:00, ETA: 00:. 7/7: [====================================] 100% Time: 00:00:00, Time: 00:00:00
.
Finished in 0.04103s
7 tests, 16 assertions, 0 failures, 0 errors, 0 skips
Finished in 0.054347s, 128.8020 runs/s, 294.4045 assertions/s.
7 runs, 16 assertions, 0 failures, 0 errors, 0 skips
- Solution presented in Listing 6.33.
- The
before_save
callback can be written asbefore_save { email.downcase! }
to modify the email attribute directly.
>> user = User.new(name: "Example User", email: "user@example.com")
=> #<User id: nil, name: "Example User", email: "user@example.com", created_at: nil, updated_at: nil, password_digest: nil>
>> user.valid?
User Exists (1.6ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) LIMIT $2 [["email", "user@example.com"], ["LIMIT", 1]]
=> false
>> user.errors.full_messages
=> ["Password can't be blank"]
>>
>> user = User.new(name: "Example User", email: "user@example.com", password: "a" * 5, password_confirmation: "a" * 5)
=> #<User id: nil, name: "Example User", email: "user@example.com", created_at: nil, updated_at: nil, password_digest: "$2a$10$2YbdzqoPAuIralVqoHzaBO.PfPqA2547P.UxMYgEy9D...">
>> user.valid?
User Exists (0.6ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) LIMIT $2 [["email", "user@example.com"], ["LIMIT", 1]]
=> false
>> user.errors.full_messages
=> ["Password is too short (minimum is 6 characters)"]
>> user = User.find_by(email: "jlusk@example.com")
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."email" = $1 LIMIT $2 [["email", "jlusk@example.com"], ["LIMIT", 1]]
=> #<User id: 6, name: "Josh Lusk", email: "jlusk@example.com", created_at: "2018-01-17 20:30:34", updated_at: "2018-01-17 20:30:34", password_digest: "$2a$10$PzRIAXrzLt7r22udGGJIAefVk7I.6VMXnuWc62mX2Th...">
- The call to
save
did not work because the validations around thepassword
virtual attribute were not met.
>> user.name = "Josh H. Lusk"
=> "Josh H. Lusk"
>> user.save
(0.3ms) BEGIN
User Exists (0.6ms) SELECT 1 AS one FROM "users" WHERE LOWER("users"."email") = LOWER($1) AND ("users"."id" != $2) LIMIT $3 [["email", "jlusk@example.com"], ["id", 6], ["LIMIT", 1]]
(0.2ms) ROLLBACK
=> false
>> user.errors.full_messages
=> ["Password can't be blank", "Password is too short (minimum is 6 characters)"]
>> user.update_attribute(:name, "Josh H. Lusk")
(0.3ms) BEGIN
SQL (0.7ms) UPDATE "users" SET "name" = $1, "updated_at" = $2 WHERE "users"."id" = $3 [["name", "Josh H. Lusk"], ["updated_at", "2018-01-17 20:39:54.809455"], ["id", 6]]
(2.3ms) COMMIT
=> true
- The controller and action of the params hash for
/about
arestatic_pages
andabout
, respectively.
>> user = User.first
User Load (0.5ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<User id: 1, name: "Example User", email: "user@example.com", created_at: "2018-01-21 04:05:46", updated_at: "2018-01-21 04:05:46", password_digest: "$2a$10$Nzw7IByN9Y23cUP3zPf/SOpcGTvPoOlN.eYPlF2ayRY...">
>> puts user.attributes.to_yaml
---
id: 1
name: Example User
email: user@example.com
created_at: !ruby/object:ActiveSupport::TimeWithZone
utc: &1 2018-01-21 04:05:46.891622000 Z
zone: &2 !ruby/object:ActiveSupport::TimeZone
name: Etc/UTC
time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
utc: &3 2018-01-21 04:05:46.891622000 Z
zone: *2
time: *3
password_digest: "$2a$10$Nzw7IByN9Y23cUP3zPf/SOpcGTvPoOlN.eYPlF2ayRYPL6tgpfnje"
=> nil
>> y user.attributes
---
id: 1
name: Example User
email: user@example.com
created_at: !ruby/object:ActiveSupport::TimeWithZone
utc: &1 2018-01-21 04:05:46.891622000 Z
zone: &2 !ruby/object:ActiveSupport::TimeZone
name: Etc/UTC
time: *1
updated_at: !ruby/object:ActiveSupport::TimeWithZone
utc: &3 2018-01-21 04:05:46.891622000 Z
zone: *2
time: *3
password_digest: "$2a$10$Nzw7IByN9Y23cUP3zPf/SOpcGTvPoOlN.eYPlF2ayRYPL6tgpfnje"
=> nil
<%= @user.name %>, <%= @user.email %> <br>
Created at <%= @user.created_at %>, updated last at <%= @user.updated_at %>.
- Refreshing the browser with the time code embedded (see below) reevaluates/redisplays the current time.
<%= @user.name %>, <%= @user.email %> <br>
Created at <%= @user.created_at %>, updated last at <%= @user.updated_at %>. <br>
Current Time: <%= Time.now %>.
- Displaying the
params
hash as YAML (see below) looks exactly the same as the debug output in the HTML.
Started GET "/users/1" for 127.0.0.1 at 2018-01-20 23:20:03 -0500
Processing by UsersController#show as HTML
Parameters: {"id"=>"1"}
User Load (0.2ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
Return value is: nil
[1, 10] in /Users/joshualusk/environment/sample_app/app/controllers/users_controller.rb
1: class UsersController < ApplicationController
2:
3: def show
4: @user = User.find(params[:id])
5: debugger
=> 6: end
7:
8: def new
9: end
10: end
(byebug) puts params.to_yaml
--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
controller: users
action: show
id: '1'
permitted: false
nil
Started GET "/users/new" for 127.0.0.1 at 2018-01-20 23:22:43 -0500
Processing by UsersController#new as HTML
Return value is: nil
[1, 10] in /Users/joshualusk/environment/sample_app/app/controllers/users_controller.rb
1: class UsersController < ApplicationController
2:
3: def show
4: @user = User.find(params[:id])
5: end
6:
7: def new
8: debugger
=> 9: end
10: end
(byebug) @user
nil
- The MD5 hash associated with the image for my gravatar email address is a61b37d367dff8110f986469327d1cc4.
- The code in listing
7.12
does correctly manipulate the size of the image based on the keyword argument. - The code in listing
7.13
is confirmed to be able to be used in place of the code in listing7.12
.
undefined method `nome' for #<User:0x00007f82d2cf2220>
Did you mean? name
foobar
would be a bad choice for the block variable (currentlyf
) because whilef
can me mapped toform
as a good shorthand variable,foobar
is simply a placeholder word that in no way relates toform
.
- Learn Enough HTML to be Dangerous does not include a server-side component that, in most simple examples, would be required to demonstrate the function of a
form
tag.
- Debug information (from
/signup?admin=1
path) displayed below:
--- !ruby/object:ActionController::Parameters
parameters: !ruby/hash:ActiveSupport::HashWithIndifferentAccess
admin: '1'
controller: users
action: new
permitted: false
- Chaing the minum password length to 5 updates the error message to read:
Password is too short (minimum is 5 characters)
. - We've pointed the
signup
route tousers#new
, but the call to render thenew
view from thecreate
action results in the path being/users
(see Table 7.1).
test "invalid signup information" do
get signup_path
assert_no_difference 'User.count' do
post users_path, params: { user: { name: "",
email: "user@invalid",
password: "foo",
password_confirmation: "bar" } }
end
assert_template 'users/new'
assert_select 'div#error_explanation'
assert_select 'div.alert.alert-danger'
assert_select "li", "Name can't be blank"
assert_select "li", "Email is invalid"
assert_select "li", "Password confirmation doesn't match Password"
min_validation = User.validators_on(:password).find do |v|
v.options.key?(:minimum)
end
min_length = min_validation.options[:minimum]
assert_select "li", "Password is too short (minimum is #{min_length} characters)"
end
- The tests are still green because pointing
post /signup
tousers#create
just add another way to navigate to that route, it does not invalidate the use ofpost /users
as a means to trigger theusers#create
action. - The tests are still green aftering modifying the
new.html.erb
users view to match Listing 7.2.6. - Adding
assert_select 'form[action="/signup"]'
as the second assertion in theusers_signup
integration test helps verify that the sign up form isPOST
ing to the/signup
route, not the/users
route.
>> user = User.find(2) # User created on form submission (2nd overall user)
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 2], ["LIMIT", 1]]
=> #<User id: 2, name: "Josh Lusk", email: "jlusk@example.com", created_at: "2018-01-21 21:06:43", updated_at: "2018-01-21 21:06:43", password_digest: "$2a$10$7jQSCRZ4z8h0eBRnqWYk5.8r4ZFzUKuYvFylU/gxCVt...">
>> user.attributes
=> {"id"=>2, "name"=>"Josh Lusk", "email"=>"jlusk@example.com", "created_at"=>Sun, 21 Jan 2018 21:06:43 UTC +00:00, "updated_at"=>Sun, 21 Jan 2018 21:06:43 UTC +00:00, "password_digest"=>"$2a$10$7jQSCRZ4z8h0eBRnqWYk5.8r4ZFzUKuYvFylU/gxCVt0ypb6DRime"}
- Verified that by updating Listing 7.28 and submitting a valid user that
redirect_to user_url(@user)
has the same effect asredirect_to @user
.
>> "#{:success}"
=> "success"
- The section of the flash iteration that sets the class names for the
div
could be rewritten as follows:
<div class="<%= "alert alert-#{message_type}" %>">
- Verified the user was successfully created as in Listing 7.32.
- Verified that the Gravatar correctly appear for a new user created with my personal email address.
- The line
assert_not flash.empty?
was added to the bottom of the signup validity test. - All tests still pass after implementing the change in Listing 7.35.
- The tests do fail when the
redirect_to
line in commented out of the User controller'screate
method (action). - If
@user.save
is returning false, most likely the validations are catching incorrect user information that is trying to be saved (triggered whenUser.count
is not one more that it was before).
- The SSL lock and https appear in next to the URL in my heroku app.
- My gravatar does appear correctly on the production site.
GET login_path
routes to the Sessions Controller'sshow
action andPOST login_path
routes to the Sessions Controller's 'create` action.
$ rails routes | grep 'users#'
signup GET /signup(.:format) users#new
POST /signup(.:format) users#create
users GET /users(.:format) users#index
POST /users(.:format) users#create
new_user GET /users/new(.:format) users#new
edit_user GET /users/:id/edit(.:format) users#edit
user GET /users/:id(.:format) users#show
PATCH /users/:id(.:format) users#update
PUT /users/:id(.:format) users#update
DELETE /users/:id(.:format) users#destroy
$ rails routes | grep 'sessions#'
login GET /login(.:format) sessions#new
POST /login(.:format) sessions#create
logout DELETE /logout(.:format) sessions#destroy
$ rails routes | grep 'users#' | wc -l
10
$ rails routes | grep 'sessions#' | wc -l
3
- Because we addded the key
:url
to theform_for
's hash with the valuelogin_path
, the path the URL will be posting to is/login
. inconfig/routes.rb
, this path is routed to go to thecreate
action in the sessions controller.
>> user = nil
=> nil
>> !!(user && user.authenticate('foobar'))
=> false
>> user = User.first
User Load (0.7ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<User id: 6, name: "Josh H. Lusk", email: "jlusk@example.com", created_at: "2018-01-17 20:30:34", updated_at: "2018-01-17 20:39:54", password_digest: "$2a$10$PzRIAXrzLt7r22udGGJIAefVk7I.6VMXnuWc62mX2Th...">
>> !!(user && user.authenticate('foobaz'))
=> false
>> !!(user && user.authenticate('foobar'))
=> true
- Verified that the flash message disappears when you click on a second page, away from the login view.
- Found using Edit My Cookie Chrome Extension:
- 22/01/2019 4:24PM is the
expire
time on the session token
>> User.find_by(id: 1000)
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1000], ["LIMIT", 1]]
=> nil
- Specifically note that no SQL is executed on the last
||=
used in conjunction with@current_user
:
>> session = {}
=> {}
>> session[:user_id] = nil
=> nil
>> @current_user ||= User.find_by(id: session[:user_id])
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."id" IS NULL LIMIT $1 [["LIMIT", 1]]
=> nil
>> session[:user_id] = User.first.id
User Load (0.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> 1
>> @current_user ||= User.find_by(id: session[:user_id])
User Load (0.5ms) SELECT "users".* FROM "users" WHERE "users"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2018-01-21 21:24:43", updated_at: "2018-01-21 21:24:43", password_digest: "$2a$10$pYeW./EatWhQgNsFlkE1nOIlUmjLPu9IjBLxQAYfjce...">
>> @current_user ||= User.find_by(id: session[:user_id])
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2018-01-21 21:24:43", updated_at: "2018-01-21 21:24:43", password_digest: "$2a$10$pYeW./EatWhQgNsFlkE1nOIlUmjLPu9IjBLxQAYfjce...">
- Removing the
!
from the line !current_user.nil? (within thelogged_in?
method) does cause the tests intest/integration/users_login_test.rb
to fail. - The tests again path when the
!
is reinstated.
- The test suite become red if you comment out the
log_in
line in Listing 8.25. - The test suite is confirmed to toggle between red and green based on whether or not the
log_in
line in Listing 8.24 is commented out or not.
- Verified that the logout link works as expected. Once the redirect happens, the Account drop-down is replaced by the Log in link, which is what gets asserted in the last three lines of the test.
- The session is correctly removed after logging out.
>> user = User.first
User Load (0.5ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2018-01-21 21:24:43", updated_at: "2018-01-21 21:24:43", password_digest: "$2a$10$pYeW./EatWhQgNsFlkE1nOIlUmjLPu9IjBLxQAYfjce...", remember_digest: nil>
>> user.remember
(0.3ms) BEGIN
SQL (51.2ms) UPDATE "users" SET "updated_at" = $1, "remember_digest" = $2 WHERE "users"."id" = $3 [["updated_at", "2018-01-29 04:39:23.382813"], ["remember_digest", "$2a$10$WautEzRlvGWz2WyiRA6ukOkO5ddMAJt4Kr6zblyA.veO8re/LvctW"], ["id", 1]]
(0.6ms) COMMIT
=> true
>> user.remember_token
=> "iNlGdGeybz9yzt90jLPONA"
>> user.remember_digest
=> "$2a$10$WautEzRlvGWz2WyiRA6ukOkO5ddMAJt4Kr6zblyA.veO8re/LvctW"
- Both methods (outlined in Listing 9.4 and Listing 9.5 work correctly.
>> user = User.first
User Load (0.5ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT $1 [["LIMIT", 1]]
=> #<User id: 1, name: "Rails Tutorial", email: "example@railstutorial.org", created_at: "2018-01-21 21:24:43", updated_at: "2018-01-29 04:55:49", password_digest: "$2a$10$pYeW./EatWhQgNsFlkE1nOIlUmjLPu9IjBLxQAYfjce...", remember_digest: "$2a$10$2pVtFKovRYJDn5QrWXh6jeMXfwuCxrGq40pt7S/ckeE...">
>> user.authenticated?('notokenyet')
=> false
>> user.remember
(0.3ms) BEGIN
SQL (0.6ms) UPDATE "users" SET "updated_at" = $1, "remember_digest" = $2 WHERE "users"."id" = $3 [["updated_at", "2018-01-29 04:59:40.255900"], ["remember_digest", "$2a$10$9jvDJhDMEG2mh9OfLx5E2O2g8JQSialxY1YYah8GJdqxeHOcfs1.q"], ["id", 1]]
(0.8ms) COMMIT
=> true
>> user.authenticated?(user.remember_token)
=> true
>>
Finished in 0.54743s
24 tests, 68 assertions, 0 failures, 0 errors, 0 skips
...
Finished in 0.578573s, 41.4814 runs/s, 117.5305 assertions/s.
24 runs, 68 assertions, 0 failures, 0 errors, 0 skips
- The checkbox is having its intended effect; checking it adds both a
remember_token
and auser_id
cookie to the browser, and not checking it adds neither.
>> 1 == 2 ? 'no way' : 'phew!'
=> "phew!"
>> 2 == 2 ? 'that\'s better' : 'not gonna happen'
=> "that's better"
- The test fails with the
authenticated?
part of the expression removed:
Failure:
SessionsHelperTest#test_current_user_returns_nil_when_remember_digest_is_wrong [/Users/joshualusk/environment/sample_app/test/helpers/sessions_helper_test.rb:17]:
Expected #<User id: 208889123, name: "Foo Bar", email: "foo@bar.com", created_at: "2018-02-03 15:42:48", updated_at: "2018-02-03 15:42:48", password_digest: "$2a$04$vpjPuDJWYiardb28TO1/yet4Tx6XudLZ.rjnrhDAEDK...", remember_digest: "$2a$04$pMwZWHcZ8RtD7o5M13M6ielnjUKyCf0P/.GvKlRqVK5..."> to be nil.