Module: UniqueBy::Generator

Defined in:
lib/unique_by.rb

Instance Method Summary collapse

Instance Method Details

#unique_by(*group_block_names, total: nil, bits: nil, &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
75
76
77
78
# File 'lib/unique_by.rb', line 14

def unique_by(*group_block_names, total: nil, bits: nil, &group_block)
  bits, total = Array(bits), Array(total)

  raise ArgumentError, "must pass either total or bits to #unique_by" \
    unless total.any? or bits.any?
  raise ArgumentError, "both total (#{total.inspect}) and bits (#{bits.inspect}) passed to #unique_by" \
    if total.any? and bits.any?
  raise ArgumentError, "must pass a group generator block" \
    unless group_block_names.any? or block_given?
  raise ArgumentError, "amount of group names (#{group_block_names.length}) doesn't match total/bits (#{total.length + bits.length})" \
    if (not block_given? and group_block_names.length != total.length + bits.length) or \
       (block_given? and group_block_names.length > total.length + bits.length)

  bits = total.map { |t| Math.log2(t).ceil } if bits.empty?
  total = bits.map { |b| 2 ** b }

  pk = primary_key # converting to a local variable

  define_singleton_method :"#{pk}_group_value_from" do |group|
    Array(group).each_with_index.reduce(0) do |group_value, (g, i)|
      raise TypeError, "group must implement #to_i, #{g.inspect} given" \
        unless g.respond_to?(:to_i)
      (group_value << bits[i]) + (g.to_i % total[i])
    end
  end

  define_singleton_method :"unique_#{pk}_from" do |id, group|
    (id.to_i << bits.inject(&:+)) + send(:"#{pk}_group_value_from", group)
  end

  define_singleton_method :"#{pk}_from" do |id|
    id.to_i >> bits.inject(&:+)
  end

  define_singleton_method :"#{pk}_group_from" do |id|
    group = bits.reverse.zip(total.reverse).map do |b, t|
      g = id & (t - 1)
      id >>= b
      g
    end.reverse
    group.length == 1 ? group[0] : group
  end

  define_method :"#{pk}_group" do
    group = group_block_names.map { |group_block_name| send(group_block_name) }
    group.push(*Array(instance_eval(&group_block))) if group_block
    raise ArgumentError, "amount of groups (#{group.length}) doesn't match amount of bits/total (#{bits.length})" if group.length != bits.length
    group.length == 1 ? group[0] : group
  end

  define_method :"unique_#{pk}" do
    primary_key = send(pk)
    raise TypeError, "#{pk} must implement #to_i, #{primary_key.inspect} given" \
      unless primary_key.respond_to?(:to_i)
    self.class.send(:"unique_#{pk}_from", primary_key.to_i, send(:"#{pk}_group"))
  end

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

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