Ch 14: Following users Flashcards

You may prefer our related Brainscape-certified flashcards:
1
Q

Create a new topic branch

A

Create a new topic branch

git checkout -b following-users

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Generate migration for relationship model

A

Generate migration for relationship model

$ rails generate model Relationship follower_id:integer followed_id:integer

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

add an index on each column for efficiency

A

add an index on each column for efficiency

db/migrate/[timestamp]_create_relationships.rbclass CreateRelationships < ActiveRecord::Migration[5.0] def change create_table :relationships do |t| t.integer :follower_id t.integer :followed_id t.timestamps end add_index :relationships, :follower_id add_index :relationships, :followed_id add_index :relationships, [:follower_id, :followed_id], unique: true endend

  • This code also includes a multiple-key index that enforces uniqueness on (follower_id, followed_id) pairs, so that a user can’t follow another user more than once.
  • migrate with rails db:migrate
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

establish the association between users and relationships.

A

establish the association between users and relationships.

  • A user has_many relationships, and—since relationships involve two users—a relationship belongs_to both a follower and a followed user.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

create new relationships using the user association

A

create new relationships using the user association

user.active_relationships.build(followed_id: ...)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

minipost vs relationships

A

minipost vs relationships

  • minipost: This works because by convention Rails looks for a Micropost model corresponding to the :microposts symbol.

class User < ApplicationRecord has_many :microposts .end

  • relationship: even though the underlying model is called Relationship. We will thus have to tell Rails the model class name to look for.

has_many :active_relationships

  • minipost: This works because the microposts table has a user_id attribute to identify the user. An id used in this manner to connect two database tables is known as a foreign key, and when the foreign key for a User model object is user_id, Rails infers the association automatically: by default, Rails expects a foreign key of the form _id, where is the lower-case version of the class name.”

class Micropost < ApplicationRecord belongs_to :user . end

  • relationship: we are still dealing with users, the user following another user is now identified with the foreign key follower_id, so we have to tell that to Rails.

app/models/user.rb class User < ApplicationRecord has_many :microposts, dependent: :destroy has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy . . . end

  • (Since destroying a user should also destroy that user’s relationships, we’ve added dependent: :destroy to the association.)
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Add the follower belongs_to association to the Relationship model.

A

Add the follower belongs_to association to the Relationship model.

app/models/relationship.rbclass Relationship < ApplicationRecord belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User"end

  • The followed association isn’t actually needed until later, but the parallel follower/followed structure is clearer if we implement them both at the same time.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

A summary of user/active relationship association methods.

A

A summary of user/active relationship association methods.

Method | Purposeactive_relationship.follower | Returns the followeractive_relationship.followed | Returns the followed useruser.active_relationships.create(followed_id: other_user.id) | Creates an active relationship associated with useruser.active_relationships.create!(followed_id: other_user.id) | Creates an active relationship associated with user(exception on failure)user.active_relationships.build(followed_id: other_user.id) | Returns a new Relationship object associated with user

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Add test for relationship model validations

A

Add test for relationship model validations

test/models/relationship_test.rbrequire 'test_helper'class RelationshipTest < ActiveSupport::TestCase def setup @relationship = Relationship.new(follower_id: users(:michael).id, followed_id: users(:archer).id) end test "should be valid" do assert @relationship.valid? end test "should require a follower_id" do @relationship.follower_id = nil assert_not @relationship.valid? end test "should require a followed_id" do @relationship.followed_id = nil assert_not @relationship.valid? endend

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Add the Relationship model validations.

A

Add the Relationship model validations.

Adding the Relationship model validations.app/models/relationship.rbclass Relationship < ApplicationRecord belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" validates :follower_id, presence: true validates :followed_id, presence: trueend

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

Remove content of relationship fixtures

A

Remove content of relationship fixtures

test/fixtures/relationships.yml# empty

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

has_many :through

A

has_many :through

app/models/user.rbclass User < ApplicationRecord has_many :microposts, dependent: :destroy has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy has_many :following, through: :active_relationships, source: :followed . . .end

  • By default, in a has_many :through association Rails looks for a foreign key corresponding to the singular version of the association. In other words

