split_dmy 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []