dsu 0.1.0.alpha.4 → 1.0.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.
@@ -6,11 +6,10 @@ require_relative '../support/configuration'
6
6
 
7
7
  module Dsu
8
8
  module Services
9
+ # This class loads an entry group file.
9
10
  class ConfigurationLoaderService
10
11
  include Dsu::Support::Configuration
11
12
 
12
- attr_reader :default_options
13
-
14
13
  def initialize(default_options: nil)
15
14
  unless default_options.nil? ||
16
15
  default_options.is_a?(Hash) ||
@@ -18,7 +17,7 @@ module Dsu
18
17
  raise ArgumentError, 'default_options must be a Hash or ActiveSupport::HashWithIndifferentAccess'
19
18
  end
20
19
 
21
- @default_options ||= default_options || {}
20
+ @default_options = default_options || {}
22
21
  @default_options = @default_options.with_indifferent_access if @default_options.is_a?(Hash)
23
22
  end
24
23
 
@@ -28,6 +27,8 @@ module Dsu
28
27
 
29
28
  private
30
29
 
30
+ attr_reader :default_options
31
+
31
32
  def config_options
32
33
  return Support::Configuration::DEFAULT_DSU_OPTIONS unless config_file?
33
34
 
@@ -0,0 +1,99 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../models/entry'
4
+ require_relative '../support/colorable'
5
+ require_relative '../support/say'
6
+ require_relative '../support/time_formatable'
7
+ require_relative '../views/edited_entries/shared/errors'
8
+ require_relative '../views/shared/messages'
9
+ require_relative 'configuration_loader_service'
10
+ require_relative 'stdout_redirector_service'
11
+
12
+ module Dsu
13
+ module Services
14
+ class EntryGroupEditorService
15
+ include Support::Colorable
16
+ include Support::Say
17
+ include Support::TimeFormatable
18
+
19
+ def initialize(entry_group:, options: {})
20
+ raise ArgumentError, 'entry_group is nil' if entry_group.nil?
21
+ raise ArgumentError, 'entry_group is the wrong object type' unless entry_group.is_a?(Models::EntryGroup)
22
+ raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash) || options.nil?
23
+
24
+ @entry_group = entry_group
25
+ @options = options || {}
26
+ end
27
+
28
+ def call
29
+ edit_view = render_edit_view
30
+ edit edit_view
31
+ # NOTE: Return the original entry group object as any permanent changes
32
+ # will have been applied to it.
33
+ entry_group
34
+ end
35
+
36
+ private
37
+
38
+ attr_reader :entry_group, :options
39
+
40
+ # Renders the edit view to a string so we can write it to a temporary file
41
+ # and edit it. The edits will be used to update the entry group.
42
+ def render_edit_view
43
+ say "Editing entry group #{formatted_time(time: entry_group.time)}...", HIGHLIGHT
44
+ StdoutRedirectorService.call { Views::EntryGroup::Edit.new(entry_group: entry_group).render }
45
+ end
46
+
47
+ # Writes the temporary file contents to disk and opens it in the editor
48
+ # for editing. It then copies the changes to the entry group and writes
49
+ # the changes to the entry group file.
50
+ def edit(edit_view)
51
+ entry_group_with_edits = Models::EntryGroup.new(time: entry_group.time)
52
+
53
+ Services::TempFileWriterService.new(tmp_file_content: edit_view).call do |tmp_file_path|
54
+ if Kernel.system("${EDITOR:-#{configuration[:editor]}} #{tmp_file_path}")
55
+ Services::TempFileReaderService.new(tmp_file_path: tmp_file_path).call do |editor_line|
56
+ next unless process_description?(editor_line)
57
+
58
+ entry_group_with_edits.entries << Models::Entry.new(description: editor_line)
59
+ end
60
+
61
+ process_entry_group!(entry_group_with_edits)
62
+ else
63
+ say "Failed to open temporary file in editor '#{configuration[:editor]}'; " \
64
+ "the system error returned was: '#{$CHILD_STATUS}'.", ERROR
65
+ say 'Either set the EDITOR environment variable ' \
66
+ 'or set the dsu editor configuration option (`$ dsu config init`).', ERROR
67
+ say 'Run `$ dsu help config` for more information:', ERROR
68
+ end
69
+ end
70
+ end
71
+
72
+ def process_entry_group!(entry_group_with_edits)
73
+ if entry_group_with_edits.entries.empty?
74
+ entry_group.delete!
75
+ return
76
+ end
77
+
78
+ if entry_group_with_edits.invalid?
79
+ header = 'The following ERRORS were encountered; these changes were not saved:'
80
+ messages = entry_group_with_edits.errors.full_messages
81
+ Views::Shared::Messages.new(messages: messages, message_type: :error, options: { header: header }).render
82
+ end
83
+
84
+ # Make sure we're saving only valid, unique entries.
85
+ entry_group.entries = entry_group_with_edits.valid_unique_entries
86
+ entry_group.save!
87
+ end
88
+
89
+ def process_description?(description)
90
+ description = Models::Entry.clean_description(description)
91
+ !(description.blank? || description[0] == '#')
92
+ end
93
+
94
+ def configuration
95
+ @configuration ||= ConfigurationLoaderService.new.call
96
+ end
97
+ end
98
+ end
99
+ end
@@ -26,6 +26,7 @@ module Dsu
26
26
  entry_group.validate!