has_many :followeds, through: :active_relationships

  • Rails would see “followeds” and use the singular “followed”, assembling a collection using the followed_id in the relationships table.
  • But, user.followeds is rather awkward, so we’ll write user.following instead. Naturally, Rails allows us to override the default, in this case using the source parameter, which explicitly tells Rails that the source of the following array is the set of followed ids.
  • The association defined in this code leads to a powerful combination of Active Record and array-like behavior. For example, we can check if the followed users collection includes another user with the include? method, or find objects through the association:”

user.following.include?(other_user) user.following.find(other_user)

  • We can also add and delete elements just as with arrays:

user.following << other_useruser.following.delete(other_user)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

write a short test for the User model to test following mechanisms

A

write a short test for the User model to test following mechanisms

test/models/user_test.rbrequire 'test_helper'class UserTest < ActiveSupport::TestCase . . . test "should follow and unfollow a user" do michael = users(:michael) archer = users(:archer) assert_not michael.following?(archer) michael.follow(archer) assert michael.following?(archer) michael.unfollow(archer) assert_not michael.following?(archer) endend

  • use following? to make sure the user isn’t following the other user
  • use follow to follow another user
  • use following? to verify that the operation succeeded
  • finally unfollow and verify that it worked.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

write the follow, unfollow, and following? methods

A

write the follow, unfollow, and following? methods

app/models/user.rbclass User < ApplicationRecord . . . def feed . . . end # Follows a user. def follow(other_user) following << other_user end # Unfollows a user. def unfollow(other_user) following.delete(other_user) end # Returns true if the current user is following the other user. def following?(other_user) following.include?(other_user) end private . . .end

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

add a user.followers method to go with user.following

A

add a user.followers method to go with user.following

app/models/user.rbclass User < ApplicationRecord has_many :microposts, dependent: :destroy has_many :active_relationships, class_name: "Relationship", foreign_key: "follower_id", dependent: :destroy has_many :passive_relationships, class_name: "Relationship", foreign_key: "followed_id", dependent: :destroy has_many :following, through: :active_relationships, source: :followed has_many :followers, through: :passive_relationships, source: :follower . . .end

  • the technique is exactly the same as for followed users, with the roles of follower_id and followed_id reversed, and with passive_relationships in place of active_relationships.
  • It’s worth noting that we could actually omit the :source key for followers in the code, using simply”

has_many :followers, through: :passive_relationships

  • This is because, in the case of a :followers attribute, Rails will singularize “followers” and automatically look for the foreign key follower_id in this case. But this code keeps the :source key to emphasize the parallel structure with the has_many :following association.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

Add assertion to see if archers followers include michael in the test

A

Add assertion to see if archers followers include michael in the test

test/models/user_test.rbrequire 'test_helper'class UserTest < ActiveSupport::TestCase . . . test "should follow and unfollow a user" do michael = users(:michael) archer = users(:archer) assert_not michael.following?(archer) michael.follow(archer) assert michael.following?(archer) assert archer.followers.include?(michael) michael.unfollow(archer) assert_not michael.following?(archer) endend

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
17
Q

arrange for the first user to follow users 3 through 51, and then have users 4 through 41 follow that user back in seed file

A

arrange for the first user to follow users 3 through 51, and then have users 4 through 41 follow that user back in seed file

db/seeds.rb# UsersUser.create!(name: "Example User", email: "example@railstutorial.org", password: "foobar", password_confirmation: "foobar", admin: true, activated: true, activated_at: Time.zone.now)99.times do |n| name = Faker::Name.name email = "example-#{n+1}@railstutorial.org" password = "password" User.create!(name: name, email: email, password: password, password_confirmation: password, activated: true, activated_at: Time.zone.now)end# Micropostsusers = User.order(:created_at).take(6)50.times do content = Faker::Lorem.sentence(5) users.each { |user| user.microposts.create!(content: content) }end# Following relationshipsusers = User.alluser = users.firstfollowing = users[2..50]followers = users[3..40]following.each { |followed| user.follow(followed) }followers.each { |follower| follower.follow(user) }

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
18
Q

Create routes for the followers and following links

A

Create routes for the followers and following links

