split_dmy 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 31735b92deac70f49b3d910445e504f16d818a62
4
+ data.tar.gz: 19083a589f95add2d3e1610c1d8ea6ba8e60f4d0
5
+ SHA512:
6
+ metadata.gz: 8b30f8b20a42374ddef4a4e3ca84bf7b8e93a373d24ef125df141463d0f0ef22a096f829975268dde58e5be1fbe2a781875fa26a89152a0ab9a877629bef575f
7
+ data.tar.gz: 98a9addb8fc1f2855c8595e5c20f586f60a5d577ac1aa3b46959e3a153696b1ca53f258bba06f5d24d8c60690198155d9efa8baa222e679cc07d95500f831b4a
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ mkmf.log
data/.rubocop.yml ADDED
@@ -0,0 +1,145 @@
1
+ AllCops:
2
+ # Exclude anything that isn't really part of our code.
3
+ # rails_helper is excluded because it's full of solecisms, but it's mostly
4
+ # generated code and copy-and-pasted snipets from READMEs.
5
+ Exclude:
6
+ - 'Guardfile'
7
+ - 'vendor/**/*'
8
+ - 'db/**/*'
9
+ - 'bin/**/*'
10
+ - '**/*.gemspec'
11
+ - '**/Rakefile'
12
+ - '**/Vagrantfile'
13
+ - 'spec/rails_helper.rb'
14
+ - 'lib/tasks/cucumber.rake'
15
+ - 'features/support/env.rb'
16
+ - 'script/cucumber'
17
+ - 'lib/log_stuff.rb'
18
+
19
+ RunRailsCops: true
20
+
21
+ # Allow long lines in specs, as it's almost impossible to fit RSpec's
22
+ # expectations into 80 characters.
23
+ Metrics/LineLength:
24
+ Max: 80
25
+ Exclude:
26
+ - 'spec/**/*'
27
+
28
+ # Allow expect {}.to blocks in specs
29
+ # but not in the code
30
+ Style/BlockDelimiters:
31
+ Enabled: true
32
+ Exclude:
33
+ - 'spec/**/*'
34
+
35
+ # Don't worry about long methods in specs.
36
+ Metrics/MethodLength:
37
+ Exclude:
38
+ - 'spec/**/*'
39
+ - 'app/models/ability.rb'
40
+ - 'config/initializers/markdown_handler.rb'
41
+
42
+ # No need to check for describe class param in support files.
43
+ RSpec/DescribeClass:
44
+ Exclude:
45
+ - spec/support/**/*
46
+ - spec/views/**/*
47
+
48
+ RSpec/FilePath:
49
+ Exclude:
50
+ - spec/routing/**/*
51
+
52
+ # private/protected/public
53
+ Style/AccessModifierIndentation:
54
+ EnforcedStyle: indent
55
+
56
+ # Just indent parameters by two spaces. It's less volatile if methods change,
57
+ # and there's less busy work lining things up.
58
+ Style/AlignParameters:
59
+ EnforcedStyle: with_fixed_indentation
60
+
61
+ Style/ClassAndModuleChildren:
62
+ EnforcedStyle: nested
63
+ Exclude:
64
+ - app/controllers/users/invitations_controller.rb
65
+
66
+ Style/CollectionMethods:
67
+ PreferredMethods:
68
+ collect: 'map'
69
+ collect!: 'map!'
70
+ each_with_object: 'inject'
71
+ inject: 'inject'
72
+ reduce: 'inject'
73
+ detect: 'find'
74
+ find_all: 'select'
75
+
76
+ # Chain methods with trailing dots.
77
+ Style/DotPosition:
78
+ EnforcedStyle: trailing
79
+
80
+ # No, we don't prefer each_with_object
81
+ Style/EachWithObject:
82
+ Enabled: false
83
+
84
+ # Prefer blank line after class/module start.
85
+ Style/EmptyLinesAroundClassBody:
86
+ Enabled: false
87
+
88
+ Style/EmptyLinesAroundModuleBody:
89
+ Enabled: false
90
+
91
+ Style/EmptyLinesAroundBlockBody:
92
+ Enabled: false
93
+
94
+ # We have a mixture at the moment, so don't enforce anything.
95
+ Style/FormatString:
96
+ Enabled: false
97
+
98
+ # It's not really clearer to replace every if with a return if.
99
+ Style/GuardClause:
100
+ Enabled: false
101
+
102
+ # Groups of three is not always the right thing for numeric literals
103
+ Style/NumericLiterals:
104
+ Enabled: false
105
+
106
+ # Percent-formatting and hash interpolation both have their place. Don't
107
+ # enforce any particular one.
108
+ Style/StringLiterals:
109
+ Enabled: false
110
+
111
+ # I'm happy with raise, thanks.
112
+ Style/SignalException:
113
+ Enabled: false
114
+
115
+ # Let us use foo? methods
116
+ Style/TrivialAccessors:
117
+ AllowPredicates: true
118
+
119
+ # Prefer sensible naming to comments everywhere.
120
+ Documentation:
121
+ Description: Document classes and non-namespace modules.
122
+ Enabled: false
123
+
124
+ # Would enforce do_y if x over if x / do y / end. As with GuardClause above,
125
+ # this enforces code organisation that doesn't necesarily make things clearer.
126
+ IfUnlessModifier:
127
+ Enabled: false
128
+
129
+ # Allow safe assignment in conditions.
130
+ Lint/AssignmentInCondition:
131
+ AllowSafeAssignment: false
132
+
133
+ # Just a preference to use %w[] over %w()
134
+ Style/PercentLiteralDelimiters:
135
+ PreferredDelimiters:
136
+ '%w': '[]'
137
+ '%W': '[]'
138
+ '%i': '[]'
139
+
140
+ # %w doesn't always make for clearer test data
141
+ Style/WordArray:
142
+ Exclude:
143
+ - 'spec/**/*'
144
+
145
+ require: rubocop-rspec
data/.travis.yml ADDED
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ sudo: false
3
+ rvm:
4
+ - 2.2.1
5
+ addons:
6
+ code_climate:
7
+ repo_token: 1e83d7ee8b8763f4fe70f376aba2c6857931a4373929796fa4093b2c8b3f5a93
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in split_dmy.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2015 Colin Bruce
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,67 @@
1
+ # SplitDmy
2
+ [![Build Status](https://travis-ci.org/CeeBeeUK/split_dmy.svg?branch=master)](https://travis-ci.org/CeeBeeUK/split_dmy)
3
+ [![Code Climate](https://codeclimate.com/github/CeeBeeUK/split_dmy/badges/gpa.svg)](https://codeclimate.com/github/CeeBeeUK/split_dmy)
4
+ [![Test Coverage](https://codeclimate.com/github/CeeBeeUK/split_dmy/badges/coverage.svg)](https://codeclimate.com/github/CeeBeeUK/split_dmy/coverage)
5
+
6
+ [![security](https://hakiri.io/github/CeeBeeUK/split_dmy/master.svg)](https://hakiri.io/github/CeeBeeUK/split_dmy/master)
7
+ [![Stories in Ready](https://badge.waffle.io/CeeBeeUK/split_dmy.svg?label=ready&title=Ready)](http://waffle.io/CeeBeeUK/split_dmy)
8
+
9
+ Allow splitting a date field into constituent day, month and year parts.
10
+
11
+ Splitting dates into constituent parts is recommended by the GOV.UK
12
+ [service manual](https://www.gov.uk/service-manual/user-centred-design/resources/patterns/dates.html#memorable-dates)
13
+ this gem is designed to allow a simple method of displaying the split date on a view, without having
14
+ to manually de and re-compose the date in the controller.
15
+
16
+ This gem was inspired by [TimeSplitter](https://github.com/shekibobo/time_splitter)
17
+ by [shekibobo](https://github.com/shekibobo), in turn based on
18
+ [SplitDatetime](https://github.com/michihuber/split_datetime)
19
+ by [Michi Huber](https://github.com/michihuber).
20
+
21
+ ## Installation
22
+
23
+ Add this line to your application's Gemfile:
24
+
25
+ ```ruby
26
+ gem 'split_dmy'
27
+ ```
28
+
29
+ And then execute:
30
+
31
+ $ bundle
32
+
33
+ Or install it yourself as:
34
+
35
+ $ gem install split_dmy
36
+
37
+ ## Usage
38
+
39
+ After bundling, assuming you have a person model with a date_of_birth attribute, add this to your model:
40
+ ```ruby
41
+ class Person < ActiveRecord::Base
42
+ extend SplitDmy::Accessors
43
+ split_dmy_accessor :date_of_birth
44
+ end
45
+ ```
46
+
47
+ In your view (if using slim):
48
+ ```ruby
49
+ = form_for(@person) do |f|
50
+ = f.text_field :date_of_birth_day
51
+ = f.text_field :date_of_birth_month
52
+ = f.text_field :date_of_birth_year
53
+ ```
54
+
55
+ In your controller, add the new variables to the strong parameter list
56
+ ```ruby
57
+ params.require(:person).permit(:name, :date_of_birth_day, :date_of_birth_month, :date_of_birth_year)
58
+ ```
59
+
60
+
61
+ ## Contributing
62
+
63
+ 1. Fork it ( https://github.com/ceebeeuk/split-date-dmy/fork )
64
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
65
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
66
+ 4. Push to the branch (`git push origin my-new-feature`)
67
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+ require 'rubocop/rake_task'
4
+
5
+ RuboCop::RakeTask.new
6
+ RSpec::Core::RakeTask.new
7
+
8
+ task :default => [:rubocop, :spec]
data/lib/split_dmy.rb ADDED
@@ -0,0 +1,3 @@
1
+ require 'split_dmy/date_validator'
2
+ require 'split_dmy/split_accessors'
3
+ require 'split_dmy/version'
@@ -0,0 +1,123 @@
1
+ module SplitDmy
2
+ class DateValidator
3
+ def initialize(object, attribute)
4
+ @split_day = object.instance_variable_get("@#{attribute}_day")
5
+ @split_month = object.instance_variable_get("@#{attribute}_month")
6
+ @split_year = object.instance_variable_get("@#{attribute}_year").to_i
7
+ @object = object
8
+ @attr = attribute
9
+ end
10
+
11
+ def partial_updated
12
+ bd = build_date
13
+ partials_valid? && bd ? bd : nil
14
+ end
15
+
16
+ def parse_month(val)
17
+ if valid_fixnum?(val, 12) || valid_numeric_string?(val, 12)
18
+ result = val.to_i
19
+ else
20
+ mon_name = valid_month_name?(val)
21
+ result = mon_name.present? ? mon_name : val
22
+ end
23
+ result.to_i
24
+ end
25
+
26
+ def get_partial_error(part)
27
+ error = "#{part} must be completed" if "@split_#{part}".to_s.empty?
28
+ error = "is not a valid #{part}" unless send("valid_#{part}?")
29
+ error
30
+ end
31
+
32
+ def build_date
33
+ date = Date.new(
34
+ @split_year.to_i,
35
+ parse_month(@split_month),
36
+ @split_day.to_i
37
+ )
38
+ date if partials_match_date(date)
39
+ rescue
40
+ false
41
+ end
42
+
43
+ def generate_errors
44
+ %w[day month year].each do |part|
45
+ error = get_partial_error(part)
46
+ @object.errors.add("#{@attr}_#{part}".to_sym, error) if error.present?
47
+ end
48
+
49
+ err = []
50
+ err << 'you need to provide a valid date' if all_partials_empty?
51
+ err << combine_partials_error if partials_valid_date_fails?
52
+ err
53
+ end
54
+
55
+ private
56
+
57
+ def partials_valid?
58
+ { d: valid_day?, m: valid_month?, y: valid_year? }.values.all?
59
+ end
60
+
61
+ def partials_valid_date_fails?
62
+ partials_valid? && !build_date
63
+ end
64
+
65
+ def combine_partials_error
66
+ joined_date = [@split_day, @split_month, @split_year].join('-')
67
+ "'#{joined_date}' is not a valid date"
68
+ end
69
+
70
+ def all_partials_empty?
71
+ [@split_day.empty?, @split_month.empty?, @split_year.empty?].values.all?
72
+ rescue
73
+ false
74
+ end
75
+
76
+ def partials_match_date(date)
77
+ date.day == @split_day.to_i &&
78
+ date.month == parse_month(@split_month).to_i &&
79
+ date.year == @split_year.to_i
80
+ end
81
+
82
+ def valid_day?
83
+ (valid_fixnum?(@split_day, 31) || valid_numeric_string?(@split_day, 31))
84
+ end
85
+
86
+ def valid_month?
87
+ (
88
+ valid_fixnum?(@split_month, 12) ||
89
+ valid_numeric_string?(@split_month, 12) ||
90
+ valid_month_name?(@split_month)
91
+ )
92
+ end
93
+
94
+ def valid_year?
95
+ (
96
+ valid_fixnum?(@split_year, 3333) ||
97
+ valid_numeric_string_year?(@split_year)
98
+ )
99
+ end
100
+
101
+ def valid_fixnum?(x, max)
102
+ x.is_a?(Fixnum) && x > 0 && x <= max
103
+ end
104
+
105
+ def valid_numeric_string?(x, max)
106
+ x =~ /^[0-9]{1,2}$/ && x.to_i <= max
107
+ end
108
+
109
+ def valid_numeric_string_year?(x)
110
+ x =~ /^[0-9]{4}$/ && x.to_i <= 3333 && x.to_i > 0000
111
+ end
112
+
113
+ def valid_month_name?(month)
114
+ short_months = I18n.t('date.abbr_month_names')
115
+ full_months = I18n.t('date.month_names')
116
+ if short_months.include?(month.to_s.capitalize)
117
+ short_months.index(month.capitalize)
118
+ elsif full_months.include?(month.to_s.capitalize)
119
+ full_months.index(month.capitalize)
120
+ end
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,67 @@
1
+ module SplitDmy
2
+ module SplitAccessors
3
+ def split_dmy_accessor(*attrs)
4
+ require 'split_dmy/date_validator'
5
+
6
+ attrs.each do |attr|
7
+ validate "validate_#{attr}_partials".to_sym
8
+
9
+ override_builtin(attr)
10
+ add_attr_accessors(attr)
11
+ extend_validation(attr)
12
+ end
13
+ add_methods
14
+ end
15
+
16
+ private
17
+
18
+ def extend_validation(attr)
19
+ define_method("validate_#{attr}_partials") do
20
+ dv = DateValidator.new(self, attr)
21
+ new_errs = dv.generate_errors
22
+ unless new_errs.empty?
23
+ errors.delete(attr.to_sym)
24
+ errors.add(attr.to_sym, "is not valid, #{make_sentence_of(new_errs)}")
25
+ end
26
+ end
27
+ end
28
+
29
+ def override_builtin(attr)
30
+ after_initialize do
31
+ full_date = send("#{attr}")
32
+ split_into_parts(attr, full_date) unless full_date.nil?
33
+ end
34
+
35
+ define_method("#{attr}=") do |val|
36
+ super(val)
37
+ split_into_parts(attr, Date.parse(val.to_s)) unless val.nil?
38
+ end
39
+ end
40
+
41
+ def add_methods
42
+ define_method('split_into_parts') do |attr, full_date|
43
+ instance_variable_set("@#{attr}_day", full_date.day)
44
+ instance_variable_set("@#{attr}_month", full_date.month)
45
+ instance_variable_set("@#{attr}_year", full_date.year)
46
+ end
47
+
48
+ define_method('make_sentence_of') do |errors|
49
+ errors.to_sentence(last_word_connector: ' and ')
50
+ end
51
+ end
52
+
53
+ def add_attr_accessors(attr)
54
+ %w[day month year].each do |part|
55
+ define_method("#{attr}_#{part}=") do |val|
56
+ instance_variable_set("@#{attr}_#{part}", val)
57
+ new = DateValidator.new(self, attr).partial_updated
58
+ send("#{attr}=", new)
59
+ end
60
+
61
+ define_method("#{attr}_#{part}") do
62
+ instance_variable_get("@#{attr}_#{part}")
63
+ end
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,3 @@
1
+ module SplitDmy
2
+ VERSION = "0.1.0"
3
+ end
@@ -0,0 +1,12 @@
1
+ if ENV['CODECLIMATE_REPO_TOKEN']
2
+ # allow Code Climate Test coverage reports to be sent
3
+ require 'codeclimate-test-reporter'
4
+ CodeClimate::TestReporter.start
5
+ end
6
+
7
+ require 'active_record'
8
+
9
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
10
+
11
+ load File.dirname(__FILE__) + '/support/schema.rb'
12
+ require File.dirname(__FILE__) + '/support/models.rb'
@@ -0,0 +1,343 @@
1
+ require 'spec_helper'
2
+ require 'split_dmy/split_accessors'
3
+ require_relative '../../spec/support/matchers/accessors_shared'
4
+
5
+ describe SplitDmy::SplitAccessors do
6
+ let(:model) { User.new }
7
+
8
+ describe 'test model' do
9
+ it 'has a date of birth field to extend' do
10
+ expect(model).to respond_to :date_of_birth
11
+ end
12
+ it 'responds to date_of_birth' do
13
+ expect(model).to have_attr_accessor :date_of_birth
14
+ end
15
+ end
16
+
17
+ describe 'after adding our gem, a model' do
18
+ before do
19
+ User.extend(described_class)
20
+ User.split_dmy_accessor(:date_of_birth)
21
+ end
22
+
23
+ ['day', 'month', 'year'].each do |part|
24
+ it "responds to attribute_#{part}" do
25
+ expect(model).to respond_to "date_of_birth_#{part}".to_sym
26
+ end
27
+
28
+ it "has split accessor for attribute_#{part}" do
29
+ expect(model).to have_attr_accessor "date_of_birth_#{part}".to_sym
30
+ end
31
+ end
32
+
33
+ describe 'split dmy methods' do
34
+ describe 'handles pre-set dates' do
35
+ describe 'passes them to the new accessors' do
36
+ before(:each) { model.date_of_birth = '1991-1-21' }
37
+
38
+ it 'sets the _day' do
39
+ expect(model.date_of_birth_day).to eq 21
40
+ end
41
+ end
42
+
43
+ context 'when object created with date' do
44
+ let(:model) { User.new(date_of_birth: Date.new(1992, 1, 21)) }
45
+
46
+ it 'sets the date' do
47
+ expect(model.date_of_birth).to eq Date.new(1992, 1, 21)
48
+ end
49
+
50
+ describe 'splits the partials' do
51
+ it 'sets the day' do
52
+ expect(model.date_of_birth_day).to eq 21
53
+ end
54
+
55
+ it 'sets the month' do
56
+ expect(model.date_of_birth_month).to eq 1
57
+ end
58
+
59
+ it 'sets the year' do
60
+ expect(model.date_of_birth_year).to eq 1992
61
+ end
62
+ end
63
+ end
64
+
65
+ describe 'allows clearing of preset data' do
66
+ before(:each) { model.date_of_birth = '1991-1-21' }
67
+
68
+ it 'by setting all values to nil' do
69
+ model.date_of_birth_day = nil
70
+ model.date_of_birth_month = nil
71
+ model.date_of_birth_year = nil
72
+ expect(model.date_of_birth).to be_nil
73
+ end
74
+
75
+ describe 'by setting' do
76
+ it 'day to nil' do
77
+ model.date_of_birth_day = nil
78
+ expect(model.date_of_birth).to be_nil
79
+ end
80
+ it 'month to nil' do
81
+ model.date_of_birth_month = nil
82
+ expect(model.date_of_birth).to be_nil
83
+ end
84
+ it 'year to nil' do
85
+ model.date_of_birth_year = nil
86
+ expect(model.date_of_birth).to be_nil
87
+ end
88
+ end
89
+ end
90
+ end
91
+
92
+ describe '_day' do
93
+ describe 'when sent numbers' do
94
+ describe 'between 1 and 31 is valid' do
95
+ it 'sets the value' do
96
+ (1..31).each do |i|
97
+ model.date_of_birth_day = i
98
+ expect(model.date_of_birth_day).to eq i
99
+ end
100
+ end
101
+ end
102
+
103
+ describe 'above accepted range' do
104
+ before do
105
+ model.date_of_birth_day = 32
106
+ model.valid?
107
+ end
108
+
109
+ it 'sets the value' do
110
+ expect(model.date_of_birth_day).to eq 32
111
+ end
112
+
113
+ it 'adds an error message' do
114
+ err_msg = ['is not a valid day']
115
+ expect(model.errors[:date_of_birth_day]).to eq err_msg
116
+ end
117
+ end
118
+
119
+ describe 'below accepted range' do
120
+ before { model.date_of_birth_day = 0 }
121
+
122
+ it 'sets the value' do
123
+ expect(model.date_of_birth_day).to eq 0
124
+ end
125
+
126
+ it 'makes the model invalid' do
127
+ expect(model).to be_invalid
128
+ end
129
+ end
130
+ end
131
+
132
+ describe 'when sent text' do
133
+ before { model.date_of_birth_day = 'first' }
134
+
135
+ it 'returns nil' do
136
+ expect(model.date_of_birth_day).to eq 'first'
137
+ end
138
+
139
+ it 'makes the model invalid' do
140
+ expect(model).to be_invalid
141
+ end
142
+ end
143
+
144
+ describe 'when sent nil' do
145
+ it 'returns nil' do
146
+ model.date_of_birth_day = nil
147
+ expect(model.date_of_birth_day).to be_nil
148
+ end
149
+ end
150
+ end
151
+
152
+ describe '_month' do
153
+ describe 'when sent numbers' do
154
+ describe 'between 1 and 31 is valid' do
155
+ it 'sets the value' do
156
+ (1..12).each do |i|
157
+ model.date_of_birth_month = i
158
+ expect(model.date_of_birth_month).to eq i
159
+ end
160
+ end
161
+ end
162
+
163
+ describe 'above accepted range' do
164
+ before { model.date_of_birth_month = 13 }
165
+
166
+ it 'sets the value' do
167
+ expect(model.date_of_birth_month).to eq 13
168
+ end
169
+
170
+ it 'makes the model invalid' do
171
+ expect(model).to be_invalid
172
+ end
173
+ end
174
+
175
+ describe 'below accepted range' do
176
+ before { model.date_of_birth_month = 0 }
177
+
178
+ it 'sets the value' do
179
+ expect(model.date_of_birth_month).to eq 0
180
+ end
181
+
182
+ it 'makes the model invalid' do
183
+ expect(model).to be_invalid
184
+ end
185
+ end
186
+ end
187
+
188
+ describe 'when sent text' do
189
+ describe 'that is a valid long month' do
190
+ I18n.t('date.month_names').each_with_index do |month, _index|
191
+ it "sending #{month} sets the month to #{month}" do
192
+ model.date_of_birth_month = month
193
+ expect(model.date_of_birth_month).to eq month unless month.nil?
194
+ end
195
+ end
196
+ end
197
+ describe 'that is a valid short month' do
198
+ I18n.t('date.abbr_month_names').each_with_index do |month, _index|
199
+ it "sending #{month} sets the month to #{month}" do
200
+ model.date_of_birth_month = month
201
+ expect(model.date_of_birth_month).to eq month unless month.nil?
202
+ end
203
+ end
204
+ end
205
+ describe 'that is not a valid month' do
206
+ before { model.date_of_birth_month = 'first' }
207
+
208
+ it 'returns the text' do
209
+ expect(model.date_of_birth_month).to eq 'first'
210
+ end
211
+
212
+ it 'makes the model invalid' do
213
+ expect(model).to be_invalid
214
+ end
215
+ end
216
+ end
217
+
218
+ describe 'when sent nil' do
219
+ it 'returns nil' do
220
+ model.date_of_birth_month = nil
221
+ expect(model.date_of_birth_month).to be_nil
222
+ end
223
+ end
224
+ end
225
+
226
+ describe '_year' do
227
+ describe 'when sent numbers' do
228
+ describe 'numerically' do
229
+ describe 'in the acceptable range' do
230
+ it 'sets the value' do
231
+ model.date_of_birth_year = 1961
232
+ expect(model.date_of_birth_year).to eq 1961
233
+ end
234
+ end
235
+ describe 'outside the acceptable range' do
236
+ before { model.date_of_birth_year = 3334 }
237
+ it 'returns nil' do
238
+ expect(model.date_of_birth_year).to be 3334
239
+ end
240
+
241
+ it 'makes the model invalid' do
242
+ expect(model).to be_invalid
243
+ end
244
+ end
245
+ end
246
+ describe 'as text' do
247
+ it 'accepts and converts them' do
248
+ model.date_of_birth_year = '1961'
249
+ expect(model.date_of_birth_year).to eq '1961'
250
+ end
251
+ end
252
+ end
253
+
254
+ describe 'when sent text' do
255
+ before { model.date_of_birth_year = 'Nineteen Sixty One' }
256
+
257
+ it 'sets the value' do
258
+ expect(model.date_of_birth_year).to eq 'Nineteen Sixty One'
259
+ end
260
+
261
+ it 'makes the model invalid' do
262
+ expect(model).to be_invalid
263
+ end
264
+ end
265
+
266
+ describe 'when sent nil' do
267
+ it 'returns nil' do
268
+ model.date_of_birth_year = nil
269
+ expect(model.date_of_birth_year).to be_nil
270
+ end
271
+ end
272
+ end
273
+
274
+ describe 'when passed implausible dates' do
275
+ before do
276
+ model.date_of_birth_day = 30
277
+ model.date_of_birth_month = 2
278
+ model.date_of_birth_year = 2015
279
+ model.valid?
280
+ end
281
+
282
+ it 'returns an error' do
283
+ err_msg = ["is not valid, '30-2-2015' is not a valid date"]
284
+ expect(model.errors[:date_of_birth]).to eq err_msg
285
+ end
286
+ end
287
+
288
+ describe 'set themselves but do not set date' do
289
+ describe '#posted_day' do
290
+ it 'can be set and returned' do
291
+ model.date_of_birth_day = 1
292
+ expect(model.date_of_birth_day).to eq 1
293
+ expect(model.date_of_birth).to be nil
294
+ end
295
+ end
296
+
297
+ describe '#posted_month' do
298
+ it 'can be set and returned' do
299
+ model.date_of_birth_month = 10
300
+ expect(model.date_of_birth_month).to eq 10
301
+ expect(model.date_of_birth).to be nil
302
+ end
303
+ end
304
+
305
+ describe '#posted_year' do
306
+ it 'can be set and returned' do
307
+ model.date_of_birth_year = 10
308
+ expect(model.date_of_birth_year).to eq 10
309
+ expect(model.date_of_birth).to be nil
310
+ end
311
+ end
312
+ end
313
+
314
+ describe 'set the date field by calling validate_date' do
315
+ context 'when all are completed' do
316
+ before(:each) { model.date_of_birth = nil }
317
+
318
+ it 'with year last' do
319
+ model.date_of_birth_day = 21
320
+ model.date_of_birth_month = 1
321
+ model.date_of_birth_year = 1961
322
+ expect(model.date_of_birth).to eql Date.new(1961, 1, 21)
323
+ end
324
+
325
+ it 'with month last' do
326
+ model.date_of_birth_year = 1961
327
+ model.date_of_birth_day = 21
328
+ model.date_of_birth_month = 1
329
+ expect(model.date_of_birth).to eql Date.new(1961, 1, 21)
330
+ end
331
+
332
+ it 'with day last' do
333
+ model.date_of_birth_month = 1
334
+ model.date_of_birth_year = 1961
335
+ model.date_of_birth_day = 21
336
+ expect(model.date_of_birth).to eql Date.new(1961, 1, 21)
337
+ end
338
+ end
339
+ end
340
+ end
341
+
342
+ end
343
+ end
@@ -0,0 +1,18 @@
1
+ RSpec::Matchers.define :have_attr_accessor do |field|
2
+ match do |object_instance|
3
+ object_instance.respond_to?(field) &&
4
+ object_instance.respond_to?("#{field}=")
5
+ end
6
+
7
+ failure_message do |object_instance|
8
+ "expected attr_accessor for #{field} on #{object_instance}"
9
+ end
10
+
11
+ failure_message_when_negated do |object_instance|
12
+ "expected attr_accessor for #{field} not to be defined on #{object_instance}"
13
+ end
14
+
15
+ description do
16
+ 'assert there is an attr_accessor of the given name on the supplied object'
17
+ end
18
+ end
@@ -0,0 +1,2 @@
1
+ class User < ActiveRecord::Base
2
+ end
@@ -0,0 +1,9 @@
1
+ ActiveRecord::Schema.define do
2
+ self.verbose = false
3
+
4
+ create_table :users, force: true do |t|
5
+ t.date :date_of_birth
6
+ t.timestamps null: false
7
+ end
8
+
9
+ end
data/split_dmy.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'split_dmy/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'split_dmy'
8
+ spec.version = SplitDmy::VERSION
9
+ spec.authors = ['Colin Bruce']
10
+ spec.email = ['colinbruce@gmail.com']
11
+ spec.summary = 'Add split accessors for date fields /
12
+ into day, month, year parts'
13
+ spec.description = <<-EOF
14
+ Use `split_dmy_accessor :date_of_birth`to provide `date_of_birth_day`,
15
+ `date_of_birth_month`, `date_of_birth_year` accessors on the model.'
16
+ EOF
17
+ spec.homepage = 'https://github.com/CeeBeeUK/split_dmy'
18
+ spec.license = 'MIT'
19
+
20
+ spec.files = `git ls-files -z`.split("\x0")
21
+ spec.executables = spec.files.grep(%r{^/bin/$}) { |f| File.basename(f) }
22
+ spec.test_files = spec.files.grep(%r{^/(test|spec|features)/$})
23
+ spec.require_paths = ['lib']
24
+
25
+ spec.add_development_dependency 'bundler', '~> 1.7'
26
+ spec.add_development_dependency 'rake', '~> 10.0'
27
+ spec.add_development_dependency 'rspec', '~> 3.2'
28
+ spec.add_development_dependency 'activesupport', '~> 4.0.0'
29
+ spec.add_development_dependency 'activemodel', '~> 4.0.0'
30
+ spec.add_development_dependency 'activerecord', '~> 4.0.0'
31
+ spec.add_development_dependency 'sqlite3'
32
+ spec.add_development_dependency 'codeclimate-test-reporter'
33
+ spec.add_development_dependency 'rubocop'
34
+ spec.add_development_dependency 'rubocop-rspec'
35
+ end
metadata ADDED
@@ -0,0 +1,203 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: split_dmy
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Colin Bruce
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: bundler
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.7'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.7'
27
+ - !ruby/object:Gem::Dependency
28
+ name: rake
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '10.0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '10.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rspec
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: activesupport
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: 4.0.0
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: 4.0.0
69
+ - !ruby/object:Gem::Dependency
70
+ name: activemodel
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: 4.0.0
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: 4.0.0
83
+ - !ruby/object:Gem::Dependency
84
+ name: activerecord
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: 4.0.0
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: 4.0.0
97
+ - !ruby/object:Gem::Dependency
98
+ name: sqlite3
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: codeclimate-test-reporter
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rubocop
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rubocop-rspec
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ description: |2
154
+ Use `split_dmy_accessor :date_of_birth`to provide `date_of_birth_day`,
155
+ `date_of_birth_month`, `date_of_birth_year` accessors on the model.'
156
+ email:
157
+ - colinbruce@gmail.com
158
+ executables: []
159
+ extensions: []
160
+ extra_rdoc_files: []
161
+ files:
162
+ - ".gitignore"
163
+ - ".rubocop.yml"
164
+ - ".travis.yml"
165
+ - Gemfile
166
+ - LICENSE.txt
167
+ - README.md
168
+ - Rakefile
169
+ - lib/split_dmy.rb
170
+ - lib/split_dmy/date_validator.rb
171
+ - lib/split_dmy/split_accessors.rb
172
+ - lib/split_dmy/version.rb
173
+ - spec/spec_helper.rb
174
+ - spec/split_dmy/split_accessors_spec.rb
175
+ - spec/support/matchers/accessors_shared.rb
176
+ - spec/support/models.rb
177
+ - spec/support/schema.rb
178
+ - split_dmy.gemspec
179
+ homepage: https://github.com/CeeBeeUK/split_dmy
180
+ licenses:
181
+ - MIT
182
+ metadata: {}
183
+ post_install_message:
184
+ rdoc_options: []
185
+ require_paths:
186
+ - lib
187
+ required_ruby_version: !ruby/object:Gem::Requirement
188
+ requirements:
189
+ - - ">="
190
+ - !ruby/object:Gem::Version
191
+ version: '0'
192
+ required_rubygems_version: !ruby/object:Gem::Requirement
193
+ requirements:
194
+ - - ">="
195
+ - !ruby/object:Gem::Version
196
+ version: '0'
197
+ requirements: []
198
+ rubyforge_project:
199
+ rubygems_version: 2.4.4
200
+ signing_key:
201
+ specification_version: 4
202
+ summary: Add split accessors for date fields / into day, month, year parts
203
+ test_files: []