|
Здравствуйте, записался на курс. При этом ставил галочку на "обучаться с тьютором". На email пришло письмо, о том, что записался на самостоятельное изучение курса. Как выбрать тьютора? |
Слежение за сообщениями пользователей
Валидации
Прежде чем двигаться дальше, мы добавим пару валидаций модели Relationship для комплектности. Тесты (Листинге 11.7) и код приложения (Листинг 11.8) просты.
describe Relationship do
.
.
.
describe "when followed id is not present" do
before { relationship.followed_id = nil }
it { should_not be_valid }
end
describe "when follower id is not present" do
before { relationship.follower_id = nil }
it { should_not be_valid }
end
end
Листинг
11.7.
Тестирование валидаций модели Relationship. spec/models/relationship_spec.rb
class Relationship < ActiveRecord::Base belongs_to :follower, class_name: "User" belongs_to :followed, class_name: "User" validates :follower_id, presence: true validates :followed_id, presence: true endЛистинг 11.8. Добавление валидаций модели Relationship. app/models/relationship.rb
Читаемые пользователи
Теперь мы переходим к сердцу ассоциаций Relationship: followed_users и followers. Мы начнем с followed_users, как показано в Листинге 11.9.
require 'spec_helper'
describe User do
.
.
.
it { should respond_to(:relationships) }
it { should respond_to(:followed_users) }
.
.
.
end
Листинг
11.9.
Тест для атрибута user.followed_users. spec/models/user_spec.rb
Реализация впервые использует has_many through: пользователь имеет много читаемых (пользователей) через взаимоотношения, как показано на рис. 11.7. По умолчанию, в ассоциации has_many through Rails ищет внешний ключ, соответствующий ассоциации в единственном числе; другими словами, код
has_many :followeds, through: :relationships
будет составлять массив, используя followed_id в таблице relationships. Но, как отмечалось в Разделе 11.1.1, user.followeds это довольно неуклюже; гораздо более естественным будет использование "followed users" в качестве множественного числа для "followed", и написание user.followed_users для массива читаемых пользователей. Естественно, Rails позволяет переопределить умолчание, в данном случае с помощью :source параметра (Листинг 11.10), который явно говорит Rails, что источником массива followed_users является множество followed ids.
class User < ActiveRecord::Base has_many :microposts, dependent: :destroy has_many :relationships, foreign_key: "follower_id", dependent: :destroy has_many :followed_users, through: :relationships, source: :followed . . . endЛистинг 11.10. Добавление к модели User ассоциации followed_users.app/models/user.rb
Для того, чтобы создавать взаимоотношение с читаемым (пользователем), мы введем служебный метод follow! с тем чтобы мы могли написать user.follow!(other_user). (Этот метод follow!должен работать всегда, так что, как и с create! и с save!, мы обозначаем восклицательным знаком что при неудачном создании будет брошено исключение.) Мы также добавим связанный булев метод following? для того чтобы иметь возможность проверять - читает ли пользователь сообщения других пользователей.6Если у вас есть большой опыт моделирования конкретной предметной области, вы зачастую можете предугадать такие вспомогательные методы, и даже если нет, вы часто обнаруживаете себя за их написанием с целью почистить тесты. Однако в данном случае нормально если вы не угадали их. Разработка програмного обеспечения это обычно итеративный процесс — вы пишете код до тех пор пока он не начинает становиться уродливым, а затем вы рефакторите его — но, для краткости, изложение в учебнике немного сглажено. Тесты в Листинге 11.11 показывают как мы планируем использовать эти методы на практике.
require 'spec_helper'
describe User do
.
.
.
it { should respond_to(:followed_users) }
it { should respond_to(:following?) }
it { should respond_to(:follow!) }
.
.
.
describe "following" do
let(:other_user) { FactoryGirl.create(:user) }
before do
@user.save
@user.follow!(other_user)
end
it { should be_following(other_user) }
its(:followed_users) { should include(other_user) }
end
end
Листинг
11.11.
Тесты для некоторых служебных методов “following”. spec/models/user_spec.rb
В коде приложения, метод following? принимает пользователя, названного other_user и проверяет, существует ли он в базе данных; метод follow! вызывает create! через relationships ассоциацию для создания взаимоотношения с читаемым. Результаты представлены в Листинге 11.12.
class User < ActiveRecord::Base
.
.
.
def feed
.
.
.
end
def following?(other_user)
relationships.find_by(followed_id: other_user.id)
end
def follow!(other_user)
relationships.create!(followed_id: other_user.id)
end
.
.
.
end
Листинг
11.12.
Служебные методы following? и follow!. app/models/user.rb
Отметим, что в Листинге 11.12 мы опустили самого пользователя, написав просто
relationships.create!(...)
вместо эквивалентного кода
self.relationships.create!(...)
Явное включение или невключение self в данном случае дело вкуса.
Конечно, пользователи должны иметь возможность прекратить слежение за сообщениями других пользователей, что приводит нас к немного предсказуемому методу unfollow!, как показано в Листинге 11.13.
require 'spec_helper'
describe User do
.
.
.
it { should respond_to(:follow!) }
it { should respond_to(:unfollow!) }
.
.
.
describe "following" do
.
.
.
describe "and unfollowing" do
before { @user.unfollow!(other_user) }
it { should_not be_following(other_user) }
its(:followed_users) { should_not include(other_user) }
end
end
end
Листинг
11.13.
Тест для прекращения слежения за сообщениями пользователя. spec/models/user_spec.rb
Код для unfollow! прост: нужно просто найти взаимоотношение по followed id и уничтожить его (Листинг 11.14).
class User < ActiveRecord::Base
.
.
.
def following?(other_user)
relationships.find_by(followed_id: other_user.id)
end
def follow!(other_user)
relationships.create!(followed_id: other_user.id)
end
def unfollow!(other_user)
relationships.find_by(followed_id: other_user.id).destroy!
end
.
.
.
end
Листинг
11.14.
Прекращение слежения за сообщениями пользователя посредством уничтожения взаимоотношения. app/models/user.rb
Читатели пользователя
Последней частью пазла взаимоотношений является метод user.followers сопутствующий user.followed_users. Вы могли заметить в рис. 11.7 что все сведения, необходимые для извлечения массива читателей уже присутствуют в таблице relationships. Действительно, техника та же, что и для читаемых пользователей, но с реверсированием ролей follower_id и followed_id. Это говорит о том, что, если бы мы смогли как-то организовать таблицу reverse_relationships, поменяв местами эти два столбца ( рис. 11.9), то мы бы с легкостью реализовали user.followers.