27
27
  create_entry_group_path_if!
28
28
  write_entry_group_to_file!
29
+ entry_group
29
30
  rescue ActiveModel::ValidationError
30
31
  puts "Error(s) encountered: #{entry_group.errors.full_messages}"
31
32
  raise
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsu
4
+ module Services
5
+ # This service captures $stdout, resirects it to a StringIO object,
6
+ # and returns the string value.
7
+ # https://stackoverflow.com/questions/4459330/how-do-i-temporarily-redirect-stderr-in-ruby/4459463#4459463
8
+ module StdoutRedirectorService
9
+ class << self
10
+ def call
11
+ raise ArgumentError, 'no block was provided' unless block_given?
12
+
13
+ # The output stream must be an IO-like object. In this case we capture it in
14
+ # an in-memory IO object so we can return the string value. Any IO object can
15
+ # be used here.
16
+ string_io = StringIO.new
17
+ original_stdout, $stdout = $stdout, string_io # rubocop:disable Style/ParallelAssignment
18
+ yield
19
+ string_io.string
20
+ ensure
21
+ # Restore the original $stdout.
22
+ $stdout = original_stdout
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -3,29 +3,29 @@
3
3
  module Dsu
4
4
  module Services
5
5
  class TempFileReaderService
6
- def initialize(temp_file_path:, options: {})
7
- raise ArgumentError, 'temp_file_path is nil' if temp_file_path.nil?
8
- raise ArgumentError, 'temp_file_path is the wrong object type' unless temp_file_path.is_a?(String)
9
- raise ArgumentError, 'temp_file_path is empty' if temp_file_path.empty?
10
- raise ArgumentError, 'temp_file_path does not exist' unless File.exist?(temp_file_path)
6
+ def initialize(tmp_file_path:, options: {})
7
+ raise ArgumentError, 'tmp_file_path is nil' if tmp_file_path.nil?
8
+ raise ArgumentError, 'tmp_file_path is the wrong object type' unless tmp_file_path.is_a?(String)
9
+ raise ArgumentError, 'tmp_file_path is empty' if tmp_file_path.empty?
10
+ raise ArgumentError, 'tmp_file_path does not exist' unless File.exist?(tmp_file_path)
11
11
  raise ArgumentError, 'options is nil' if options.nil?
12
12
  raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash)
13
13
 
14
- @temp_file_path = temp_file_path
14
+ @tmp_file_path = tmp_file_path
15
15
  @options = options || {}
16
16
  end
17
17
 
18
18
  def call
19
19
  raise ArgumentError, 'no block given' unless block_given?
20
20
 
21
- File.foreach(temp_file_path) do |line|
21
+ File.foreach(tmp_file_path) do |line|
22
22
  yield line.strip
23
23
  end
24
24
  end
25
25
 
26
26
  private
27
27
 
28
- attr_reader :temp_file_path, :options
28
+ attr_reader :tmp_file_path, :options
29
29
  end
30
30
  end
31
31
  end
@@ -5,13 +5,13 @@ require 'tempfile'
5
5
  module Dsu
6
6
  module Services
7
7
  class TempFileWriterService
8
- def initialize(temp_file_content:, options: {})
9
- raise ArgumentError, 'temp_file_content is nil' if temp_file_content.nil?
10
- raise ArgumentError, 'temp_file_content is the wrong object type' unless temp_file_content.is_a?(String)
8
+ def initialize(tmp_file_content:, options: {})
9
+ raise ArgumentError, 'tmp_file_content is nil' if tmp_file_content.nil?
10
+ raise ArgumentError, 'tmp_file_content is the wrong object type' unless tmp_file_content.is_a?(String)
11
11
  raise ArgumentError, 'options is nil' if options.nil?
