Benito Serna Tips and tools for Ruby on Rails developers

Deciding who to invite to your wedding using ruby

November 21, 2015

Maybe some of you know that I am planning to get marry the next year. If not know you know =).

Well maybe is not your case, but for me and my fiancee it has not been so easy to decide who we will invite to our wedding. Mainly because we have a limited number of people to invite and because we also have friends and family that are that live in different states or outside the country that maybe will not attend.

Because I am to geek to make this work in excel, I decided to build a little program that will help with our decision. Well I am also to geek to make the program just once, so I build it two times with two different “styles”.

I will share the two programs here. So you can use the one that you like the most (or just read them).

Enter guest information

One of the things that I wanted was the ability to enter the names of the people in and easy way. I think the easiest way form me to enter the data is using my editor, VIM =). Thats why the way to enter the data is with a ruby data structure like in the next example.

GROUPS = {
  # each hash key is the a group name that will have an array of "families" o invitations
  "Familia Sandoval" => [
    ["pepe", "ilia", "daniel"], # each invitation or family
    ["abuelita"], # a family or invitation can have just one member
    ["alan", "norely"]
    #....
  ],
  "Friends" => [
    ["chuy", "sonia"],
    ["cheo", "silvia"],
    ["alejandro", "bety"],
    ["miguel", "mireya"],
  ]
  #....
}

You can enter the data with a hash called GROUPS, where each key is the name of the group, for example “Work Friends”, “Family”, “School Friends”, etc.

With this info you will get a report with the total number of people, the total number of invitations and the total number of tables (I am expecting that a table has 10 places).

But the cool part (or at least the cool part for me) is that you can also filter “families” and also get some numbers about them.

You have two filters, :remove, that I am using for the “families” that maybe I will not invite because I have not enough places, and :will_not_go for “families” that I find very hard that they can attend.

You can use these filters in the next way..

GROUPS = {
  "Familia Sandoval" => [
    [:remove, "pepe", "ilia", "daniel"],
    ["abuelita"],
    [:will_not_go, "alan", "norely"]
    #....
  ],
  #....
}

With these filters you will get data about the number of people, tables and invitations that you have :removed or that :will_not_go.

First program: Without classes or custom objects

GROUPS = {} # Define your groups here

module All
  def self.people_count
    GROUPS.values.map { |group| Group.people_count(group) }.reduce(:+)
  end

  def self.families_count
    GROUPS.values.map { |group| Group.families_count(group) }.reduce(:+)
  end

  def self.tables_count
    GROUPS.values.map { |group| Group.tables_count(group) }.reduce(:+).ceil
  end
end

module Group
  def self.original_people_count(group)
    group.map { |family| Family.members_count(family) }.reduce(:+)
  end

  def self.removed_people_count(group)
    removed_families(group).map { |family| Family.members_count(family) }.reduce(0, :+)
  end

  def self.will_not_go_people_count(group)
    will_not_go_families(group).map { |family| Family.members_count(family) }.reduce(0, :+)
  end

  def self.people_count(group)
    original_people_count(group) - removed_people_count(group) - will_not_go_people_count(group)
  end

  def self.original_families_count(group)
    group.count
  end

  def self.removed_families_count(group)
    removed_families(group).count
  end

  def self.will_not_go_families_count(group)
    will_not_go_families(group).count
  end

  def self.families_count(group)
    original_families_count(group) - removed_families_count(group) - will_not_go_families_count(group)
  end

  def self.removed_families(group)
    group.select { |family| Family.removed?(family) }
  end

  def self.will_not_go_families(group)
    group.select { |family| Family.will_not_go?(family) }
  end

  def self.removed_families?(group)
    removed_families(group).any? || will_not_go_families(group).any?
  end

  def self.original_tables_count(group)
    original_people_count(group) / 10.0
  end

  def self.removed_tables_count(group)
    removed_people_count(group) / 10.0
  end

  def self.will_not_go_tables_count(group)
    will_not_go_people_count(group) / 10.0
  end

  def self.tables_count(group)
    people_count(group) / 10.0
  end
end

module Family
  def self.removed?(family)
    family.first == :remove
  end

  def self.will_not_go?(family)
    family.first == :will_not_go
  end

  def self.members_count(family)
    family.reject { |member| [:remove, :will_not_go].include?(member) }.count
  end
end

puts ""
puts ""
puts ""
puts "---------------------------"
puts "---- Invitados Boduqui ----"
puts "---------------------------"
puts ""
puts ""

GROUPS.each do |name, group|
  puts "#{name}"

  if Group.removed_families?(group)
    puts "  - Total original de personas: #{Group.original_people_count(group)}"
    puts "  - Total de personas removidas: #{Group.removed_people_count(group)}"
    puts "  - Total de personas removidas porque no asistiran: #{Group.will_not_go_people_count(group)}"
    puts "  -------------------------------------------------"
    puts "  - Total de personas: #{Group.people_count(group)}"
    puts ""

    puts "  - Total original de mesas: #{Group.original_tables_count(group)}"
    puts "  - Total de mesas removidas: #{Group.removed_tables_count(group)}"
    puts "  - Total de mesas removidas porque no asistiran: #{Group.will_not_go_tables_count(group)}"
    puts "  -----------------------------------------------"
    puts "  - Total de mesas: #{Group.tables_count(group)}"
    puts ""

    puts "  - Total original de invitaciones: #{Group.original_families_count(group)}"
    puts "  - Total de invitaciones removidas: #{Group.removed_families_count(group)}"
    puts "  - Total de invitaciones removidas porque no asistiran: #{Group.will_not_go_families_count(group)}"
    puts "  ------------------------------------------------------"
    puts "  - Total de invitaciones: #{Group.families_count(group)}"
  else
    puts "  - Total de personas: #{Group.people_count(group)}"
    puts "  - Total de invitaciones: #{Group.families_count(group)}"
    puts "  - Total de mesas: #{Group.tables_count(group)}"
  end

  puts ""