Рис. 11.9. Модель данных для читателей пользователя, использующая реверсированную модель Relationship.
Начнем с тестов, веря, что магия Rails выручит нас (когда дело дойдет до реализации) (Листинг 11.15).
require 'spec_helper'
describe User do
.
.
.
it { should respond_to(:relationships) }
it { should respond_to(:followed_users) }
it { should respond_to(:reverse_relationships) }
it { should respond_to(:followers) }
.
.
.
describe "following" do
.
.
.
it { should be_following(other_user) }
its(:followed_users) { should include(other_user) }
describe "followed user" do
subject { other_user }
its(:followers) { should include(@user) }
end
.
.
.
end
end
Листинг
11.15.
Тестирование перевернутых взаимоотношений. spec/models/user_spec.rb
Обратите внимание на то, как мы изменили субъект с помощью метода subject, замена @user на other_user, позволяет нам протестировать взаимоотношение с читателями естесственным образом:
subject { other_user }
its(:followers) { should include(@user) }Как вы наверное подозреваете, мы не будем создавать полную таблицу в базе данных только для того чтобы просто произвести реверс взаимоотношений. Вместо этого мы воспользуемся базовой симметрией между читаемыми и читателями для симуляции таблицы reverse_relationships, передав followed_id в качестве внешнего ключа. Иными словами, там где ассоциация relationships использует внешний ключ follower_id,
has_many :relationships, foreign_key: "follower_id"
ассоциация reverse_relationships использует followed_id:
has_many :reverse_relationships, foreign_key: "followed_id"
Ассоциация followers затем строится через реверсированные взаимоотношения, как показано в Листинге 11.16.
class User < ActiveRecord::Base
.
.
.
has_many :reverse_relationships, foreign_key: "followed_id",
class_name: "Relationship",
dependent: :destroy
has_many :followers, through: :reverse_relationships, source: :follower
.
.
.
end
Листинг
11.16.
Реализация user.followers использующая реверсированные взаимоотношения. app/models/user.rb
(Как и с Листингом 11.4, тест для dependent :destroy остается в качестве упражнения (Раздел 11.5).) Обратите внимание, что мы должны включить имя класса для этой ассоциации, т.e.,
has_many :reverse_relationships, foreign_key: "followed_id",
class_name: "Relationship"потому что иначе Rails будет искать несуществующий класс ReverseRelationship.
Стоит также отметить, что мы могли бы в этом случае пропустить :source, используя просто
has_many :followers, through: :reverse_relationships
поскольку Rails будет автоматически искать внешний ключ follower_id в данном случае. Я сохранил ключ :source для того чтобы подчеркнуть параллельность со структурой ассоциации has_many :followed_users, но вы можете пропустить его.
С кодом в Листинге 11.16, ассоциации читаемые/читатели завершены, и все тесты должны пройти:
$ bundle exec rspec spec/
Этот раздел предъявил довольно высокие требования к вашим навыкам моделирования данных, и это нормально, если для его усвоения потребуется некоторое время. Фактически, одним из самых лучших способов понять ассоциации является их использование в веб интерфейсе, как мы увидим в следующем разделе.