Rails.application.routes.draw do root 'static_pages#home' get '/help', to: 'static_pages#help' get '/about', to: 'static_pages#about' get '/contact', to: 'static_pages#contact' get '/signup', to: 'users#new' get '/login', to: 'sessions#new' post '/login', to: 'sessions#create' delete '/logout', to: 'sessions#destroy' resources :users do member do get :following, :followers end end resources :account_activations, only: [:edit] resources :password_resets, only: [:new, :create, :edit, :update] resources :microposts, only: [:create, :destroy]end

  • The URLs for following and followers will look like /users/1/following and /users/1/followers
  • Since both pages will be showing data, the proper HTTP verb is a GET request, so we use the getmethod to arrange for the URLs to respond appropriately.
  • Meanwhile, the member method arranges for the routes to respond to URLs containing the user id.
  • The other possibility, collection, works without the id, so that the code below would respond to the URL /users/tigers (presumably to display all the tigers in our application).

resources :users do collection do get :tigers endend

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
19
Q

Table of routes generated

A

Table of routes generated

HTTP | request | URL | Action | Named routeGET | /users/1/following | following | following_user_path(1)GET | /users/1/followers | followers | followers_user_path(1)

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
20
Q

define the stats partial

A

define the stats partial

app/views/shared/_stats.html.erb<% @user ||= current_user %>≤div class="stats"> ≤a href="<%= following_user_path(@user) %>"> ≤strong id="following" class="stat"> <%= @user.following.count %> ≤/strong> following ≤/a> ≤a href="<%= followers_user_path(@user) %>"> ≤strong id="followers" class="stat"> <%= @user.followers.count %> ≤/strong> followers ≤/a>≤/div>

  • Since we will be including the stats on both the user show pages and the home page, the first line of the code picks the right one using

<% @user ||= current_user %>

  • this does nothing when @user is not nil (as on a profile page), but when it is (as on the Home page) it sets @user to the current user.
  • Note also that the following/follower counts are calculated through the associations using”

@user.following.count and @user.followers.count

  • One final detail worth noting is the presence of CSS ids on some elements, as in”

≤strong id="following" class="stat"> ... ≤/strong>

  • This is for the benefit of the Ajax implementation later, which will accesses elements on the page using their unique ids.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
21
Q

Add follower Stats to the homepage

A

Add follower Stats to the homepage

app/views/static_pages/home.html.erb<% if logged_in? %> ≤div class="row"> ≤aside class="col-md-4"> ≤section class="user_info"> <%= render 'shared/user_info' %> ≤/section> ≤section class="stats"> <%= render 'shared/stats' %> ≤/section> ≤section class="micropost_form"> <%= render 'shared/micropost_form' %> ≤/section> ≤/aside> ≤div class="col-md-8"> ≤h3>Micropost Feed≤/h3> <%= render 'shared/feed' %> ≤/div> ≤/div><% else %> . . .<% end %>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
22
Q

style the stats

A

style the stats

app/assets/stylesheets/custom.scss.../* sidebar */....gravatar { float: left; margin-right: 10px;}.gravatar_edit { margin-top: 15px;}.stats { overflow: auto; margin-top: 0; padding: 0; a { float: left; padding: 0 10px; border-left: 1px solid $gray-lighter; color: gray; &amp;:first-child { padding-left: 0; border: 0; } &amp;:hover { text-decoration: none; color: blue; } } strong { display: block; }}.user_avatars { overflow: auto; margin-top: 10px; .gravatar { margin: 1px 1px; } a { padding: 0; }}.users.follow { padding: 0;}/* forms */...

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
23
Q

make a partial for the follow/unfollow button

A

make a partial for the follow/unfollow button

app/views/users/_follow_form.html.erb<% unless current_user?(@user) %> ≤div id="follow_form"> <% if current_user.following?(@user) %> <%= render 'unfollow' %> <% else %> <%= render 'follow' %> <% end %> ≤/div><% end %>

  • This does nothing but defer the real work to follow and unfollow partials, which need new routes for the Relationships resource
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
24
Q

Add the routes for user relationships.

A

Add the routes for user relationships.

config/routes.rbRails.application.routes.draw do root 'static_pages#home' get 'help' => 'static_pages#help' get 'about' => 'static_pages#about' get 'contact' => 'static_pages#contact' get 'signup' => 'users#new' get 'login' => 'sessions#new' post 'login' => 'sessions#create' delete 'logout' => 'sessions#destroy' resources :users do member do get :following, :followers end end resources :account_activations, only: [:edit] resources :password_resets, only: [:new, :create, :edit, :update] resources :microposts, only: [:create, :destroy] resources :relationships, only: [:create, :destroy]end

25
Q

Create forms for following and unfollowing a user

A

Create forms for following and unfollowing a user

  • Create a form for following a user.

app/views/users/_follow.html.erb<%= form_for(current_user.active_relationships.build) do |f| %> ≤div><%= hidden_field_tag :followed_id, @user.id %>≤/div> <%= f.submit "Follow", class: "btn btn-primary" %><% end %>

  • Create a form for unfollowing a user.”

app/views/users/_unfollow.html.erb <%= form_for(current_user.active_relationships.find_by(followed_id: @user.id), html: { method: :delete }) do |f| %> <%= f.submit "Unfollow", class: "btn" %> <% end %>

  • These two forms both use form_for to manipulate a Relationship model object; the main difference between the two is that the code for following a user builds a new relationship, whereas the code for unfollowing finds the existing relationship.
  • the former sends a POST request to the Relationships controller to create a relationship, while the latter sends a DELETErequest to destroy a relationship. (We’ll write these actions later)
  • note that the follow form doesn’t have any content other than the button, but it still needs to send the followed_id to the controller. We accomplish this with the hidden_field_tag method in the code for following, which produces HTML of the form

≤input id="followed_id" name="followed_id" type="hidden" value="3" />

26
Q

include the follow form and the following statistics on the user profile page simply by rendering the partials

A

include the follow form and the following statistics on the user profile page simply by rendering the partials

app/views/users/show.html.erb<% provide(:title, @user.name) %>≤div class="row"> ≤aside class="col-md-4"> ≤section> ≤h1> <%= gravatar_for @user %> <%= @user.name %> ≤/h1> ≤/section> ≤section class="stats"> <%= render 'shared/stats' %> ≤/section> ≤/aside> ≤div class="col-md-8"> <%= render 'follow_form' if logged_in? %> <% if @user.microposts.any? %> ≤h3>Microposts (<%= @user.microposts.count %>)≤/h3> ≤ol class="microposts"> <%= render @microposts %> ≤/ol> <%= will_paginate @microposts %> <% end %> ≤/div>≤/div>

27
Q

Write tests for the authorization of the following and followers pages.

A

Write tests for the authorization of the following and followers pages.

test/controllers/users_controller_test.rbrequire 'test_helper'class UsersControllerTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) @other_user = users(:archer) end . . . test "should redirect following when not logged in" do get following_user_path(@user) assert_redirected_to login_url end test "should redirect followers when not logged in" do get followers_user_path(@user) assert_redirected_to login_url endend

28
Q

add two new actions to the Users controller.

A

add two new actions to the Users controller.

app/controllers/users_controller.rbclass UsersController < ApplicationController before_action :logged_in_user, only: [:index, :edit, :update, :destroy, :following, :followers] . . . def following @title = "Following" @user = User.find(params[:id]) @users = @user.following.paginate(page: params[:page]) render 'show_follow' end def followers @title = "Followers" @user = User.find(params[:id]) @users = @user.followers.paginate(page: params[:page]) render 'show_follow' end private . . .end

  • Based on the routes defined in previous code, we need to call them following and followers.
  • Each action needs to set a title, find the user, retrieve either @user.following or @user.followers (in paginated form), and then render the page.
  • The usual Rails convention is to implicitly render the template corresponding to an action, such as rendering show.html.erb at the end of the show action. In contrast, both actions in this code make an explicit call to render, in this case rendering a view called show_follow, which we must create. The reason for the common view is that the ERb is nearly identical for the two cases
29
Q

Create the show_follow view used to render following and followers.

A

Create the show_follow view used to render following and followers.

