12.1.4 Читаемые пользователи
Теперь мы переходим к сердцу ассоциаций Relationship: following и followers. Мы начнем с following, как показано в Листинге 12.10.
user.following. spec/models/user_spec.rbdescribe 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.
following с has_many :through. app/models/user.rbclass 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 показывают как мы планируем использовать эти методы на практике.
spec/models/user_spec.rbdescribe 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
following? и follow!. app/models/user.rbclass 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
spec/models/user_spec.rbdescribe 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
app/models/user.rbclass 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