end

puts "Total"
puts "-----"
puts "  - Numero total de personas: #{All.people_count}"
puts "  - Numero total de invitaciones: #{All.families_count}"
puts "  - Numero total de mesas: #{All.tables_count}"
puts ""
puts ""

Second program: Using objects including a custom “collection” object

GROUPS = {} # Define your groups here

module Groups
  def self.build(data)
    Groups.new(GROUPS.map { |name, families|
      Group.new(name, families.map { |family_data|
        Family.new(family_data)
      })
    })
  end

  class Groups
    include Enumerable

    def initialize(all)
      @all = all
    end

    def each(&block)
      all.each(&block)
    end

    def people_count
      all.map { |group| group.expected.people_count }.reduce(:+)
    end

    def tables_count
      all.map { |group| group.expected.tables_count }.reduce(:+)
    end

    def families_count
      all.map { |group| group.expected.families_count }.reduce(:+)
    end

    private

    attr_reader :all
  end

  class Group
    attr_reader :name, :original

    def initialize(name, original)
      @name = name
      @original = Families.new(original)
    end

    def has_removed?
      removed.any? || will_not_go.any?
    end

    def removed
      Families.new(original.select(&:removed?))
    end

    def will_not_go
      Families.new(original.select(&:will_not_go?))
    end

    def expected
      Families.new(original.reject(&:removed?).reject(&:will_not_go?))
    end
  end

  class Families
    include Enumerable

    def initialize(all)
      @all = all
    end

    def each(&block)
      all.each(&block)
    end

    def people_count
      all.map(&:members).reduce(0, :+)
    end

    def families_count
      all.count
    end

    def tables_count
      people_count / 10.0
    end

    private

    attr_reader :all
  end

  class Family
    def initialize(data)
      @data = data
    end

    def removed?
      data.first == :remove
    end

    def will_not_go?
      data.first == :will_not_go
    end

    def members
      data.reject { |member| [:remove, :will_not_go].include?(member) }.count
    end

    private

    attr_reader :data
  end
end

puts ""
puts ""
puts ""
puts "---------------------------"
puts "---- Invitados Boduqui ----"
puts "---------------------------"
puts ""
puts ""

groups = Groups.build(GROUPS)

groups.each do |group|
  puts "#{group.name}"

  if group.has_removed?
    puts "  - Total original de personas: #{group.original.people_count}"
    puts "  - Total de personas removidas: #{group.removed.people_count}"
    puts "  - Total de personas removidas porque no asistiran: #{group.will_not_go.people_count}"
    puts "  -------------------------------------------------"
    puts "  - Total de personas: #{group.expected.people_count}"
    puts ""

    puts "  - Total original de mesas: #{group.original.tables_count}"
    puts "  - Total de mesas removidas: #{group.removed.tables_count}"
    puts "  - Total de mesas removidas porque no asistiran: #{group.will_not_go.tables_count}"
    puts "  -----------------------------------------------"
    puts "  - Total de mesas: #{group.expected.tables_count}"
    puts ""

    puts "  - Total original de invitaciones: #{group.original.families_count}"
    puts "  - Total de invitaciones removidas: #{group.removed.families_count}"
    puts "  - Total de invitaciones removidas porque no asistiran: #{group.will_not_go.families_count}"
    puts "  ------------------------------------------------------"
    puts "  - Total de invitaciones: #{group.expected.families_count}"
  else
    puts "  - Total de personas: #{group.expected.people_count}"
    puts "  - Total de invitaciones: #{group.expected.families_count}"
    puts "  - Total de mesas: #{group.expected.tables_count}"
  end

  puts ""
end

puts "Total"
puts "-----"
puts "  - Numero total de personas: #{groups.people_count}"
puts "  - Numero total de invitaciones: #{groups.families_count}"
puts "  - Numero total de mesas: #{groups.tables_count}"
puts ""
puts ""

Some final notes

I am sorry because the output is in spanish… But if for some weird reason you actually use this program and you don’t speak spanish I hope you can understand it with the help of google translate or the names of the variables =).

Also if for some weird reason you are reading this =) and you have an opinion about which option is better. I will be very happy if you comment which option you prefer and why.

Related articles

Weekly tips and tools for Ruby on Rails developers

I send an email each week, trying to share knowledge and fixes to common problems and struggles for ruby on rails developers, like How to fetch the latest-N-of-each record or How to test that an specific mail was sent or a Capybara cheatsheet. You can see more examples on Most recent posts or All post by topic.