app/views/users/show_follow.html.erb<% provide(:title, @title) %>≤div class="row"> ≤aside class="col-md-4"> ≤section class="user_info"> <%= gravatar_for @user %> ≤h1><%= @user.name %>≤/h1> <%= link_to "view my profile", @user %>≤/span> Microposts:≤/b> <%= @user.microposts.count %>≤/span> ≤/section> ≤section class="stats"> <%= render 'shared/stats' %> <% if @users.any? %> ≤div class="user_avatars"> <% @users.each do |user| %> <%= link_to gravatar_for(user, size: 30), user %> <% end %> ≤/div> <% end %> ≤/section> ≤/aside> ≤div class="col-md-8"> ≤h3><%= @title %>≤/h3> <% if @users.any? %> ≤ul class="users follow"> <%= render @users %> ≤/ul> <%= will_paginate %> <% end %> ≤/div>≤/div>

  • The actions in the controller render the view from this code in two contexts, “following” and “followers”.
  • Note that nothing in the above code uses the current user, so the same links work for other users.
30
Q

Generate an integration test for following

A

Generate an integration test for following

$ rails generate integration_test following

31
Q

Assemble some test data, which we can do by adding some relationships fixtures to create following/follower relationships.

A

Assemble some test data, which we can do by adding some relationships fixtures to create following/follower relationships.

test/fixtures/relationships.ymlone: follower: michael followed: lanatwo: follower: michael followed: malorythree: follower: lana followed: michaelfour: follower: archer followed: michael

32
Q

Create tests for following/follower pages.

A

Create tests for following/follower pages.

test/integration/following_test.rbrequire 'test_helper'class FollowingTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) log_in_as(@user) end test "following page" do get following_user_path(@user) assert_not @user.following.empty? assert_match @user.following.count.to_s, response.body @user.following.each do |user| assert_select "a[href=?]", user_path(user) end end test "followers page" do get followers_user_path(@user) assert_not @user.followers.empty? assert_match @user.followers.count.to_s, response.body @user.followers.each do |user| assert_select "a[href=?]", user_path(user) end endend

  • To test for the right count, we can use the same assert_match method we used in earlier to test for the display of the number of microposts on the user profile page.
  • note that we include the assertion”

assert_not @user.following.empty?

  • which is included to make sure that the following code isn’t vacuously true (and similarly for followers). In other words, if @user.following.empty? were true, not a single assert_select would execute in the loop, leading the tests to pass and thereby give us a false sense of security.

@user.following.each do |user| assert_select "a[href=?]", user_path(user)end

33
Q

Because following and unfollowing involve creating and destroying relationships, we need a Relationships controller, which we generate as usual

A

Because following and unfollowing involve creating and destroying relationships, we need a Relationships controller, which we generate as usual

$ rails generate controller Relationships

34
Q

Write basic access control tests for relationships

A

Write basic access control tests for relationships

test/controllers/relationships_controller_test.rbrequire 'test_helper'class RelationshipsControllerTest < ActionDispatch::IntegrationTest test "create should require logged-in user" do assert_no_difference 'Relationship.count' do post relationships_path end assert_redirected_to login_url end test "destroy should require logged-in user" do assert_no_difference 'Relationship.count' do delete relationship_path(relationships(:one)) end assert_redirected_to login_url endend

  • enforcing access control on the Relationships controller actions won’t much matter, but we’ll still follow our previous practice of enforcing the security model as early as possible. In particular, we’ll check that attempts to access actions in the Relationships controller require a logged-in user (and thus get redirected to the login page), while also not changing the Relationship count
35
Q

get the tests in relationship controler test to pass by adding the logged_in_user before filter

A

get the tests in relationship controler test to pass by adding the logged_in_user before filter

app/controllers/relationships_controller.rbclass RelationshipsController < ApplicationController before_action :logged_in_user def create end def destroy endend

36
Q

Write the create and destroy actions in the relationships controller

A

Write the create and destroy actions in the relationships controller

app/controllers/relationships_controller.rbclass RelationshipsController < ApplicationController before_action :logged_in_user def create user = User.find(params[:followed_id]) current_user.follow(user) redirect_to user end def destroy user = Relationship.find(params[:id]).followed current_user.unfollow(user) redirect_to user endend

  • To get the follow and unfollow buttons to work, all we need to do is find the user associated with the followed_id in the corresponding form (i.e., follow.html.erb or unfollow.html.erb), and then use the appropriate follow or unfollow method fromUser.rb.
  • We can see from the code why the security issue mentioned above is minor: if an unlogged-in user were to hit either action directly (e.g., using a command-line tool like curl), current_user would be nil, and in both cases the action’s second line would raise an exception, resulting in an error but no harm to the application or its data. It’s best not to rely on that, though, so we’ve taken the extra step and added an additional layer of security.
37
Q

How to add Ajax in rails

A

How to add Ajax in rails

  • change “form_for” to “form_for …, remote: true”
38
Q

Change follow and unfollow forms to use Ajax

A

Change follow and unfollow forms to use Ajax

  • follow form

app/views/users/_follow.html.erb<%= form_for(current_user.active_relationships.build, remote: true) do |f| %> ≤div><%= hidden_field_tag :followed_id, @user.id %>≤/div> <%= f.submit "Follow", class: "btn btn-primary" %><% end %>

  • unfollow form

app/views/users/_unfollow.html.erb<%= form_for(current_user.active_relationships.find_by(followed_id: @user.id), html: { method: :delete }, remote: true) do |f| %> <%= f.submit "Unfollow", class: "btn" %><% end %>

39
Q

arrange for the Relationships controller to respond to Ajax requests.

A

arrange for the Relationships controller to respond to Ajax requests.

app/controllers/relationships_controller.rbclass RelationshipsController < ApplicationController before_action :logged_in_user def create @user = User.find(params[:followed_id]) current_user.follow(@user) respond_to do |format| format.html { redirect_to @user } format.js end end def destroy @user = Relationship.find(params[:id]).followed current_user.unfollow(@user) respond_to do |format| format.html { redirect_to @user } format.js end endend

  • do this using the respond_to method, responding appropriately depending on the type of request. The general pattern looks like this:

respond_to do |format| format.html { redirect_to user } format.jsend

  • The syntax is potentially confusing, and it’s important to understand that in the code above only one of the lines gets executed. (In this sense, respond_to is more like an if-then-else statement than a series of sequential lines.) Adapting the Relationships controller to respond to Ajax involves adding respond_to as above to the create and destroy actions.
  • Note the change from the local variable user to the instance variable @user; in the code there was no need for an instance variable, but now such a variable is necessary for use in later code.
40
Q

Write a small amount of configuration for application.rb to make sure the code will still work in browsers with javascript disabled

A

Write a small amount of configuration for application.rb to make sure the code will still work in browsers with javascript disabled

config/application.rbrequire File.expand_path('../boot', \_\_FILE\_\_)...module SampleApp class Application < Rails::Application . . . # Include the authenticity token in remote forms. config.action_view.embed_authenticity_token_in_remote_forms = true endend

41
Q

How to respond propery for Ajax when javascript is enabled

A

How to respond propery for Ajax when javascript is enabled

  • In the case of an Ajax request, Rails automatically calls a JavaScript embedded Ruby (.js.erb) file with the same name as the action, i.e., create.js.erb or destroy.js.erb.
  • As you might guess, such files allow us to mix JavaScript and embedded Ruby to perform actions on the current page.
  • It is these files that we need to create and edit in order to update the user profile page upon being followed or unfollowed.
  • Inside a JS-ERb file, Rails automatically provides the jQuery JavaScript helpers to manipulate the page using the Document Object Model (DOM).
  • The jQuery library provides a large number of methods for manipulating the DOM
42
Q

The dollar-sign syntax to access a DOM element based on its unique CSS id.

A

The dollar-sign syntax to access a DOM element based on its unique CSS id.

  • For example, to manipulate the follow_form element, we will use the syntax

$("#follow_form")

  • (Recall from earlier code that this is a div that wraps the form, not the form itself.)
  • This syntax, inspired by CSS, uses the # symbol to indicate a CSS id. As you might guess, jQuery, like CSS, uses a dot . to manipulate CSS classes.
  • The second method we’ll need is html, which updates the HTML inside the relevant element with the contents of its argument. For example, to replace the entire follow form with the string “foobar”, we would write”

$("#follow_form").html("foobar")

  • Unlike plain JavaScript files, JS-ERb files also allow the use of embedded Ruby, which we apply in the create.js.erb file to update the follow form with the unfollow partial (which is what should show after a successful following) and update the follower count.
  • This code also uses the escape_javascript method, which is needed to escape out the result when inserting HTML in a JavaScript file.
  • Note the presence of line-ending semicolons, which are characteristic of languages with syntax descended from ALGOL.
43
Q

Write the create and destory js files