12
12
  raise ArgumentError, 'options is the wrong object type' unless options.is_a?(Hash)
13
13
 
14
- @temp_file_content = temp_file_content
14
+ @tmp_file_content = tmp_file_content
15
15
  @options = options || {}
16
16
  end
17
17
 
@@ -19,7 +19,7 @@ module Dsu
19
19
  raise ArgumentError, 'no block given' unless block_given?
20
20
 
21
21
  Tempfile.new('dsu').tap do |file|
22
- file.write("#{temp_file_content}\n")
22
+ file.write("#{tmp_file_content}\n")
23
23
  file.close
24
24
  yield file.path
25
25
  end.unlink
@@ -27,7 +27,7 @@ module Dsu
27
27
 
28
28
  private
29
29
 
30
- attr_reader :temp_file_content, :options
30
+ attr_reader :tmp_file_content, :options
31
31
  end
32
32
  end
33
33
  end
@@ -1,19 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'English'
4
3
  require_relative '../base_cli'
5
4
  require_relative '../models/entry_group'
6
- require_relative '../services/temp_file_reader_service'
7
- require_relative '../services/temp_file_writer_service'
8
- require_relative '../support/time_formatable'
9
- require_relative '../views/entry_group/edit'
10
5
  require_relative '../views/entry_group/show'
11
6
 
12
7
  module Dsu
13
8
  module Subcommands
14
9
  class Edit < Dsu::BaseCLI
15
- include Support::TimeFormatable
16
-
17
10
  map %w[d] => :date
18
11
  map %w[n] => :today
19
12
  map %w[t] => :tomorrow
@@ -25,7 +18,8 @@ module Dsu
25
18
  Edits the DSU entries for today.
26
19
  LONG_DESC
27
20
  def today
28
- Views::EntryGroup::Show.new(entry_group: edit_entry_group(time: Time.now)).render
21
+ entry_group = Models::EntryGroup.edit(time: Time.now)
22
+ Views::EntryGroup::Show.new(entry_group: entry_group).render
29
23
  end
30
24
 
31
25
  desc 'tomorrow, t',
@@ -34,7 +28,8 @@ module Dsu
34
28
  Edits the DSU entries for tomorrow.
35
29
  LONG_DESC
36
30
  def tomorrow
37
- Views::EntryGroup::Show.new(entry_group: edit_entry_group(time: Time.now.tomorrow)).render
31
+ entry_group = Models::EntryGroup.edit(time: Time.now.tomorrow)
32
+ Views::EntryGroup::Show.new(entry_group: entry_group).render
38
33
  end
39
34
 
40
35
  desc 'yesterday, y',
@@ -43,7 +38,8 @@ module Dsu
43
38
  Edits the DSU entries for yesterday.
44
39
  LONG_DESC
45
40
  def yesterday
46
- Views::EntryGroup::Show.new(entry_group: edit_entry_group(time: Time.now.yesterday)).render
41
+ entry_group = Models::EntryGroup.edit(time: Time.now.yesterday)
42
+ Views::EntryGroup::Show.new(entry_group: entry_group).render
47
43
  end
48
44
 
49
45
  desc 'date, d DATE',
@@ -54,79 +50,12 @@ module Dsu
54
50
  \x5 #{date_option_description}
55
51
  LONG_DESC
56
52
  def date(date)
57
- Views::EntryGroup::Show.new(entry_group: edit_entry_group(time: Time.parse(date))).render
53
+ entry_group = Models::EntryGroup.edit(time: Time.parse(date))
54
+ Views::EntryGroup::Show.new(entry_group: entry_group).render
58
55
  rescue ArgumentError => e
59
56
  say "Error: #{e.message}", ERROR
60
57
  exit 1
61
58
  end
62
-
63
- private
64
-
65
- def edit_entry_group(time:)
66
- formatted_time = formatted_time(time: time)
67
- unless Models::EntryGroup.exists?(time: time)
68
- say "No DSU entries exist for #{formatted_time}"
69
- exit 1
70
- end
71
-
72
- say "Editing DSU entries for #{formatted_time}..."
73
- entry_group = Models::EntryGroup.load(time: time)
74
-
75
- # This renders the view to a string...
76
- output = capture_stdxxx do
77
- Views::EntryGroup::Edit.new(entry_group: entry_group).render
78
- end
79
- # ...which is then written to a temp file.
80
- Services::TempFileWriterService.new(temp_file_content: output).call do |temp_file_path|
81
- unless system("${EDITOR:-#{configuration[:editor]}} #{temp_file_path}")
82
- say "Failed to open temporary file in editor '#{configuration[:editor]}';" \
83
- "the system error returned was: '#{$CHILD_STATUS}'.", ERROR
84
- say 'Either set the EDITOR environment variable ' \
85
- 'or set the dsu editor configuration option (`$ dsu config init`).', ERROR
86
- say 'Run `$ dsu help config` for more information.', ERROR
87
- system('dsu help config')
88
- exit 1
89
- end
90
- entries = []
91
- Services::TempFileReaderService.new(temp_file_path: temp_file_path).call do |temp_file_line|
92
- # Skip comments and blank lines.
93
- next if ['#', nil].include? temp_file_line[0]
94
-
95
- match_data = temp_file_line.match(/(\S+)\s(.+)/)
96
- # TODO: Error handling if match_data is nil.
97
- entry_sha = match_data[1]
98
- entry_description = match_data[2]
99
-
100
- next if %w[- d delete].include?(entry_sha) # delete the entry
101
-
102
- entry_sha = nil if %w[+ a add].include?(entry_sha) # add the new entry
103
- entries << Models::Entry.new(uuid: entry_sha, description: entry_description)
104
- end
105
-
106
- entry_group.entries = entries
107
-
108
- return entry_group.delete if entries.empty?
109
-
110
- entry_group.save!
111
- end
112
- entry_group
113
- end
114
-
115
- # https://stackoverflow.com/questions/4459330/how-do-i-temporarily-redirect-stderr-in-ruby/4459463#4459463
116
- def capture_stdxxx
117
- # The output stream must be an IO-like object. In this case we capture it in
118
- # an in-memory IO object so we can return the string value. You can assign any
119
- # IO object here.
120
- string_io = StringIO.new
121
- prev_stdout, $stdout = $stdout, string_io # rubocop:disable Style/ParallelAssignment
122
- prev_stderr, $stderr = $stderr, string_io # rubocop:disable Style/ParallelAssignment
123
- yield
124
- string_io.string
125
- ensure
126
- # Restore the previous value of stderr and stdout (typically equal to STDERR).
127
- $stdout = prev_stdout
128
- $stderr = prev_stderr
129
- end
130
59
  end
131
60
  end
132
61
  end
@@ -6,6 +6,7 @@ module Dsu
6
6
  ABORTED = :red
7
7
  ERROR = :red
8
8
  HIGHLIGHT = :cyan
9
+ INFO = HIGHLIGHT
9
10
  SUCCESS = :green
10
11
  WARNING = :yellow
11
12
  end
@@ -0,0 +1,43 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsu
4
+ module Support
5
+ module Descriptable
6
+ class << self
7
+ def included(mod)
8
+ mod.extend(ClassMethods)
9
+ end
10
+ end
11
+
12
+ def short_description
13
+ return '' if description.blank?
14
+
15
+ self.class.short_description(string: description)
16
+ end
17
+
18
+ module ClassMethods
19
+ def short_description(string:, count: 25, elipsis: '...')
20
+ return elipsis unless string.is_a?(String)
21
+
22
+ elipsis_length = elipsis.length
23
+ count = elipsis_length if count.nil? || count < elipsis_length
24
+
25
+ return string if string.length <= count
26
+
27
+ tokens = string.split
28
+ string = ''
29
+
30
+ return "#{tokens.first[0..(count - elipsis_length)]}#{elipsis}" if tokens.count == 1
31
+
32
+ tokens.each do |token|
33
+ break if string.length + token.length + elipsis_length > count
34
+
35
+ string = "#{string} #{token}"
36
+ end
37
+
38
+ "#{string.strip}#{elipsis}"
39
+ end
40
+ end
41
+ end
42
+ end
43
+ end
@@ -3,6 +3,7 @@
3
3
  require 'pathname'
4
4
  require_relative '../services/entry_group_reader_service'
5
5
  require_relative '../models/entry'
6
+ require_relative '../models/entry_group'
6
7
 
7
8
  module Dsu
8
9
  module Support
@@ -12,22 +13,19 @@ module Dsu
12
13
  # returns a Hash having :time and :entries
