-
Notifications
You must be signed in to change notification settings - Fork 0
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
start with OOP #5
Conversation
movie.rb
Outdated
attr_accessor :link, :original_title, :year, :country, :release_date, :genres, :runtime, :popularity, :film_director, | ||
:stars | ||
|
||
def initialize(params = {}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Лучше использовать keyword arguments.
Hash of params это очень старый трюк, в современном руби к нему лучше не прибегать.
movie.rb
Outdated
movies.each do |movie| | ||
puts "#{movie.original_title} (#{movie.release_date.strftime('%d - %B - %Y')} " \ | ||
"#{movie.genres}) - #{movie.runtime}" | ||
def has_genre?(param) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Лучше стараться аргументы называть конкретно по смыслу, а не «аргумент», «параметр» и т.д.
movie_collection.rb
Outdated
|
||
class MovieCollection < Movie | ||
require 'date' | ||
require 'csv' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
require
лучше оставить снаружи класса (в начале файла), а то оно выглядит не так как ведёт себя (как будто включит CSV только внутрь класса, но это не так)
movie_collection.rb
Outdated
attr_accessor :movies | ||
|
||
def initialize(file_name) | ||
input_arg = ARGV[1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Нет, если мы пишем библиотечный класс, лучше ему не работать с ARGV (пусть это делает скрипт его использующий), иначе его будет неудобно использовать.
Пусть просто принимает имя файла который открыть (и держит «имя файла по умолчанию» в константе)
movie_collection.rb
Outdated
CSV.read(file_name, col_sep: '|', | ||
headers: %i[link original_title year country release_date | ||
genres runtime popularity film_director stars], | ||
converters: %i[date integer float]).map { |row| @movies.push(Movie.new(row.to_hash)) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
нет, push
не нужен тут :) надо подумать как тут правильно использоать map
.
И кстати список заголовков (да и список конвертеров) можно вынести в костанты, тогда код будет легче читать.
movie_collection.rb
Outdated
|
||
def sort_by(param) | ||
if movies.first.send(param).is_a? Date | ||
films = movies.select { |val| val.send(param).is_a? Date } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
На самом деле это не очень хороший ход — а если как раз у первого фильма дата не распарсилась?
Я бы скорее усложнил немного парсинг даты в фильмах (там же её не то чтобы нету, просто у некоторых она только годом, её вполне можно превратить в «1 января того года» просто)
movie_collection.rb
Outdated
films = movies.select { |val| val.send(param).is_a? Date } | ||
films.sort_by { |val| val.send(param) } | ||
else | ||
movies.sort_by { |val| val.send(param) } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
sort_by(¶m)
можно просто.
кстати, что сделает этот код при сортировке по длительности фильма?
movie_collection.rb
Outdated
end | ||
|
||
def filter(param) | ||
if param.keys.first == :genres || param.keys.first == :country |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Не, должно со всеми полями работать :)
И кроме того:
- работать когда фильтров несколько:
filter(year: 2001, country: 'USA')
- работать с паттернами, например:
filter(year: 2000..2005, title: /terminator/i)
(тут пригодится метод===
)
movie_collection.rb
Outdated
end | ||
|
||
def stats(param) | ||
movies.group_by { |movie| movie.send(param) }.transform_values(&:count).sort_by(&:last) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
А что будет при попытке посчитать статистику по актёрам, или по жанрам? ;)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
↑ На этот вопрос по-моему всё ещё нету ответа :)
.rubocop.yml
Outdated
@@ -3,3 +3,12 @@ Metrics/LineLength: | |||
StyleGuide: '#80-character-limits' | |||
Enabled: true | |||
Max: 120 | |||
|
|||
Metrics/MethodLength: | |||
Max: 40 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Это очень много :)
.rubocop.yml
Outdated
Max: 40 | ||
|
||
Metrics/AbcSize: | ||
Max: 50 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Это очень-очень много. Для учебного кода 20 — это в общем норм, больше не стоит. Если метод становится слишком сложным, надо его упрощать и/или разбивать на другие методы.
movie.rb
Outdated
@runtime = args[:runtime] | ||
@popularity = args[:popularity] | ||
@film_director = args[:film_director] | ||
@stars = args[:stars] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Если мы знаем, какие у метода будут аргументы с ключами, это имеет смысл так и описать (keyword arguments):
def initialize(link:, original_title:, year:...)
Во-первых, код станет проще внутри метода, во-вторых по описанию метода сразу будет видно чего он ожидает, в-третьих, станет невозможно его вызвать неправильно (с не теми аргументами)
movie_collection.rb
Outdated
|
||
HEADERS_KEYS = %i[link original_title year country release_date genres runtime popularity film_director stars].freeze | ||
CONVERTERS_KEYS = %i[date integer float].freeze | ||
INPUT_ARG = ARGV[1] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Нет, это неверно :) Константа — это константа, не то что имеет право меняться при каждом запуске.
movie_collection.rb
Outdated
val.release_date = Date.parse(val.release_date + '-01') | ||
elsif val.release_date.is_a? Integer | ||
val.release_date = Date.new(val.release_date) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Эту логику (как правильно хранить дату) должен взять на себя фильм, коллекции для этого никакого дела нету.
movie_collection.rb
Outdated
def stats(param) | ||
special_keys = %i[genres stars] | ||
group = if special_keys.include? param | ||
movies.group_by { |movie| movie.send(param).split(',').shift } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Хранить поля, для которых это релевантно, в виде массивов — это тоже дело фильма. У коллекции тут должен быть очень простой и обобщённый код.
movie.rb
Outdated
elsif release_date.is_a? Integer | ||
Date.new(release_date) | ||
end | ||
@genres = genres.split(',').shift |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
а shift
зачем?..
end | ||
def actors | ||
stars.split(',') | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
а зачем этот метод вообще? (если зачем-то таки нужен — можно решить через alias
просто... или переименовать колонку исходную :))
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
↑ Этот комментарий пропущен.
movie.rb
Outdated
genres runtime popularity film_director stars], | ||
converters: %i[date integer float]) | ||
.map { |row| OpenStruct.new(row.to_hash) } | ||
def genre?(movies, param) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Это не соответствует заданию :) «Клиент» этого кода не должен думать что туда передать — он просто спрашивает movie.genre?('Drama')
Чтобы у фильма была возможность узнать какие жанры вообще бывают, имеет смысл чтобы коллекция, при конструировании фильма, передавала ему себя — тогда фильм может хранить в своей переменной ссылку на всю коллекцию, и при необходимости спрашивать у неё «какие вообще жанры существуют?»
movie_collection.rb
Outdated
else | ||
params[key].member? d.send(key) | ||
end | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- Вместо
params.keys.all do |key|
и потом использоватьparam[key]
, мы можем использоватьparams.all? do |key, value|
- Логика тут запуталась.
param[key].is_a?(Array)
— какой случай это поддерживает, какой код? Работает ли сейчасfilter(title: /terminator/i)
? И т.д. Правильная логика — а) использовать===
для сравнения (тогда и регулярные выражения, и диапазоны, и простые строки будут работать) и б) нам нужны всего две веткиif
— для полей в которых массив (типа stars —stars: /Schwarzeneg/
тоже должно работать), и для всех остальных полей. - А ещё лучше вынести проверку фильтра в фильм — метод выглядящий как
movie.matches?(field, pattern)
— тогда в коллекции будет совсем простой код, а в фильме он тоже будет довольно несложный и его легко будет протестировать.
movie_collection.rb
Outdated
end | ||
|
||
def stats(param) | ||
movies.group_by { |movie| movie.send(param) }.transform_values(&:count).sort_by(&:last) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
↑ На этот вопрос по-моему всё ещё нету ответа :)
.rubocop.yml
Outdated
Max: 20 | ||
|
||
Metrics/ParameterLists: | ||
Max: 11 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Лучше CountKeywordArgs: false
(тогда он не будет ругаться ТОЛЬКО на методы с keyword args, что логично — их можно и две дюжины сделать без потери понятности)
demo.rb
Outdated
movies = MovieCollection.new('movies.txt') | ||
movies.sort_by(:release_date) | ||
|
||
movies.filter(country: 'USA', year: 2001..2002, runtime: '92 min') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
К слову, лучше бы runtime тоже хранить числом, потому что более-менее реальное применение этого фильтра — что-то вроде «хочу посмотреть фильм не больше 1.5 часов, т.е. runtime: 60..90
(а «хочу посмотреть фильм длиной 92 минуты» — не очень реальное :))
movie_collection.rb
Outdated
end | ||
|
||
def filter(params) | ||
movies.select { |d| params.all? { |key, _| params[key] === d.send(key) } } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
_
— это как раз то значение, которое params[key]
, тут вы сами себе жизнь усложняете :)
А работает ли этот код с жанрами? И скажем с актёрами?
movie_collection.rb
Outdated
movies.map { |movie| movie.send(param) }.flatten.group_by(&:itself).transform_values(&:count).sort_by(&:last) | ||
else | ||
movies.group_by { |movie| movie.send(param) }.transform_values(&:count).sort_by(&:last) | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Вместо двух веток, во многих случаях можно просто обобщить код. Например, flat_map(¶m).group_by(&:itself)
сработает для всех вариантов содержимого поля.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
уже близко :)
end | ||
def actors | ||
stars.split(',') | ||
end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
↑ Этот комментарий пропущен.
movie_collection.rb
Outdated
headers: HEADERS_KEYS, | ||
converters: CONVERTERS_KEYS).map do |row| | ||
row[:all_movies] = self | ||
Movie.new(row.to_hash) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Можно, во-первых, вот так:
Movie.new(all_movies: self, **row.to_hash)
(**
это «распаковать keyword args из этого хеша»)
а во-вторых лучше всего сделать коллекцию единственным НЕ-keyword аргументом, чтобы сразу видна была разница:
# в Movie
def initialize(collection, link:, original_title: ....)
# вызов в коллекции:
Movie.new(self, row.to_h)
добавление методов change methods change methods change methods changes change method refactoring
No description provided.