Module: UniqueBy::Generator

Defined in:
lib/unique_by.rb

Instance Method Summary collapse

Instance Method Details

#unique_by(**group_totals, &group_block) ⇒ Object

For a primary_key ‘id’, generates:

::id_group_value_from(group) => group_value
::unique_id_from(id, group) => unique_id
::id_from(unique_id) => id
::id_group_from(unique_id) => group
#id_group => group
#unique_id => unique_id
::find_by_unique_id(unique_id) => find_by_id(id_from(unique_id))
::find_by_unique_id!(unique_id) => find_by_id!(id_from(unique_id))

Raises:

  • (ArgumentError)


14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
# File 'lib/unique_by.rb', line 14

def unique_by(**group_totals, &group_block)
  raise ArgumentError, "must pass a group definition (Hash of name => total)" if group_totals.empty?
  raise ArgumentError, "group definition must be a Hash of name => Fixnum, #{group_totals.inspect} given" unless group_totals.values.all? { |t| t.is_a?(Fixnum) }

  bits = Hash[group_totals.map { |k, t| [k, Math.log2(t).ceil] }]
  totals = Hash[bits.map { |k, b| [k, 2 ** b] }] # real total

  pk = primary_key # converting to a local variable

  define_singleton_method :"#{pk}_group_value_from" do |**group|
    raise ArgumentError, "unknown #{pk} group keys: #{group.keys - group_totals.keys}" if (group.keys - group_totals.keys).any?
    raise ArgumentError, "missing #{pk} group keys: #{group_totals.keys - group.keys}" if (group_totals.keys - group.keys).any?
    group_totals.keys.reduce(0) do |group_value, group_name|
      g = group[group_name]
      raise TypeError, "#{pk} group #{group_name} must not be nil" if g.nil?
      raise TypeError, "#{pk} group #{group_name} must implement #to_i, #{g.inspect} given" unless g.respond_to?(:to_i)
      (group_value << bits[group_name]) + (g.to_i % totals[group_name])
    end
  end

  define_singleton_method :"unique_#{pk}_from" do |id, **group|
    break nil if id.nil?
    raise TypeError, "#{pk} must implement #to_i, #{id.inspect} given" unless id.respond_to?(:to_i)
    (id.to_i << bits.values.inject(&:+)) + send(:"#{pk}_group_value_from", **group)
  end

  define_singleton_method :"#{pk}_from" do |unique_id|
    break nil if unique_id.nil?
    raise TypeError, "unique_#{pk} must implement #to_i, #{unique_id.inspect} given" unless unique_id.respond_to?(:to_i)
    unique_id.to_i >> bits.values.inject(&:+)
  end

  define_singleton_method :"#{pk}_group_from" do |unique_id|
    break nil if unique_id.nil?
    raise TypeError, "unique_#{pk} must implement #to_i, #{unique_id.inspect} given" unless unique_id.respond_to?(:to_i)
    Hash[group_totals.keys.reverse.map do |group_name|
      g = unique_id & (totals[group_name] - 1)
      unique_id >>= bits[group_name]
      [group_name, g]
    end.reverse]
  end

  define_method :"#{pk}_group" do
    group_from_block = group_block ? instance_eval(&group_block) : {}
    raise TypeError, "#{pk} group block must return a Hash with any of the following keys: #{group_totals.keys}, #{group_from_block.inspect} given" unless group_from_block.is_a?(Hash)
    raise ArgumentError, "unknown #{pk} group passed to block: #{group_from_block.keys - group_totals.keys}" if (group_from_block.keys - group_totals.keys).any?
    Hash[(group_totals.keys - group_from_block.keys).map { |group_name| [group_name, send(group_name)] }].merge(group_from_block)
  end

  define_method :"unique_#{pk}" do
    self.class.send(:"unique_#{pk}_from", send(pk), **send(:"#{pk}_group"))
  end

  define_singleton_method :"find_by_unique_#{pk}" do |unique_id|
    send(:"find_by_#{pk}", send(:"#{pk}_from", unique_id))
  end

  define_singleton_method :"find_by_unique_#{pk}!" do |unique_id|
    send(:"find_by_#{pk}!", send(:"#{pk}_from", unique_id))
  end
end