diff --git a/.travis.yml b/.travis.yml index 0db98ac..378d672 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,21 +1,13 @@ language: ruby rvm: - - 2.0.0 - - 2.1.0 - - 2.1.1 - - 2.1.2 - - 2.1.3 - - 2.1.4 - - 2.1.5 - - 2.1.6 - - 2.1.7 - - 2.1.8 - 2.2.0 - 2.2.1 - 2.2.2 - 2.2.3 - 2.2.4 + - 2.2.5 - 2.3.0 + - 2.3.1 - rbx-2 - jruby-9000 - jruby-9.0.1.0 diff --git a/README.md b/README.md index 7a45b08..daa17e8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ And then execute: Or install it yourself as: $ gem install shoulda-hanami - + ## Configure Create file `spec/support/shoulda_hanami.rb` with: @@ -38,17 +38,18 @@ end class Person include Hanami::Validations - attribute :email, type: String, presence: true, format: /@/ - attribute :name, type: String, size: 5..50 - attribute :password, type: String, size: 10 - attribute :birthday, type: Date - attribute :created_at, type: DateTime - attribute :state, type: String, inclusion: %w(PR SC SP) - attribute :year, type: Integer, inclusion: 1979..1990 + validations do + required(:email) { format?(/@/) } + required(:name) { size?(5..50) } + required(:password) { size?(10) } + required(:state) { included_in?(%w(PR SC SP)) } + required(:year) { included_in?(1979..1990) } + end end ``` ### Spec + ```ruby # allow_value it { is_expected.to allow_value("leo@nospam.org").for(:email) } @@ -61,13 +62,6 @@ it { is_expected.to validate_presence_of(:email) } it { is_expected.to validate_length_of(:name).is_at_least(5).is_at_most(50) } it { is_expected.to validate_length_of(:password).is_equal_to(10) } -# coerces -it { is_expected.to coerce_attribute(:email).to(String) } -it { is_expected.to coerce_attribute(:name).to(String) } -it { is_expected.to coerce_attribute(:password).to(String) } -it { is_expected.to coerce_attribute(:birthday).to(Date) } -it { is_expected.to coerce_attribute(:created_at).to(DateTime) } - # inclusion it { is_expected.to validate_inclusion_of(:state).in_array(%w(PR SC SP)) } it { is_expected.to validate_inclusion_of(:year).in_array(1979..1990) } diff --git a/lib/shoulda/hanami/matchers.rb b/lib/shoulda/hanami/matchers.rb index 7fe5c25..791ca8d 100644 --- a/lib/shoulda/hanami/matchers.rb +++ b/lib/shoulda/hanami/matchers.rb @@ -1,4 +1,3 @@ -require 'shoulda/hanami/matchers/coerce_attribute_matcher' require 'shoulda/hanami/matchers/allow_value_matcher' require 'shoulda/hanami/matchers/validate_inclusion_of_matcher' require 'shoulda/hanami/matchers/validate_length_of_matcher' @@ -8,15 +7,23 @@ module Shoulda module Hanami module Matchers class Matcher + ERRORS = { + format: 'is in invalid format', + inclusion: 'must be one of:', + size: 'length must be', + presence: 'must be filled' + } + def initialize(target, attribute, validation) - @target = target - @attribute = attribute + @target = target + @attribute = attribute @validation = validation end def matches? - @target.valid? - @target.errors.for(@attribute).select { |error| error.attribute == @attribute.to_s && error.validation == @validation }.size > 0 + result = @target.validate + result.failure? && + result.messages.fetch(@attribute).select { |msg| msg.include?(ERRORS[@validation]) }.size > 0 end end end diff --git a/lib/shoulda/hanami/matchers/allow_value_matcher.rb b/lib/shoulda/hanami/matchers/allow_value_matcher.rb index c6214c4..0eb0ea9 100644 --- a/lib/shoulda/hanami/matchers/allow_value_matcher.rb +++ b/lib/shoulda/hanami/matchers/allow_value_matcher.rb @@ -11,8 +11,7 @@ def initialize(value) end def matches?(target) - target.send("#{@attribute}=", @value) - !Matcher.new(target, @attribute, :format).matches? + !Matcher.new(target.class.new(@attribute => @value), @attribute, :format).matches? end def description diff --git a/lib/shoulda/hanami/matchers/coerce_attribute_matcher.rb b/lib/shoulda/hanami/matchers/coerce_attribute_matcher.rb deleted file mode 100644 index 2bc3dec..0000000 --- a/lib/shoulda/hanami/matchers/coerce_attribute_matcher.rb +++ /dev/null @@ -1,53 +0,0 @@ -module Shoulda - module Hanami - module Matchers - def coerce_attribute(attribute) - CoerceAttributeMatcher.new(attribute) - end - - class CoerceAttributeMatcher - TYPES = { - 'Array' => [1, [1]], - 'BigDecimal' => ['x', nil], - 'Boolean' => ['0303', false], - 'Date' => [false, nil], - 'DateTime' => ['x', nil], - 'Float' => ['x', nil], - 'Hash' => ['รง', nil], - 'Integer' => ['x', nil], - 'Pathname' => [true, nil], - 'Set' => [nil, Set.new], - 'String' => [1, '1'], - 'Symbol' => [Hash.new, nil], - 'Time' => ['uy', nil], - } - - def initialize(attribute) - @attribute = attribute - end - - def matches?(target) - target.send("#{@attribute}=", TYPES[@type.to_s].first) - target.send(@attribute) == TYPES[@type.to_s].last - end - - def description - "coerce '#{@attribute}' to '#{@type}'" - end - - def failure_message - "does coerce '#{@attribute}' to '#{@type}'" - end - - def failure_message_when_negated - "does not coerce '#{@attribute}' to '#{@type}'" - end - - def to(type) - @type = type - self - end - end - end - end -end diff --git a/lib/shoulda/hanami/matchers/validate_inclusion_of_matcher.rb b/lib/shoulda/hanami/matchers/validate_inclusion_of_matcher.rb index c2e5aca..57dd08a 100644 --- a/lib/shoulda/hanami/matchers/validate_inclusion_of_matcher.rb +++ b/lib/shoulda/hanami/matchers/validate_inclusion_of_matcher.rb @@ -18,8 +18,7 @@ def matches?(target) break unless @values.include? value end - target.send("#{@attribute}=", value) - Matcher.new(target, @attribute, :inclusion).matches? + Matcher.new(target.class.new(@attribute => value), @attribute, :inclusion).matches? end def description diff --git a/lib/shoulda/hanami/matchers/validate_length_of_matcher.rb b/lib/shoulda/hanami/matchers/validate_length_of_matcher.rb index a5c34c3..6a2ce22 100644 --- a/lib/shoulda/hanami/matchers/validate_length_of_matcher.rb +++ b/lib/shoulda/hanami/matchers/validate_length_of_matcher.rb @@ -13,11 +13,9 @@ def initialize(attribute) def matches?(target) errors = [] - target.send("#{@attribute}=", '*' * (minimum - 1)) - errors << Matcher.new(target, @attribute, :size).matches? + errors << Matcher.new(target.class.new(@attribute => '*' * (minimum - 1)), @attribute, :size).matches? - target.send("#{@attribute}=", '*' * (maximum + 1)) - errors << Matcher.new(target, @attribute, :size).matches? + errors << Matcher.new(target.class.new(@attribute => '*' * (maximum + 1)), @attribute, :size).matches? errors.all? { |error| error } end diff --git a/lib/shoulda/hanami/matchers/validate_presence_of_matcher.rb b/lib/shoulda/hanami/matchers/validate_presence_of_matcher.rb index 8a95095..cf669de 100644 --- a/lib/shoulda/hanami/matchers/validate_presence_of_matcher.rb +++ b/lib/shoulda/hanami/matchers/validate_presence_of_matcher.rb @@ -11,7 +11,7 @@ def initialize(attribute) end def matches?(target) - Matcher.new(target, @attribute, :presence).matches? + Matcher.new(target.class.new(@attribute => nil), @attribute, :presence).matches? end def description diff --git a/lib/shoulda/hanami/version.rb b/lib/shoulda/hanami/version.rb index 9df4a06..2e197af 100644 --- a/lib/shoulda/hanami/version.rb +++ b/lib/shoulda/hanami/version.rb @@ -1,5 +1,5 @@ module Shoulda module Hanami - VERSION = '0.1.0'.freeze + VERSION = '0.2.0'.freeze end end diff --git a/shoulda-hanami.gemspec b/shoulda-hanami.gemspec index 296ce83..46125ce 100644 --- a/shoulda-hanami.gemspec +++ b/shoulda-hanami.gemspec @@ -22,5 +22,5 @@ Gem::Specification.new do |spec| spec.add_development_dependency 'rake', '~> 10.0' spec.add_development_dependency 'rspec', '~> 3.4.0' - spec.add_dependency 'hanami-validations', '~> 0.5.0' + spec.add_dependency 'hanami-validations', '~> 0.6.0' end diff --git a/spec/fixtures/with_validation_format_model.rb b/spec/fixtures/with_validation_format_model.rb index a4ddacf..aa32d11 100644 --- a/spec/fixtures/with_validation_format_model.rb +++ b/spec/fixtures/with_validation_format_model.rb @@ -1,5 +1,7 @@ class WithValidationFormatModel include Hanami::Validations - attribute :email, format: /@/ + validations do + optional(:email) { format?(/@/) } + end end diff --git a/spec/fixtures/with_validation_inclusion_model.rb b/spec/fixtures/with_validation_inclusion_model.rb index f27f7d4..15fc043 100644 --- a/spec/fixtures/with_validation_inclusion_model.rb +++ b/spec/fixtures/with_validation_inclusion_model.rb @@ -1,5 +1,7 @@ class WithValidationInclusionModel include Hanami::Validations - attribute :state, inclusion: %w(PR SC SP) + validations do + optional(:state).filled(inclusion?: %w(PR SC SP)) + end end diff --git a/spec/fixtures/with_validation_length_model.rb b/spec/fixtures/with_validation_length_model.rb index 72b19f9..ea75c10 100644 --- a/spec/fixtures/with_validation_length_model.rb +++ b/spec/fixtures/with_validation_length_model.rb @@ -1,6 +1,8 @@ class WithValidationLengthModel include Hanami::Validations - attribute :name_equal_10, size: 10 - attribute :name_between_2_10, size: 2..10 + validations do + optional(:name_equal_10).filled(size?: 10) + optional(:name_between_2_10).filled(size?: 2..10) + end end diff --git a/spec/fixtures/with_validation_presence_model.rb b/spec/fixtures/with_validation_presence_model.rb index 188e963..a19d5fd 100644 --- a/spec/fixtures/with_validation_presence_model.rb +++ b/spec/fixtures/with_validation_presence_model.rb @@ -1,5 +1,7 @@ class WithValidationPresenceModel include Hanami::Validations - attribute :email, presence: true + validations do + required(:email).filled + end end diff --git a/spec/fixtures/with_validation_type_model.rb b/spec/fixtures/with_validation_type_model.rb index c006029..63da4f4 100644 --- a/spec/fixtures/with_validation_type_model.rb +++ b/spec/fixtures/with_validation_type_model.rb @@ -1,17 +1,19 @@ class WithValidationTypeModel include Hanami::Validations - attribute :attr_array, type: Array - attribute :attr_bigdecimal, type: BigDecimal - attribute :attr_boolean, type: Boolean - attribute :attr_date, type: Date - attribute :attr_datetime, type: DateTime - attribute :attr_float, type: Float - attribute :attr_hash, type: Hash - attribute :attr_integer, type: Integer - attribute :attr_pathname, type: Pathname - attribute :attr_set, type: Set - attribute :attr_string, type: String - attribute :attr_symbol, type: Symbol - attribute :attr_time, type: Time + validations do + optional(:attr_array) { array? } + optional(:attr_bigdecimal) { decimal? } + optional(:attr_boolean) { bool? } + optional(:attr_date) { date? } + optional(:attr_datetime).filled(type?: DateTime) + optional(:attr_float) { float? } + optional(:attr_hash) { hash? } + optional(:attr_integer) { int? } + optional(:attr_pathname).filled(type?: Pathname) + optional(:attr_set).filled(type?: Set) + optional(:attr_string) { str? } + optional(:attr_symbol).filled(type?: Symbol) + optional(:attr_time) { time? } + end end diff --git a/spec/fixtures/without_validation_model.rb b/spec/fixtures/without_validation_model.rb index 319c891..bf87b19 100644 --- a/spec/fixtures/without_validation_model.rb +++ b/spec/fixtures/without_validation_model.rb @@ -1,5 +1,7 @@ class WithoutValidationModel include Hanami::Validations - attribute :email + validations do + optional(:email) + end end diff --git a/spec/shoulda/lotus_spec.rb b/spec/shoulda/hanami_spec.rb similarity index 100% rename from spec/shoulda/lotus_spec.rb rename to spec/shoulda/hanami_spec.rb diff --git a/spec/shoulda/matchers/coerce_attribute_matcher_spec.rb b/spec/shoulda/matchers/coerce_attribute_matcher_spec.rb deleted file mode 100644 index c2cb99f..0000000 --- a/spec/shoulda/matchers/coerce_attribute_matcher_spec.rb +++ /dev/null @@ -1,131 +0,0 @@ -require 'spec_helper' - -RSpec.describe Shoulda::Hanami::Matchers::CoerceAttributeMatcher do - include Shoulda::Hanami::Matchers - - it '#description' do - matcher = coerce_attribute(:attr_string).to(String) - - expect(matcher.description).to eq "coerce 'attr_string' to 'String'" - end - - context 'an attribute with a validation' do - let(:model) { WithValidationTypeModel.new } - - it 'coerces to custom' - - it 'coerces to array' do - expect(model).to coerce_attribute(:attr_array).to(Array) - end - - it 'coerces to bigdecimal' do - expect(model).to coerce_attribute(:attr_bigdecimal).to(BigDecimal) - end - - it 'coerces to boolean' do - expect(model).to coerce_attribute(:attr_boolean).to(Boolean) - end - - it 'coerces to date' do - expect(model).to coerce_attribute(:attr_date).to(Date) - end - - it 'coerces to datetime' do - expect(model).to coerce_attribute(:attr_datetime).to(DateTime) - end - - it 'coerces to float' do - expect(model).to coerce_attribute(:attr_float).to(Float) - end - - it 'coerces to hash' do - expect(model).to coerce_attribute(:attr_hash).to(Hash) - end - - it 'coerces to integer' do - expect(model).to coerce_attribute(:attr_integer).to(Integer) - end - - it 'coerces to pathname' do - expect(model).to coerce_attribute(:attr_pathname).to(Pathname) - end - - it 'coerces to set' do - expect(model).to coerce_attribute(:attr_set).to(Set) - end - - it 'coerces to string' do - expect(model).to coerce_attribute(:attr_string).to(String) - end - - it 'coerces to symbol' do - expect(model).to coerce_attribute(:attr_symbol).to(Symbol) - end - - it 'coerces to time' do - expect(model).to coerce_attribute(:attr_time).to(Time) - end - - it 'provides correct failure message' - end - - context 'an attribute without a validation' do - let(:model) { WithoutValidationModel.new } - - it 'does not coerce to custom' - - it 'does not coerce to array' do - expect(model).to_not coerce_attribute(:email).to(Array) - end - - it 'does not coerce to bigdecimal' do - expect(model).to_not coerce_attribute(:email).to(BigDecimal) - end - - it 'does not coerce to boolean' do - expect(model).to_not coerce_attribute(:email).to(Boolean) - end - - it 'does not coerce to date' do - expect(model).to_not coerce_attribute(:email).to(Date) - end - - it 'does not coerce to datetime' do - expect(model).to_not coerce_attribute(:email).to(DateTime) - end - - it 'does not coerce to float' do - expect(model).to_not coerce_attribute(:email).to(Float) - end - - it 'does not coerce to hash' do - expect(model).to_not coerce_attribute(:email).to(Hash) - end - - it 'does not coerce to integer' do - expect(model).to_not coerce_attribute(:email).to(Integer) - end - - it 'does not coerce to pathname' do - expect(model).to_not coerce_attribute(:email).to(Pathname) - end - - it 'does not coerce to set' do - expect(model).to_not coerce_attribute(:email).to(Set) - end - - it 'does not coerce to string' do - expect(model).to_not coerce_attribute(:email).to(String) - end - - it 'does not coerce to symbol' do - expect(model).to_not coerce_attribute(:email).to(Symbol) - end - - it 'does not coerce to time' do - expect(model).to_not coerce_attribute(:email).to(Time) - end - - it 'provides correct failure message' - end -end