Здравствуйте, записался на курс. При этом ставил галочку на "обучаться с тьютором". На 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.
![Модель данных для читателей пользователя, использующая реверсированную модель Relationship.](/EDI/13_05_16_2/1463091622-23109/tutorial/1150/objects/11/files/11_9.png)
Рис. 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/
Этот раздел предъявил довольно высокие требования к вашим навыкам моделирования данных, и это нормально, если для его усвоения потребуется некоторое время. Фактически, одним из самых лучших способов понять ассоциации является их использование в веб интерфейсе, как мы увидим в следующем разделе.