A

Write the create and destory js files

  • Create

app/views/relationships/create.js.erb$("#follow_form").html("<%= escape_javascript(render('users/unfollow')) %>");$("#followers").html('<%= @user.followers.count %>');

  • Destroy

app/views/relationships/destroy.js.erb$("#follow_form").html("<%= escape_javascript(render('users/follow')) %>");$("#followers").html('<%= @user.followers.count %>');

44
Q

Now that the follow buttons are working, we’ll write some simple tests to prevent regressions.

A

Now that the follow buttons are working, we’ll write some simple tests to prevent regressions.

test/integration/following_test.rbrequire 'test_helper'class FollowingTest < ActionDispatch::IntegrationTest def setup @user = users(:michael) @other = users(:archer) log_in_as(@user) end . . . test "should follow a user the standard way" do assert_difference '@user.following.count', 1 do post relationships_path, params: { followed_id: @other.id } end end test "should follow a user with Ajax" do assert_difference '@user.following.count', 1 do post relationships_path, xhr: true, params: { followed_id: @other.id } end end test "should unfollow a user the standard way" do @user.follow(@other) relationship = @user.active_relationships.find_by(followed_id: @other.id) assert_difference '@user.following.count', -1 do delete relationship_path(relationship) end end test "should unfollow a user with Ajax" do @user.follow(@other) relationship = @user.active_relationships.find_by(followed_id: @other.id) assert_difference '@user.following.count', -1 do delete relationship_path(relationship), xhr: true end endend

  • To follow a user, we post to the relationships path and verify that the number of followed users increases by 1:

assert_difference '@user.following.count', 1 do post relationships_path, params: { followed_id: @other.id }end

  • This tests the standard implementation, but testing the Ajax version is almost exactly the same; the only difference is the addition of the option xhr: true:”

assert_difference '@user.following.count', 1 do post relationships_path, params: { followed_id: @other.id }, xhr: true end

* Here xhr stands for XmlHttpRequest; setting the xhr option to true issues an Ajax request in the test, which causes the respond\_to block in relationships\_controller.rb to execute the proper JavaScript method. * The same parallel structure applies to deleting users, with delete instead of post. Here we check that the followed user count goes down by 1 and include the relationship and followed user’s id:"

assert_difference '@user.following.count', -1 do delete relationship_path(relationship) end

* and

assert_difference '@user.following.count', -1 do delete relationship_path(relationship), xhr: true end

45
Q

Update the User model test to allow Michael to follow Lana but not Archer which results in Michael seeing Lana’s posts and his own posts, but not Archer’s posts.

A

Update the User model test to allow Michael to follow Lana but not Archer which results in Michael seeing Lana’s posts and his own posts, but not Archer’s posts.

test/models/user_test.rbrequire 'test_helper'class UserTest < ActiveSupport::TestCase . . . test "feed should have the right posts" do michael = users(:michael) archer = users(:archer) lana = users(:lana) # Posts from followed user lana.microposts.each do |post_following| assert michael.feed.include?(post_following) end # Posts from self michael.microposts.each do |post_self| assert michael.feed.include?(post_self) end # Posts from unfollowed user archer.microposts.each do |post_unfollowed| assert_not michael.feed.include?(post_unfollowed) end endend

46
Q

Schematic writeup of selecting all the microposts from the microposts table with ids corresponding to the users being followed by a given user (or the user itself).

A

Schematic writeup of selecting all the microposts from the microposts table with ids corresponding to the users being followed by a given user (or the user itself).

SELECT * FROM micropostsWHERE user_id IN (≤list of ids>) OR user_id = ≤user id>

  • In writing this code, we’ve guessed that SQL supports an IN keyword that allows us to test for set inclusion. (Happily, it does.)
47
Q

By using Active Record’s where method, think of a general selection of all the users posts made by somebody you follow and your own

A

By using Active Record’s where method, think of a general selection of all the users posts made by somebody you follow and your own

Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)

48
Q

Change array items into a string using map

A

Change array items into a string using map

$ rails console>> [1, 2, 3, 4].map { |i| i.to_s }=> ["1", "2", "3", "4"]

49
Q

Short-hand notation for changing an array of items into a string

A

Short-hand notation for changing an array of items into a string

>> [1, 2, 3, 4].map(&amp;:to_s)=> ["1", "2", "3", "4"]

50
Q

Using the join method create a string composed of the ids by joining them on comma-space :

A

Using the join method create a string composed of the ids by joining them on comma-space :

>> [1, 2, 3, 4].map(&amp;:to_s).join(', ')=> "1, 2, 3, 4"

51
Q

construct the necessary array of followed user ids by calling id on each element in user.following for the first user.

A

construct the necessary array of followed user ids by calling id on each element in user.following for the first user.

>> User.first.following.map(&amp;:id)=> [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,42, 43, 44, 45, 46, 47, 48, 49, 50, 51]

52
Q

Active Record’s shortcut for returning the ids that the first user follows

A

Active Record’s shortcut for returning the ids that the first user follows

>> User.first.following_ids=> [3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,42, 43, 44, 45, 46, 47, 48, 49, 50, 51]

  • Here the following_ids method is synthesized by Active Record based on the has_many :following association; the result is that we need only append _ids to the association name to get the ids corresponding to the user.following collection.
53
Q

Create a string with Active records shortcut for returning the ids that the first user follows

A

Create a string with Active records shortcut for returning the ids that the first user follows

>> User.first.following_ids.join(', ')=> "3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22,23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41,42, 43, 44, 45, 46, 47, 48, 49, 50, 51"

54
Q

Write the feed action in User.rb using Active Record’s shortcuts

A

Write the feed action in User.rb using Active Record’s shortcuts

app/models/user.rbclass User < ApplicationRecord . . . # Returns true if a password reset has expired. def password_reset_expired? reset_sent_at < 2.hours.ago end # Returns a user's status feed. def feed Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id) end # Follows a user. def follow(other_user) following << other_user end . . .end

  • When inserting into an SQL string you don’t need to do this a a .join(); the ? interpolation takes care of it for you (and in fact eliminates some database-dependent incompatibilities).This means we can use following_ids by itself.
55
Q

refactor the feed with the slightly modified code

A

refactor the feed with the slightly modified code

app/models/user.rbclass User < ApplicationRecord . . . # Returns a user's status feed. def feed Micropost.where("user_id IN (:following_ids) OR user_id = :user_id", following_ids: following_ids, user_id: id) end . . .end

  • We replaced with the above equivilant

Micropost.where("user_id IN (?) OR user_id = ?", following_ids, id)

  • The question mark syntax is fine, but when we want the same variable inserted in more than one place, the second syntax is more convenient.
56
Q

The above discussion implies that we will be adding a second occurrence of user_id in the SQL query. In particular, we can replace the Ruby code “following_ids” with the SQL snippet

A

The above discussion implies that we will be adding a second occurrence of user_id in the SQL query. In particular, we can replace the Ruby code “following_ids” with the SQL snippet

following_ids = "SELECT followed_id FROM relationships WHERE follower_id = :user_id"

  • This code contains an SQL subselect, and internally the entire select for user 1 would look something like this:

SELECT * FROM micropostsWHERE user_id IN (SELECT followed_id FROM relationships WHERE follower_id = 1) OR user_id = 1

  • This subselect arranges for all the set logic to be pushed into the database, which is more efficient.
57
Q

Write a more efficient feed implementation that takes the searching to the database

A

Write a more efficient feed implementation that takes the searching to the database

app/models/user.rbclass User < ApplicationRecord . . . # Returns a user's status feed. def feed following_ids = "SELECT followed_id FROM relationships WHERE follower_id = :user_id" Micropost.where("user_id IN (#{following_ids}) OR user_id = :user_id", user_id: id) end . . .end

58
Q

14.4.2 What we learned in this chapter

A
  1. 4.2 What we learned in this chapter
    * Rails’ has_many :through allows the modeling of complicated data relationships.
  • The has_many method takes several optional arguments, including the object class name and the foreign key.
  • Using has_many and has_many :through with properly chosen class names and foreign keys, we can model both active (following) and passive (being followed) relationships.
  • Rails routing supports nested routes.
  • The where method is a flexible and powerful way to create database queries.
  • Rails supports issuing lower-level SQL queries if needed.
  • By putting together everything we’ve learned in this book, we’ve successfully implemented user following with a status feed of microposts from followed users.