# coding: utf-8

12.1.4 Читаемые пользователи

Теперь мы переходим к сердцу ассоциаций Relationship: following и followers. Мы начнем с following, как показано в Листинге 12.10.

Листинг 12.10. Тест для атрибута user.following.

spec/models/user_spec.rb
describe User do
  .
  .
  .
  describe "relationships" do

    before(:each) do
      @user = User.create!(@attr)
      @followed = Factory(:user)
    end

    it "should have a relationships method" do
      @user.should respond_to(:relationships)
    end

    it "should have a following method" do
      @user.should respond_to(:following)
    end
  end
end

Реализация впервые использует has_many :through: пользователь имеет много читаемых (пользователей) через взаимоотношения, как показано на Рис. 12.7. По умолчанию, в ассоциации has_many :through Rails ищет внешний ключ, соответствующий ассоциации в единственном числе; другими словами, код

has_many :followeds, :through => :relationships

будет составлять массив, используя followed_id в таблице relationships. Но, как отмечалось в Разделе 12.1.1, user.followeds это довольно неуклюже; гораздо более естественным будет использование “following” в качестве множественноо числа для “followed”, и написание user.following для массива читаемых пользователей. Естественно, Rails позволяет переопределить умолчание, в данном случае с помощью :source параметра (Листинг 12.11), что явно говорит Rails, что источником массива following является множество followed id.

Листинг 12.11. Добавление к модели User ассоциации following с has_many :through.

app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  has_many :microposts, :dependent => :destroy
  has_many :relationships, :foreign_key => "follower_id",
                           :dependent => :destroy
  has_many :following, :through => :relationships, :source => :followed
  .
  .
  .
end

Чтобы создать взаимоотношение с читаемыми (пользователями), мы введем служебный метод follow! и мы сможем написать user.follow!(other_user).7 Мы также добавим связанный булев метод following? для того чтобы протестировать, читает ли пользователь сообщения других пользователей.8 Тесты в Листинге 12.12 показывают как мы планируем использовать эти методы на практике.

Листинг 12.12. Тесты для некоторых служебных методов “following”.

spec/models/user_spec.rb
describe User do
  .
  .
  .
  describe "relationships" do
    .
    .
    .
    it "should have a following? method" do
      @user.should respond_to(:following?)
    end

    it "should have a follow! method" do
      @user.should respond_to(:follow!)
    end

    it "should follow another user" do
      @user.follow!(@followed)
      @user.should be_following(@followed)
    end

    it "should include the followed user in the following array" do
      @user.follow!(@followed)
      @user.following.should include(@followed)
    end
  end
end

Обратите внимание, что мы заменили метод include? виденный в Листинге 11.31 на should include, преобразовав

@user.following.include?(@followed).should be_true

в более ясный и краткий

@user.following.should include(@followed)

Этот пример показывает, насколько гибкой является булевая конвенция RSpec; несмотря на то, что include уже является ключевым словом Ruby (используется для включения модуля, как мы видели, например, в Листинге 9.11), в этом контексте RSpec правильно угадывает, что мы хотим протестировать включение массива.

В коде приложения, метод following? принимает пользователя, называемого followed, и проверяет, существует ли он в базе данных; метод follow! вызывает create! через relationships ассоциацию для создания взаимоотношения с читаемым. Результаты представлены в Листинге 12.13.9

Листинг 12.13. Служебные методы following? и follow!.

app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  def self.authenticate_with_salt(id, stored_salt)
    .
    .
    .
  end

  def following?(followed)
    relationships.find_by_followed_id(followed)
  end

  def follow!(followed)
    relationships.create!(:followed_id => followed.id)
  end
  .
  .
  .
end

Отметим, что в Листинге 12.13 мы опустили self пользователя, написав просто

relationships.create!(...)

вместо эквивалентного кода

self.relationships.create!(...)

Явное включение или невключение self в данном случае дело вкуса.

Конечно, пользователи должны иметь возможность прекратить слежение за сообщениями других пользователей, что приводит нас к немного предсказуемому методу unfollow!, как показано в Листинге 12.14.10

Листинг 12.14. Тест для прекращения слежения за сообщениями пользователя.

spec/models/user_spec.rb
describe User do
  .
  .
  .
  describe "relationships" do
    .
    .
    .
    it "should have an unfollow! method" do
      @followed.should respond_to(:unfollow!)
    end

    it "should unfollow a user" do
      @user.follow!(@followed)
      @user.unfollow!(@followed)
      @user.should_not be_following(@followed)
    end
  end
end

Код для unfollow! прост: нужно просто найти взаимоотношение по followed id и уничтожить его (Листинг 12.15).11

Листинг 12.15. Прекращение слежения за сообщениями пользователя посредством уничтожения взаимоотношения.

app/models/user.rb
class User < ActiveRecord::Base
  .
  .
  .
  def following?(followed)
    relationships.find_by_followed_id(followed)
  end

  def follow!(followed)
    relationships.create!(:followed_id => followed.id)
  end

  def unfollow!(followed)
    relationships.find_by_followed_id(followed).destroy
  end
  .
  .
  .
end
# coding: utf-8