13
14
  # where entries == an Array of Entry Hashes
14
15
  # representing the JSON Entry objects for :time.
15
- def entry_group_hash_for(time:)
16
+ def load_entry_group_file_for(time:)
16
17
  entry_group_json = Services::EntryGroupReaderService.new(time: time).call
17
- if entry_group_json.present?
18
- return JSON.parse(entry_group_json, symbolize_names: true).tap do |hash|
18
+ hash = if entry_group_json.present?
19
+ JSON.parse(entry_group_json, symbolize_names: true).tap do |hash|
19
20
  hash[:time] = Time.parse(hash[:time])
20
21
  end
22
+ else
23
+ { time: time, entries: [] }
21
24
  end
22
25
 
23
- {
24
- time: time,
25
- entries: []
26
- }
26
+ Models::EntryGroup.new(**hydrate_entry_group_hash(hash: hash, time: time))
27
27
  end
28
28
 
29
- private
30
-
31
29
  # Accepts an entry group hash and returns a
32
30
  # hydrated entry group hash:
33
31
  #
@@ -39,10 +37,10 @@ module Dsu
39
37
  # ...
40
38
  # ]
41
39
  # }
42
- def hydrate_entry_group_hash(entry_group_hash:, time:)
43
- time = entry_group_hash.fetch(:time, time)
40
+ def hydrate_entry_group_hash(hash:, time:)
41
+ time = hash.fetch(:time, time)
44
42
  time = Time.parse(time) unless time.is_a? Time
45
- entries = entry_group_hash.fetch(:entries, [])
43
+ entries = hash.fetch(:entries, [])
46
44
  entries = entries.map { |entry_hash| Models::Entry.new(**entry_hash) }
47
45
 
48
46
  { time: time, entries: entries }
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dsu
4
+ module Validators
5
+ class DescriptionValidator < ActiveModel::Validator
6
+ def validate(record)
7
+ description = record.description
8
+
9
+ if description.blank?
10
+ record.errors.add(:description, :blank)
11
+ return
12
+ end
13
+
14
+ unless description.is_a?(String)
15
+ record.errors.add(field, 'is the wrong object type. ' \
16
+ "\"String\" was expected, but \"#{description.class}\" was received.")
17
+ return
18
+ end
19
+
20
+ validate_description record
21
+ end
22
+
23
+ private
24
+
25
+ def validate_description(record)
26
+ description = record.description
27
+
28
+ return if description.length.between?(2, 256)
29
+
30
+ if description.length < 2
31
+ record.errors.add(:description, "is too short: \"#{record.short_description}\" (minimum is 2 characters).")
32
+ elsif description.length > 256
33
+ record.errors.add(:description, "is too long: \"#{record.short_description}\" (maximum is 256 characters).")
34
+ end
35
+ end
36
+ end
37
+ end
38
+ end
@@ -7,55 +7,67 @@ require_relative '../support/field_errors'
7
7
  module Dsu
8
8
  module Validators
9
9
  class EntriesValidator < ActiveModel::Validator
10
- include Dsu::Support::FieldErrors
10
+ include Support::FieldErrors
11
11
 
12
12
  def validate(record)
13
- raise 'options[:fields] is not defined.' unless options.key? :fields
14
- raise 'options[:fields] is not an Array.' unless options[:fields].is_a? Array
15
- raise 'options[:fields] elements are not Symbols.' unless options[:fields].all?(Symbol)
13
+ unless record.entries.is_a?(Array)
14
+ record.errors.add(:entries_entry, 'is the wrong object type. ' \
15
+ "\"Array\" was expected, but \"#{record.entries.class}\" was received.")
16
+ end
16
17
 
17
- options[:fields].each do |field|
18
- entries = record.send(field)
18
+ validate_entry_types record
19
+ validate_unique_entry record
20
+ validate_entries record
21
+ end
19
22
 
20
- unless entries.is_a?(Array)
21
- record.errors.add(field, 'is the wrong object type. ' \
22
- "\"Array\" was expected, but \"#{entries.class}\" was received.")
23
- next
24
- end
23
+ private
25
24
 
26
- validate_entry_types field, entries, record
27
- validate_unique_entry_uuids field, entries, record
25
+ def validate_entry_types(record)
26
+ record.entries.each do |entry|
27
+ next if entry.is_a? Dsu::Models::Entry
28
+
29
+ record.errors.add(:entries_entry, 'entry Array element is the wrong object type. ' \
30
+ "\"Entry\" was expected, but \"#{entry.class}\" was received.",
31
+ type: Support::FieldErrors::FIELD_TYPE_ERROR)
28
32
  end
