Class: Reading::Item
- Extended by:
- Forwardable
- Defined in:
- lib/reading/item.rb,
lib/reading/item/view.rb,
lib/reading/item/time_length.rb
Overview
A wrapper for an item parsed from a CSV reading log, providing convenience methods beyond what the parser’s raw Hash output can provide.
Defined Under Namespace
Classes: TimeLength, View
Constant Summary collapse
- ATTRIBUTES =
%i[rating author title genres variants experiences notes]
Instance Attribute Summary collapse
-
#view ⇒ Object
readonly
Returns the value of attribute view.
Instance Method Summary collapse
-
#==(other) ⇒ Boolean
Equality to another Item.
-
#done? ⇒ Boolean
Whether this item is done.
-
#initialize(item_hash_or_data, view: Item::View) ⇒ Item
constructor
A new instance of Item.
-
#last_end_date ⇒ Date?
This item’s last end date.
-
#split(date) ⇒ Array(Item, Item)
Splits this Item into two Items: one < the given date, and the other >= it.
-
#status ⇒ Symbol
This item’s status.
-
#with_experiences(new_experiences, view: false) ⇒ Item
Returns a new Item containing shallow copy of @data, with its experiences replaced with new_experiences.
-
#with_variants(new_variants, new_experiences: nil, view: false) ⇒ Item
Returns a new Item containing shallow copy of @data, with its variants replaced with new_variants.
Constructor Details
#initialize(item_hash_or_data, view: Item::View) ⇒ Item
Returns a new instance of Item.
26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
# File 'lib/reading/item.rb', line 26 def initialize(item_hash_or_data, view: Item::View) if item_hash_or_data.is_a? Data @data = item_hash_or_data elsif item_hash_or_data.is_a? Hash item_hash = item_hash_or_data.dup add_missing_attributes_with_filler_values!(item_hash) add_statuses_and_last_end_dates!(item_hash) @data = item_hash.to_data end @view = view.new(self) if view end |
Instance Attribute Details
#view ⇒ Object (readonly)
Returns the value of attribute view.
16 17 18 |
# File 'lib/reading/item.rb', line 16 def view @view end |
Instance Method Details
#==(other) ⇒ Boolean
Equality to another Item.
315 316 317 318 319 320 321 |
# File 'lib/reading/item.rb', line 315 def ==(other) unless other.is_a?(Item) raise ArgumentError, "An Item can be compared only with another Item." end data == other.send(:data) end |
#done? ⇒ Boolean
Whether this item is done.
49 50 51 |
# File 'lib/reading/item.rb', line 49 def done? status == :done end |
#last_end_date ⇒ Date?
This item’s last end date.
55 56 57 |
# File 'lib/reading/item.rb', line 55 def last_end_date data.experiences.last&.last_end_date end |
#split(date) ⇒ Array(Item, Item)
Splits this Item into two Items: one < the given date, and the other >= it.
118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 |
# File 'lib/reading/item.rb', line 118 def split(date) before_index = nil middle_indices = experiences.map.with_index { |experience, i| if experience.spans.first.dates && experience.spans.first.dates.begin < date before_index = i if (experience.last_end_date || Date.today) >= date i else nil end end } .compact # There are no experiences with spans that overlap the date. if middle_indices.none? # The Item is planned. return [] if experiences.none? { _1.spans.first.dates } # date is after all spans. return [self, nil] if experiences.all? { _1.last_end_date && date > _1.last_end_date } # date is before all spans. return [nil, self] if experiences.all? { _1.spans.first.dates.begin >= date } # Date is in between experiences. if before_index item_before = with_experiences(experiences[..before_index]) item_after = with_experiences(experiences[(before_index + 1)..]) return [item_before, item_after] end end if middle_indices.first == 0 experiences_before = [] else experiences_before = experiences[..(middle_indices.first - 1)] end experiences_after = experiences[(middle_indices.first + middle_indices.count)..] experiences_middle = experiences.values_at(*middle_indices) # TODO remove this check? unless middle_indices == (middle_indices.min..middle_indices.max).to_a raise Reading::Error, "Non-consecutive experiences found during Item#split." end experiences_middle.each do |experience_middle| before_index = nil span_middle_index = experience_middle .spans .index.with_index { |span, i| if span.dates && span.dates.begin < date before_index = i (span.dates.end || Date.today) >= date end } if span_middle_index.nil? # date is between spans. spans_before = experience_middle.spans[..before_index] spans_after = experience_middle.spans[(before_index + 1)..] else span_middle = experience_middle.spans[span_middle_index] unless span_middle.dates.end end_today_instead_of_endless = { dates: span_middle.dates.begin..Date.today } span_middle = span_middle.to_h.merge(end_today_instead_of_endless).to_data end dates_before = span_middle.dates.begin..date.prev_day amount_before = (span_middle.amount || 0) * (dates_before.count / span_middle.dates.count.to_f) span_middle_before = span_middle.with( dates: dates_before, amount: amount_before, ) dates_after = date..span_middle.dates.end amount_after = (span_middle.amount || 0) * (dates_after.count / span_middle.dates.count.to_f) span_middle_after = span_middle.with( dates: dates_after, amount: amount_after, ) if span_middle_index.zero? spans_before = [span_middle_before] else spans_before = [ *experience_middle.spans[..(span_middle_index - 1)], span_middle_before, ] end spans_after = [ span_middle_after, *experience_middle.spans[(span_middle_index + 1)..], ] end experience_middle_before = experience_middle.with( spans: spans_before, last_end_date: spans_before.map { _1.dates&.end }.compact.last, ) experience_middle_after = experience_middle.with( spans: spans_after, ) experiences_before << experience_middle_before experiences_after.unshift(*experience_middle_after) end # RM (alternate implementation) # experiences_before = experiences # .select(&:last_end_date) # .select { _1.last_end_date < date } # experiences_after = experiences # .select { _1.spans.first.dates.nil? || _1.spans.first.dates.begin >= date } # experiences_middle = experiences.select { # _1.spans.first.dates.begin < date && _1.last_end_date >= date # } # experiences_middle.each do |experience_middle| # spans_before = experience_middle # .spans # .select { _1.dates&.end } # .select { _1.dates.end < date } # spans_after = experience_middle # .spans # .select(&:dates) # .select { _1.dates.begin >= date } # span_middle = experience_middle # .spans # .find { _1.dates && _1.dates.begin < date && _1.dates.end >= date } # middle_index = experience_middle.spans.index(span_middle) # planned_spans_before = experience_middle # .spans # .map.with_index { |span, i| # [i, span] if span.dates.nil? && i < middle_index # } # .compact # planned_spans_after = experience_middle # .spans # .map.with_index { |span, i| # [i, span] if span.dates.nil? && i > middle_index # } # .compact # if span_middle # dates_before = span_middle.dates.begin..date.prev_day # amount_before = span_middle.amount * (dates_before.count / span_middle.dates.count.to_f) # span_middle_before = span_middle.with( # dates: dates_before, # amount: amount_before, # ) # dates_after = date..span_middle.dates.end # amount_after = span_middle.amount * (dates_after.count / span_middle.dates.count.to_f) # span_middle_after = span_middle.with( # dates: dates_after, # amount: amount_after, # ) # spans_before = [*spans_before, span_middle_before] # spans_after = [span_middle_after, *spans_after] # planned_spans_before.each do |i, planned_span| # spans_before.insert(i, planned_span) # end # planned_spans_after.each do |i, planned_span| # spans_after.insert(i - middle_index, planned_span) # end # end # experience_middle_before = experience_middle.with( # spans: spans_before, # last_end_date: spans_before.last.dates.end, # ) # experience_middle_after = experience_middle.with( # spans: spans_after, # ) # experiences_before << experience_middle_before # experiences_after = [experience_middle_after, *experiences_after] # end item_before = with_experiences(experiences_before) item_after = with_experiences(experiences_after) [item_before, item_after] end |
#status ⇒ Symbol
This item’s status.
43 44 45 |
# File 'lib/reading/item.rb', line 43 def status data.experiences.last&.status || :planned end |
#with_experiences(new_experiences, view: false) ⇒ Item
Returns a new Item containing shallow copy of @data, with its experiences replaced with new_experiences.
64 65 66 67 68 69 70 71 72 73 74 |
# File 'lib/reading/item.rb', line 64 def with_experiences(new_experiences, view: false) new_variants = variants.filter.with_index { |variant, old_index| new_experiences.any? { _1.variant_index == old_index } } with_variants( new_variants, new_experiences:, view:, ) end |
#with_variants(new_variants, new_experiences: nil, view: false) ⇒ Item
Returns a new Item containing shallow copy of @data, with its variants replaced with new_variants.
82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 |
# File 'lib/reading/item.rb', line 82 def with_variants(new_variants, new_experiences: nil, view: false) updated_variant_indices = [] # Map old to new indices, omitting those of variants that are not in new_variants. variants.each.with_index { |variant, old_index| new_index = new_variants.index(variant) updated_variant_indices[old_index] = new_index if new_index } # Remove experiences associated with the removed variants. kept_experiences = (new_experiences || experiences).select { |experience| # Conditional in case Item was created with fragmentary experience hashes, # as in stats_test.rb variant_index = experience.variant_index if experience.members.include?(:variant_index) !!updated_variant_indices[variant_index || 0] } # Then update the kept experiences' variant indices. updated_kept_experiences = kept_experiences.map { |experience| updated_variant_index = updated_variant_indices[experience.variant_index] experience.with(variant_index: updated_variant_index) } self.class.new( data.with( variants: new_variants, experiences: updated_kept_experiences, ), view:, ) end |