Skip to content

How to do a multipart updatable date

bethesque edited this page Dec 8, 2014 · 7 revisions

Handles multipart dates, allowing for the initial date to be nil.

require 'minitest/autorun'
require 'date'
require 'reform' #  1.2.2

Person = Struct.new(:date_of_birth)

class DateForm < Reform::Form

  reform_2_0!

  property :day
  property :month
  property :year

  validate :validate_date

  def validate_date
    # Need a more elegant way to add error to base of date_of_birth.
    # Using :base will add the error to the Person object.
    # Can't hardcode the message to "Date of birth ..."
    # because this form can be used for
    # more than one date property.
    unless valid_date?
      errors.add(:'', "is not a valid date")
    end
  end

  def date
    Date.parse("#{year}-#{month}-#{day}")
  end

  def valid_date?
    date
    true
  rescue ArgumentError => e
    false
  end
end

class PersonForm < Reform::Form

  reform_2_0!

  NilDate = Struct.new(:year, :month, :day)

  property :date_of_birth,
    form: DateForm,
    populate_if_empty: NilDate,
    # virtual: true
    writeable: false

  def date_of_birth
    super || DateForm.new(NilDate.new)
  end

  def sync
    result = super
    model.date_of_birth = date_of_birth.date
    result
  end

end

describe PersonForm do

  let(:date) { nil }
  let(:person) { Person.new(date) }
  let(:form_data) do
    {
      "date_of_birth" => {
        "day" => "2",
        "month" => "1",
        "year" => "2000",
      }
    }
  end

  subject { PersonForm.new(person) }

  describe "date_of_birth" do

    describe "with a date" do

      let(:date) { Date.new(2014, 1, 2) }

      it "is a DateForm" do
        assert_kind_of(DateForm, subject.date_of_birth)
      end

      describe "day" do
        it 'returns the day' do
          assert_equal(2, subject.date_of_birth.day)
        end
      end
      describe "month" do
        it 'returns the month number' do
          assert_equal(1, subject.date_of_birth.month)
        end
      end
      describe "year" do
        it 'returns the year' do
          assert_equal(2014, subject.date_of_birth.year)
        end
      end
    end

    describe "with a nil date" do

      it "is a DateForm" do
        assert_kind_of(DateForm, subject.date_of_birth)
      end

      describe "day" do
        it 'returns nil' do
          assert_nil(subject.date_of_birth.day)
        end
      end
      describe "month" do
        it 'returns nil' do
          assert_nil(subject.date_of_birth.month)
        end
      end
      describe "year" do
        it 'returns nil' do
          assert_nil(subject.date_of_birth.year)
        end
      end
    end
  end

  describe "validate" do
    before do
      subject.validate(form_data)
    end

    describe "with an invalid date" do
      let(:form_data) do
        {
          "date_of_birth" => {
            "day" => "30",
            "month" => "2",
            "year" => "2000",
          }
        }
      end

      it 'returns an error' do
        assert_equal(["Date of birth  is not a valid date"], subject.errors.full_messages)
      end
    end

    describe "with a valid date" do

      it "does not return an error" do
        assert_empty(subject.errors)
      end

    end
  end

  describe "sync" do

    before do
      subject.validate(form_data)
      subject.sync
    end

    describe "with an existing date" do
      let(:date) { Date.new(2014, 3, 4) }

      it "sets the date_of_birth" do
        assert_equal(Date.new(2000, 1, 2), person.date_of_birth)
      end
    end

    describe "with no existing date" do
      it "sets the date_of_birth" do
        assert_equal(Date.new(2000, 1, 2), person.date_of_birth)
      end
    end
  end
end