29
33
  end
30
34
 
31
- private
35
+ def validate_unique_entry(record)
36
+ return unless record.entries.is_a? Array
32
37
 
33
- def validate_entry_types(field, entries, record)
34
- entries.each do |entry|
35
- next if entry.is_a? Dsu::Models::Entry
38
+ entry_objects = record.entries.select { |entry| entry.is_a?(Dsu::Models::Entry) }
36
39
 
37
- record.errors.add(field, 'entry Array element is the wrong object type. ' \
38
- "\"Entry\" was expected, but \"#{entry.class}\" was received.",
39
- type: Dsu::Support::FieldErrors::FIELD_TYPE_ERROR)
40
+ descriptions = entry_objects.map(&:description)
41
+ return if descriptions.uniq.length == descriptions.length
40
42
 
41
- next
43
+ non_unique_descriptions = descriptions.select { |description| descriptions.count(description) > 1 }.uniq
44
+ if non_unique_descriptions.any?
45
+ record.errors.add(:entries_entry, 'contains a duplicate entry: ' \
46
+ "#{format_non_unique_descriptions(non_unique_descriptions)}.",
47
+ type: Support::FieldErrors::FIELD_DUPLICATE_ERROR)
42
48
  end
43
49
  end
44
50
 
45
- def validate_unique_entry_uuids(field, entries, record)
46
- return unless entries.is_a? Array
47
-
48
- entry_objects = entries.select { |entry| entry.is_a?(Dsu::Models::Entry) }
51
+ def validate_entries(record)
52
+ entries = record.entries
53
+ return if entries.none?
49
54
 
50
- uuids = entry_objects.map(&:uuid)
51
- return if uuids.uniq.length == uuids.length
55
+ entries.each do |entry|
56
+ next if entry.valid?
52
57
 
53
- non_unique_uuids = uuids.select { |element| uuids.count(element) > 1 }.uniq
54
- if non_unique_uuids.any?
55
- record.errors.add(field, "contains duplicate UUIDs: #{non_unique_uuids.join(', ')}.",
56
- type: Dsu::Support::FieldErrors::FIELD_DUPLICATE_ERROR)
58
+ entry.errors.each do |error|
59
+ record.errors.add(:entries_entry, error.full_message)
60
+ end
57
61
  end
58
62
  end
63
+
64
+ def format_non_unique_descriptions(non_unique_descriptions)
65
+ non_unique_descriptions.map { |description| "\"#{short_description(description)}\"" }.join(', ')
66
+ end
67
+
68
+ def short_description(description)
69
+ Models::Entry.short_description(string: description)
70
+ end
59
71
  end
60
72
  end
61
73
  end
@@ -5,29 +5,20 @@ module Dsu
5
5
  module Validators
6
6
  class TimeValidator < ActiveModel::Validator
7
7
  def validate(record)
8
- raise 'options[:fields] is not defined.' unless options.key? :fields
9
- raise 'options[:fields] is not an Array.' unless options[:fields].is_a? Array
10
- raise 'options[:fields] elements are not Symbols.' unless options[:fields].all?(Symbol)
8
+ time = record.time
11
9
 
12
- options[:fields].each do |field|
13
- time = record.send(field)
14
-
15
- if time.nil?
16
- record.errors.add(field, :blank)
17
- next
18
- end
19
-
20
- unless time.is_a?(Time)
21
- record.errors.add(field, 'is the wrong object type. ' \
22
- "\"Time\" was expected, but \"#{time.class}\" was received.")
23
- next
24
- end
10
+ if time.nil?
11
+ record.errors.add(:time, :blank)
12
+ return
13
+ end
25
14
 
26
- if time.utc?
27
- record.errors.add(field, 'is not in localtime format.')
28
- next
29
- end
15
+ unless time.is_a?(Time)
16
+ record.errors.add(:time, 'is the wrong object type. ' \
17
+ "\"Time\" was expected, but \"#{time.class}\" was received.")
18
+ return
30
19
  end
20
+
21
+ record.errors.add(:time, 'is not in localtime format.') if time.utc?
31
22
  end
32
23
  end
33
24
  end
data/lib/dsu/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Dsu
4
- VERSION = '0.1.0.alpha.4'
4
+ VERSION = '1.0.0'
5
5
  end