Class: WeekSauce

Inherits:
Object
  • Object
show all
Defined in:
lib/week_sauce.rb

Overview

The underlying ‘days` database column can be either a string or integer type.

Constant Summary collapse

MAX_VALUE =
2**7 - 1
DAY_NAMES =
%w(sunday monday tuesday wednesday thursday friday saturday).map(&:to_sym).freeze
DAY_BITS =
Hash[ DAY_NAMES.zip(Array.new(7) { |i| 2**i }) ].freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(value = nil) ⇒ WeekSauce

Init a new WeekSauce instance. If value is omitted, the new instance will default to a bitmask of zero, i.e. no days set.

If a value argument is given, to_i will be called on it, and the resulting integer will be clamped to 0..127



76
77
78
# File 'lib/week_sauce.rb', line 76

def initialize(value = nil)
  @value = [[0, value.to_i].max, MAX_VALUE].min
end

Class Method Details

.dump(instance) ⇒ Object

ActiveRecord attribute serialization support

Dump a WeekSauce instance to a stringified bitmask value



62
63
64
65
66
67
68
# File 'lib/week_sauce.rb', line 62

def dump(instance)
  if instance.is_a?(self)
    instance.to_i.to_s
  else
    "0"
  end
end

.load(string) ⇒ Object

ActiveRecord attribute serialization support

Create a WeekSauce instance from a stringified integer bitmask. The value will be clamped (see #new)



53
54
55
56
57
# File 'lib/week_sauce.rb', line 53

def load(string)
  self.new(string.to_i)
rescue NoMethodError => err
  self.new
end

Instance Method Details

#==(arg) ⇒ Object

Compare this instance against another instance or a Fixnum



81
82
83
84
85
86
87
88
# File 'lib/week_sauce.rb', line 81

def ==(arg)
  case arg
  when self.class, Fixnum
    to_i == arg.to_i
  else
    false
  end
end

#[](wday) ⇒ Object

Returns true if the given day is set, false if it isn’t, and nil if the argument was invalid.

The wday argument can be

  • a Fixnum from 0 (Sunday) to 6 (Saturday),

  • a day-name symbol, e.g. :tuesday, :friday,

  • a day-name string (case-insensitive), e.g. "Monday", "sunday"

  • a Time object, or

  • a Date object



138
139
140
# File 'lib/week_sauce.rb', line 138

def [](wday)
  get_bit coerce_to_bit(wday)
end

#[]=(wday, bool) ⇒ Object

Set or unset the given day. See #[] for possible wday values. Invalid wday values are ignored.



144
145
146
# File 'lib/week_sauce.rb', line 144

def []=(wday, bool)
  set_bit coerce_to_bit(wday), bool
end

#all!Object

Set all days to true. Returns self



187
188
189
190
# File 'lib/week_sauce.rb', line 187

def all!
  @value = MAX_VALUE
  self
end

#all?Boolean

Returns true if all days are set, false otherwise

Returns:

  • (Boolean)


97
98
99
# File 'lib/week_sauce.rb', line 97

def all?
  @value == MAX_VALUE
end

#any?Boolean

Returns true if any of the week’s 7 days are set, false otherwise. Opposite of #blank?

Returns:

  • (Boolean)


103
104
105
# File 'lib/week_sauce.rb', line 103

def any?
  !blank?
end

#blank!Object

Set all days to false. Returns self



181
182
183
184
# File 'lib/week_sauce.rb', line 181

def blank!
  @value = 0
  self
end

#blank?Boolean

Returns true if no days are set, false otherwise. Opposite of #any?

Returns:

  • (Boolean)


92
93
94
# File 'lib/week_sauce.rb', line 92

def blank?
  @value == 0
end

#countObject

Returns the number of “set” days



209
210
211
# File 'lib/week_sauce.rb', line 209

def count
  to_a.count
end

#dates_in(date_range) ⇒ Object

Return all dates in the given date_range that match the bitmask. If the week’s blank, an empty array will be returned.

Note that the range is converted to an array, which is then filtered, so if the range is “backwards” (high to low) an empty array will be returned



259
260
261
262
# File 'lib/week_sauce.rb', line 259

def dates_in(date_range)
  return [] if blank?
  date_range.to_a.select { |date| self[date.wday] }
end

#inspectObject

Returns a string with the bitmask value and a list of “set” days, or a simple message if all/no days are set



215
216
217
218
219
220
221
222
223
224
# File 'lib/week_sauce.rb', line 215

def inspect
  if blank?
    "0: No days set"
  elsif all?
    "#{MAX_VALUE}: All days set"
  else
    list = to_a.map { |day| day.to_s.sub(/./, &:upcase) }.join(", ")
    "#{@value}: #{list}"
  end
end

#many?Boolean

Returns true if two or days are set, false otherwise

Returns:

  • (Boolean)


113
114
115
# File 'lib/week_sauce.rb', line 113

def many?
  any? && !one?
end

#next_date(from_date = nil) ⇒ Object

Return the next date matching the bitmask, or nil if the week is blank.

If no from_date argument is given, it’ll default to Date.current if ActiveSupport is available, otherwise it’ll use Date.today.

If from_date argument can be a Date or a Time object (the latter will be converted using #to_date)

If from_date is given, #next_date will return the first matching date from - and including - from_date



238
239
240
241
242
243
244
245
246
247
248
249
250
# File 'lib/week_sauce.rb', line 238

def next_date(from_date = nil)
  return nil if blank?
  from_date ||= if Date.respond_to?(:current)
    Date.current
  else
    Date.today
  end
  from_date = from_date.to_date
  until self[from_date.wday]
    from_date = from_date.succ
  end
  from_date.dup
end

#one?Boolean

Returns true if exactly one day is set, false otherwise

Returns:

  • (Boolean)


108
109
110
# File 'lib/week_sauce.rb', line 108

def one?
  any? && @value & (@value - 1) == 0
end

#set(*days) ⇒ Object

Set the given days. Like #[], arguments can be symbols, Fixnums, or Date/Time objects



150
151
152
153
154
# File 'lib/week_sauce.rb', line 150

def set(*days)
  days.each do |day|
    self[day] = true
  end
end

#set!(*days) ⇒ Object

Exclusive version of #set. Clears the week, and sets only the days passed. Returns self



158
159
160
161
162
# File 'lib/week_sauce.rb', line 158

def set!(*days)
  blank!
  set(*days)
  self
end

#to_aObject

Returns an array of “set” day names as symbols



198
199
200
# File 'lib/week_sauce.rb', line 198

def to_a
  DAY_NAMES.select { |day| self[day] }
end

#to_hashObject

Returns a hash where the keys are the week’s 7 days as symbols, and the values are booleans



204
205
206
# File 'lib/week_sauce.rb', line 204

def to_hash
  Hash[ DAY_NAMES.map { |day| [day, self[day]] } ]
end

#to_iObject

Returns the raw bitmask integer



193
194
195
# File 'lib/week_sauce.rb', line 193

def to_i
  @value
end

#unset(*days) ⇒ Object

Unset the given days. Like #[], arguments can be symbols, Fixnums, or Date/Time objects



166
167
168
169
170
# File 'lib/week_sauce.rb', line 166

def unset(*days)
  days.each do |day|
    self[day] = false
  end
end

#unset!(*days) ⇒ Object

Exclusive version of #unset. Sets all days to true and then unsets days passed. Returns self



174
175
176
177
178
# File 'lib/week_sauce.rb', line 174

def unset!(*days)
  all!
  unset(*days)
  self
end