shoulda-matchers 4.3.0 → 4.4.1
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 +4 -4
- data/README.md +157 -77
- data/lib/shoulda/matchers/active_model/numericality_matchers/comparison_matcher.rb +5 -1
- data/lib/shoulda/matchers/active_model/validate_inclusion_of_matcher.rb +2 -21
- data/lib/shoulda/matchers/active_model/validate_length_of_matcher.rb +27 -3
- data/lib/shoulda/matchers/active_model/validate_numericality_of_matcher.rb +32 -0
- data/lib/shoulda/matchers/active_model/validation_matcher.rb +25 -0
- data/lib/shoulda/matchers/active_record.rb +2 -0
- data/lib/shoulda/matchers/active_record/association_matcher.rb +20 -4
- data/lib/shoulda/matchers/active_record/association_matchers/join_table_matcher.rb +2 -2
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflection.rb +12 -6
- data/lib/shoulda/matchers/active_record/association_matchers/model_reflector.rb +20 -3
- data/lib/shoulda/matchers/active_record/have_attached_matcher.rb +147 -0
- data/lib/shoulda/matchers/active_record/have_implicit_order_column.rb +106 -0
- data/lib/shoulda/matchers/active_record/have_secure_token_matcher.rb +28 -9
- data/lib/shoulda/matchers/active_record/validate_uniqueness_of_matcher.rb +2 -2
- data/lib/shoulda/matchers/independent.rb +0 -1
- data/lib/shoulda/matchers/rails_shim.rb +4 -0
- data/lib/shoulda/matchers/util.rb +9 -2
- data/lib/shoulda/matchers/util/word_wrap.rb +1 -1
- data/lib/shoulda/matchers/version.rb +1 -1
- metadata +8 -8
- data/MIT-LICENSE +0 -22
- data/lib/shoulda/matchers/independent/delegate_method_matcher/stubbed_target.rb +0 -37
| @@ -232,13 +232,34 @@ module Shoulda | |
| 232 232 | 
             
                  #       it { should validate_length_of(:bio).is_at_least(15).allow_nil }
         | 
| 233 233 | 
             
                  #     end
         | 
| 234 234 | 
             
                  #
         | 
| 235 | 
            -
                  #     #  | 
| 235 | 
            +
                  #     # Minitest (Shoulda)
         | 
| 236 236 | 
             
                  #     class UserTest < ActiveSupport::TestCase
         | 
| 237 237 | 
             
                  #       should validate_length_of(:bio).is_at_least(15).allow_nil
         | 
| 238 238 | 
             
                  #     end
         | 
| 239 239 | 
             
                  #
         | 
| 240 240 | 
             
                  # @return [ValidateLengthOfMatcher]
         | 
| 241 241 | 
             
                  #
         | 
| 242 | 
            +
                  # # ##### allow_blank
         | 
| 243 | 
            +
                  #
         | 
| 244 | 
            +
                  # Use `allow_blank` to assert that the attribute allows blank.
         | 
| 245 | 
            +
                  #
         | 
| 246 | 
            +
                  #     class User
         | 
| 247 | 
            +
                  #       include ActiveModel::Model
         | 
| 248 | 
            +
                  #       attr_accessor :bio
         | 
| 249 | 
            +
                  #
         | 
| 250 | 
            +
                  #       validates_length_of :bio, minimum: 15, allow_blank: true
         | 
| 251 | 
            +
                  #     end
         | 
| 252 | 
            +
                  #
         | 
| 253 | 
            +
                  #     # RSpec
         | 
| 254 | 
            +
                  #     describe User do
         | 
| 255 | 
            +
                  #       it { should validate_length_of(:bio).is_at_least(15).allow_blank }
         | 
| 256 | 
            +
                  #     end
         | 
| 257 | 
            +
                  #
         | 
| 258 | 
            +
                  #     # Minitest (Shoulda)
         | 
| 259 | 
            +
                  #     class UserTest < ActiveSupport::TestCase
         | 
| 260 | 
            +
                  #       should validate_length_of(:bio).is_at_least(15).allow_blank
         | 
| 261 | 
            +
                  #     end
         | 
| 262 | 
            +
                  #
         | 
| 242 263 | 
             
                  def validate_length_of(attr)
         | 
| 243 264 | 
             
                    ValidateLengthOfMatcher.new(attr)
         | 
| 244 265 | 
             
                  end
         | 
| @@ -331,7 +352,8 @@ module Shoulda | |
| 331 352 |  | 
| 332 353 | 
             
                      lower_bound_matches? &&
         | 
| 333 354 | 
             
                        upper_bound_matches? &&
         | 
| 334 | 
            -
                        allow_nil_matches?
         | 
| 355 | 
            +
                        allow_nil_matches? &&
         | 
| 356 | 
            +
                        allow_blank_matches?
         | 
| 335 357 | 
             
                    end
         | 
| 336 358 |  | 
| 337 359 | 
             
                    def does_not_match?(subject)
         | 
| @@ -339,7 +361,8 @@ module Shoulda | |
| 339 361 |  | 
| 340 362 | 
             
                      lower_bound_does_not_match? ||
         | 
| 341 363 | 
             
                        upper_bound_does_not_match? ||
         | 
| 342 | 
            -
                        allow_nil_does_not_match?
         | 
| 364 | 
            +
                        allow_nil_does_not_match? ||
         | 
| 365 | 
            +
                        allow_blank_does_not_match?
         | 
| 343 366 | 
             
                    end
         | 
| 344 367 |  | 
| 345 368 | 
             
                    private
         | 
| @@ -376,6 +399,7 @@ module Shoulda | |
| 376 399 | 
             
                    def disallows_lower_length?
         | 
| 377 400 | 
             
                      !@options.key?(:minimum) ||
         | 
| 378 401 | 
             
                        @options[:minimum] == 0 ||
         | 
| 402 | 
            +
                        (@options[:minimum] == 1 && expects_to_allow_blank?) ||
         | 
| 379 403 | 
             
                        disallows_length_of?(
         | 
| 380 404 | 
             
                          @options[:minimum] - 1,
         | 
| 381 405 | 
             
                          translated_short_message
         | 
| @@ -204,6 +204,33 @@ module Shoulda | |
| 204 204 | 
             
                  #         is_greater_than(21)
         | 
| 205 205 | 
             
                  #     end
         | 
| 206 206 | 
             
                  #
         | 
| 207 | 
            +
                  # ##### is_other_than
         | 
| 208 | 
            +
                  #
         | 
| 209 | 
            +
                  # Use `is_other_than` to test usage of the `:other_than` option.
         | 
| 210 | 
            +
                  # This asserts that the attribute can take a number which is not equal to
         | 
| 211 | 
            +
                  # the given value.
         | 
| 212 | 
            +
                  #
         | 
| 213 | 
            +
                  #     class Person
         | 
| 214 | 
            +
                  #       include ActiveModel::Model
         | 
| 215 | 
            +
                  #       attr_accessor :legal_age
         | 
| 216 | 
            +
                  #
         | 
| 217 | 
            +
                  #       validates_numericality_of :legal_age, other_than: 21
         | 
| 218 | 
            +
                  #     end
         | 
| 219 | 
            +
                  #
         | 
| 220 | 
            +
                  #     # RSpec
         | 
| 221 | 
            +
                  #     RSpec.describe Person, type: :model do
         | 
| 222 | 
            +
                  #       it do
         | 
| 223 | 
            +
                  #         should validate_numericality_of(:legal_age).
         | 
| 224 | 
            +
                  #           is_other_than(21)
         | 
| 225 | 
            +
                  #       end
         | 
| 226 | 
            +
                  #     end
         | 
| 227 | 
            +
                  #
         | 
| 228 | 
            +
                  #     # Minitest (Shoulda)
         | 
| 229 | 
            +
                  #     class PersonTest < ActiveSupport::TestCase
         | 
| 230 | 
            +
                  #       should validate_numericality_of(:legal_age).
         | 
| 231 | 
            +
                  #         is_other_than(21)
         | 
| 232 | 
            +
                  #     end
         | 
| 233 | 
            +
                  #
         | 
| 207 234 | 
             
                  # ##### even
         | 
| 208 235 | 
             
                  #
         | 
| 209 236 | 
             
                  # Use `even` to test usage of the `:even` option. This asserts that the
         | 
| @@ -395,6 +422,11 @@ module Shoulda | |
| 395 422 | 
             
                      self
         | 
| 396 423 | 
             
                    end
         | 
| 397 424 |  | 
| 425 | 
            +
                    def is_other_than(value)
         | 
| 426 | 
            +
                      prepare_submatcher(comparison_matcher_for(value, :!=).for(@attribute))
         | 
| 427 | 
            +
                      self
         | 
| 428 | 
            +
                    end
         | 
| 429 | 
            +
             | 
| 398 430 | 
             
                    def with_message(message)
         | 
| 399 431 | 
             
                      @expects_custom_validation_message = true
         | 
| 400 432 | 
             
                      @expected_message = message
         | 
| @@ -24,6 +24,11 @@ module Shoulda | |
| 24 24 | 
             
                      self
         | 
| 25 25 | 
             
                    end
         | 
| 26 26 |  | 
| 27 | 
            +
                    def allow_blank
         | 
| 28 | 
            +
                      options[:allow_blank] = true
         | 
| 29 | 
            +
                      self
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
             | 
| 27 32 | 
             
                    def strict
         | 
| 28 33 | 
             
                      @expects_strict = true
         | 
| 29 34 | 
             
                      self
         | 
| @@ -116,8 +121,20 @@ module Shoulda | |
| 116 121 | 
             
                      )
         | 
| 117 122 | 
             
                    end
         | 
| 118 123 |  | 
| 124 | 
            +
                    def allow_blank_matches?
         | 
| 125 | 
            +
                      !expects_to_allow_blank? ||
         | 
| 126 | 
            +
                        blank_values.all? { |value| allows_value_of(value) }
         | 
| 127 | 
            +
                    end
         | 
| 128 | 
            +
             | 
| 129 | 
            +
                    def allow_blank_does_not_match?
         | 
| 130 | 
            +
                      expects_to_allow_blank? &&
         | 
| 131 | 
            +
                        blank_values.all? { |value| disallows_value_of(value) }
         | 
| 132 | 
            +
                    end
         | 
| 133 | 
            +
             | 
| 119 134 | 
             
                    private
         | 
| 120 135 |  | 
| 136 | 
            +
                    attr_reader :options
         | 
| 137 | 
            +
             | 
| 121 138 | 
             
                    def overall_failure_message
         | 
| 122 139 | 
             
                      Shoulda::Matchers.word_wrap(
         | 
| 123 140 | 
             
                        "Expected #{model.name} to #{description}, but this could not be " +
         | 
| @@ -161,6 +178,14 @@ module Shoulda | |
| 161 178 | 
             
                      @last_submatcher_run = matcher
         | 
| 162 179 | 
             
                      matcher.matches?(subject)
         | 
| 163 180 | 
             
                    end
         | 
| 181 | 
            +
             | 
| 182 | 
            +
                    def expects_to_allow_blank?
         | 
| 183 | 
            +
                      options[:allow_blank]
         | 
| 184 | 
            +
                    end
         | 
| 185 | 
            +
             | 
| 186 | 
            +
                    def blank_values
         | 
| 187 | 
            +
                      ['', ' ', "\n", "\r", "\t", "\f"]
         | 
| 188 | 
            +
                    end
         | 
| 164 189 | 
             
                  end
         | 
| 165 190 | 
             
                end
         | 
| 166 191 | 
             
              end
         | 
| @@ -14,6 +14,7 @@ require "shoulda/matchers/active_record/association_matchers/model_reflection" | |
| 14 14 | 
             
            require "shoulda/matchers/active_record/association_matchers/option_verifier"
         | 
| 15 15 | 
             
            require "shoulda/matchers/active_record/have_db_column_matcher"
         | 
| 16 16 | 
             
            require "shoulda/matchers/active_record/have_db_index_matcher"
         | 
| 17 | 
            +
            require "shoulda/matchers/active_record/have_implicit_order_column"
         | 
| 17 18 | 
             
            require "shoulda/matchers/active_record/have_readonly_attribute_matcher"
         | 
| 18 19 | 
             
            require "shoulda/matchers/active_record/have_rich_text_matcher"
         | 
| 19 20 | 
             
            require "shoulda/matchers/active_record/have_secure_token_matcher"
         | 
| @@ -22,6 +23,7 @@ require "shoulda/matchers/active_record/accept_nested_attributes_for_matcher" | |
| 22 23 | 
             
            require "shoulda/matchers/active_record/define_enum_for_matcher"
         | 
| 23 24 | 
             
            require "shoulda/matchers/active_record/uniqueness"
         | 
| 24 25 | 
             
            require "shoulda/matchers/active_record/validate_uniqueness_of_matcher"
         | 
| 26 | 
            +
            require "shoulda/matchers/active_record/have_attached_matcher"
         | 
| 25 27 |  | 
| 26 28 | 
             
            module Shoulda
         | 
| 27 29 | 
             
              module Matchers
         | 
| @@ -920,21 +920,21 @@ module Shoulda | |
| 920 920 | 
             
                  # asserts that the table you're referring to actually exists.
         | 
| 921 921 | 
             
                  #
         | 
| 922 922 | 
             
                  #     class Person < ActiveRecord::Base
         | 
| 923 | 
            -
                  #       has_and_belongs_to_many :issues, join_table:  | 
| 923 | 
            +
                  #       has_and_belongs_to_many :issues, join_table: :people_tickets
         | 
| 924 924 | 
             
                  #     end
         | 
| 925 925 | 
             
                  #
         | 
| 926 926 | 
             
                  #     # RSpec
         | 
| 927 927 | 
             
                  #     RSpec.describe Person, type: :model do
         | 
| 928 928 | 
             
                  #       it do
         | 
| 929 929 | 
             
                  #         should have_and_belong_to_many(:issues).
         | 
| 930 | 
            -
                  #           join_table( | 
| 930 | 
            +
                  #           join_table(:people_tickets)
         | 
| 931 931 | 
             
                  #       end
         | 
| 932 932 | 
             
                  #     end
         | 
| 933 933 | 
             
                  #
         | 
| 934 934 | 
             
                  #     # Minitest (Shoulda)
         | 
| 935 935 | 
             
                  #     class PersonTest < ActiveSupport::TestCase
         | 
| 936 936 | 
             
                  #       should have_and_belong_to_many(:issues).
         | 
| 937 | 
            -
                  #         join_table( | 
| 937 | 
            +
                  #         join_table(:people_tickets)
         | 
| 938 938 | 
             
                  #     end
         | 
| 939 939 | 
             
                  #
         | 
| 940 940 | 
             
                  # ##### validate
         | 
| @@ -1144,6 +1144,7 @@ module Shoulda | |
| 1144 1144 | 
             
                      @subject = subject
         | 
| 1145 1145 | 
             
                      association_exists? &&
         | 
| 1146 1146 | 
             
                        macro_correct? &&
         | 
| 1147 | 
            +
                        validate_inverse_of_through_association &&
         | 
| 1147 1148 | 
             
                        (polymorphic? || class_exists?) &&
         | 
| 1148 1149 | 
             
                        foreign_key_exists? &&
         | 
| 1149 1150 | 
             
                        primary_key_exists? &&
         | 
| @@ -1198,7 +1199,14 @@ module Shoulda | |
| 1198 1199 | 
             
                    end
         | 
| 1199 1200 |  | 
| 1200 1201 | 
             
                    def expectation
         | 
| 1201 | 
            -
                       | 
| 1202 | 
            +
                      expectation =
         | 
| 1203 | 
            +
                        "#{model_class.name} to have a #{macro} association called #{name}"
         | 
| 1204 | 
            +
             | 
| 1205 | 
            +
                      if through?
         | 
| 1206 | 
            +
                        expectation << " through #{reflector.has_and_belongs_to_many_name}"
         | 
| 1207 | 
            +
                      end
         | 
| 1208 | 
            +
             | 
| 1209 | 
            +
                      expectation
         | 
| 1202 1210 | 
             
                    end
         | 
| 1203 1211 |  | 
| 1204 1212 | 
             
                    def missing_options
         | 
| @@ -1241,6 +1249,14 @@ module Shoulda | |
| 1241 1249 | 
             
                      end
         | 
| 1242 1250 | 
             
                    end
         | 
| 1243 1251 |  | 
| 1252 | 
            +
                    def validate_inverse_of_through_association
         | 
| 1253 | 
            +
                      reflector.validate_inverse_of_through_association!
         | 
| 1254 | 
            +
                      true
         | 
| 1255 | 
            +
                    rescue ::ActiveRecord::ActiveRecordError => error
         | 
| 1256 | 
            +
                      @missing = error.message
         | 
| 1257 | 
            +
                      false
         | 
| 1258 | 
            +
                    end
         | 
| 1259 | 
            +
             | 
| 1244 1260 | 
             
                    def macro_supports_primary_key?
         | 
| 1245 1261 | 
             
                      macro == :belongs_to ||
         | 
| 1246 1262 | 
             
                        ([:has_many, :has_one].include?(macro) && !through?)
         | 
| @@ -29,7 +29,7 @@ module Shoulda | |
| 29 29 | 
             
                          if option_verifier.correct_for_string?(:join_table, options[:join_table_name])
         | 
| 30 30 | 
             
                            true
         | 
| 31 31 | 
             
                          else
         | 
| 32 | 
            -
                            @failure_message = "#{name} should use  | 
| 32 | 
            +
                            @failure_message = "#{name} should use #{options[:join_table_name].inspect} for :join_table option"
         | 
| 33 33 | 
             
                            false
         | 
| 34 34 | 
             
                          end
         | 
| 35 35 | 
             
                        else
         | 
| @@ -38,7 +38,7 @@ module Shoulda | |
| 38 38 | 
             
                      end
         | 
| 39 39 |  | 
| 40 40 | 
             
                      def join_table_exists?
         | 
| 41 | 
            -
                        if RailsShim.tables_and_views(connection).include?(join_table_name)
         | 
| 41 | 
            +
                        if RailsShim.tables_and_views(connection).include?(join_table_name.to_s)
         | 
| 42 42 | 
             
                          true
         | 
| 43 43 | 
             
                        else
         | 
| 44 44 | 
             
                          @failure_message = missing_table_message
         | 
| @@ -35,12 +35,12 @@ module Shoulda | |
| 35 35 | 
             
                        join_table_name.to_s
         | 
| 36 36 | 
             
                      end
         | 
| 37 37 |  | 
| 38 | 
            -
                      def association_relation
         | 
| 38 | 
            +
                      def association_relation(related_instance)
         | 
| 39 39 | 
             
                        relation = associated_class.all
         | 
| 40 40 |  | 
| 41 41 | 
             
                        if reflection.scope
         | 
| 42 42 | 
             
                          # Source: AR::Associations::AssociationScope#eval_scope
         | 
| 43 | 
            -
                          relation.instance_exec( | 
| 43 | 
            +
                          relation.instance_exec(related_instance, &reflection.scope)
         | 
| 44 44 | 
             
                        else
         | 
| 45 45 | 
             
                          relation
         | 
| 46 46 | 
             
                        end
         | 
| @@ -65,16 +65,22 @@ module Shoulda | |
| 65 65 | 
             
                        end
         | 
| 66 66 | 
             
                      end
         | 
| 67 67 |  | 
| 68 | 
            +
                      def validate_inverse_of_through_association!
         | 
| 69 | 
            +
                        if through?
         | 
| 70 | 
            +
                          reflection.check_validity!
         | 
| 71 | 
            +
                        end
         | 
| 72 | 
            +
                      end
         | 
| 73 | 
            +
             | 
| 74 | 
            +
                      def has_and_belongs_to_many_name
         | 
| 75 | 
            +
                        reflection.options[:through]
         | 
| 76 | 
            +
                      end
         | 
| 77 | 
            +
             | 
| 68 78 | 
             
                      protected
         | 
| 69 79 |  | 
| 70 80 | 
             
                      attr_reader :reflection, :subject
         | 
| 71 81 |  | 
| 72 82 | 
             
                      private
         | 
| 73 83 |  | 
| 74 | 
            -
                      def has_and_belongs_to_many_name
         | 
| 75 | 
            -
                        reflection.options[:through]
         | 
| 76 | 
            -
                      end
         | 
| 77 | 
            -
             | 
| 78 84 | 
             
                      def has_and_belongs_to_many_name_table_name
         | 
| 79 85 | 
             
                        if has_and_belongs_to_many_reflection
         | 
| 80 86 | 
             
                          has_and_belongs_to_many_reflection.table_name
         | 
| @@ -4,15 +4,32 @@ module Shoulda | |
| 4 4 | 
             
                  module AssociationMatchers
         | 
| 5 5 | 
             
                    # @private
         | 
| 6 6 | 
             
                    class ModelReflector
         | 
| 7 | 
            -
                      delegate | 
| 8 | 
            -
                        : | 
| 9 | 
            -
                        :association_foreign_key, | 
| 7 | 
            +
                      delegate(
         | 
| 8 | 
            +
                        :associated_class,
         | 
| 9 | 
            +
                        :association_foreign_key,
         | 
| 10 | 
            +
                        :foreign_key,
         | 
| 11 | 
            +
                        :has_and_belongs_to_many_name,
         | 
| 12 | 
            +
                        :join_table_name,
         | 
| 13 | 
            +
                        :polymorphic?,
         | 
| 14 | 
            +
                        :validate_inverse_of_through_association!,
         | 
| 15 | 
            +
                        to: :reflection
         | 
| 16 | 
            +
                      )
         | 
| 17 | 
            +
             | 
| 18 | 
            +
                      delegate(
         | 
| 19 | 
            +
                        :through?,
         | 
| 20 | 
            +
                        to: :reflection,
         | 
| 21 | 
            +
                        allow_nil: true
         | 
| 22 | 
            +
                      )
         | 
| 10 23 |  | 
| 11 24 | 
             
                      def initialize(subject, name)
         | 
| 12 25 | 
             
                        @subject = subject
         | 
| 13 26 | 
             
                        @name = name
         | 
| 14 27 | 
             
                      end
         | 
| 15 28 |  | 
| 29 | 
            +
                      def association_relation
         | 
| 30 | 
            +
                        reflection.association_relation(subject)
         | 
| 31 | 
            +
                      end
         | 
| 32 | 
            +
             | 
| 16 33 | 
             
                      def reflection
         | 
| 17 34 | 
             
                        @reflection ||= reflect_on_association(name)
         | 
| 18 35 | 
             
                      end
         | 
| @@ -0,0 +1,147 @@ | |
| 1 | 
            +
            module Shoulda
         | 
| 2 | 
            +
              module Matchers
         | 
| 3 | 
            +
                module ActiveRecord
         | 
| 4 | 
            +
                  def have_one_attached(name)
         | 
| 5 | 
            +
                    HaveAttachedMatcher.new(:one, name)
         | 
| 6 | 
            +
                  end
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                  def have_many_attached(name)
         | 
| 9 | 
            +
                    HaveAttachedMatcher.new(:many, name)
         | 
| 10 | 
            +
                  end
         | 
| 11 | 
            +
             | 
| 12 | 
            +
                  # @private
         | 
| 13 | 
            +
                  class HaveAttachedMatcher
         | 
| 14 | 
            +
                    attr_reader :name
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    def initialize(macro, name)
         | 
| 17 | 
            +
                      @macro = macro
         | 
| 18 | 
            +
                      @name = name
         | 
| 19 | 
            +
                    end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                    def description
         | 
| 22 | 
            +
                      "have a has_#{macro}_attached called #{name}"
         | 
| 23 | 
            +
                    end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                    def failure_message
         | 
| 26 | 
            +
                      <<-MESSAGE
         | 
| 27 | 
            +
            Expected #{expectation}, but this could not be proved.
         | 
| 28 | 
            +
              #{@failure}
         | 
| 29 | 
            +
                      MESSAGE
         | 
| 30 | 
            +
                    end
         | 
| 31 | 
            +
             | 
| 32 | 
            +
                    def failure_message_when_negated
         | 
| 33 | 
            +
                      <<-MESSAGE
         | 
| 34 | 
            +
            Did not expect #{expectation}, but it does.
         | 
| 35 | 
            +
                      MESSAGE
         | 
| 36 | 
            +
                    end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                    def expectation
         | 
| 39 | 
            +
                      "#{model_class.name} to #{description}"
         | 
| 40 | 
            +
                    end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                    def matches?(subject)
         | 
| 43 | 
            +
                      @subject = subject
         | 
| 44 | 
            +
                      reader_attribute_exists? &&
         | 
| 45 | 
            +
                        writer_attribute_exists? &&
         | 
| 46 | 
            +
                        attachments_association_exists? &&
         | 
| 47 | 
            +
                        blobs_association_exists? &&
         | 
| 48 | 
            +
                        eager_loading_scope_exists?
         | 
| 49 | 
            +
                    end
         | 
| 50 | 
            +
             | 
| 51 | 
            +
                    private
         | 
| 52 | 
            +
             | 
| 53 | 
            +
                    attr_reader :subject, :macro
         | 
| 54 | 
            +
             | 
| 55 | 
            +
                    def reader_attribute_exists?
         | 
| 56 | 
            +
                      if subject.respond_to?(name)
         | 
| 57 | 
            +
                        true
         | 
| 58 | 
            +
                      else
         | 
| 59 | 
            +
                        @failure = "#{model_class.name} does not have a :#{name} method."
         | 
| 60 | 
            +
                        false
         | 
| 61 | 
            +
                      end
         | 
| 62 | 
            +
                    end
         | 
| 63 | 
            +
             | 
| 64 | 
            +
                    def writer_attribute_exists?
         | 
| 65 | 
            +
                      if subject.respond_to?("#{name}=")
         | 
| 66 | 
            +
                        true
         | 
| 67 | 
            +
                      else
         | 
| 68 | 
            +
                        @failure = "#{model_class.name} does not have a :#{name}= method."
         | 
| 69 | 
            +
                        false
         | 
| 70 | 
            +
                      end
         | 
| 71 | 
            +
                    end
         | 
| 72 | 
            +
             | 
| 73 | 
            +
                    def attachments_association_exists?
         | 
| 74 | 
            +
                      if attachments_association_matcher.matches?(subject)
         | 
| 75 | 
            +
                        true
         | 
| 76 | 
            +
                      else
         | 
| 77 | 
            +
                        @failure = attachments_association_matcher.failure_message
         | 
| 78 | 
            +
                        false
         | 
| 79 | 
            +
                      end
         | 
| 80 | 
            +
                    end
         | 
| 81 | 
            +
             | 
| 82 | 
            +
                    def attachments_association_matcher
         | 
| 83 | 
            +
                      @_attachments_association_matcher ||=
         | 
| 84 | 
            +
                        AssociationMatcher.new(
         | 
| 85 | 
            +
                          :"has_#{macro}",
         | 
| 86 | 
            +
                          attachments_association_name,
         | 
| 87 | 
            +
                        ).
         | 
| 88 | 
            +
                          conditions(name: name).
         | 
| 89 | 
            +
                          class_name('ActiveStorage::Attachment').
         | 
| 90 | 
            +
                          inverse_of(:record)
         | 
| 91 | 
            +
                    end
         | 
| 92 | 
            +
             | 
| 93 | 
            +
                    def attachments_association_name
         | 
| 94 | 
            +
                      case macro
         | 
| 95 | 
            +
                      when :one then
         | 
| 96 | 
            +
                        "#{name}_attachment"
         | 
| 97 | 
            +
                      when :many then
         | 
| 98 | 
            +
                        "#{name}_attachments"
         | 
| 99 | 
            +
                      end
         | 
| 100 | 
            +
                    end
         | 
| 101 | 
            +
             | 
| 102 | 
            +
                    def blobs_association_exists?
         | 
| 103 | 
            +
                      if blobs_association_matcher.matches?(subject)
         | 
| 104 | 
            +
                        true
         | 
| 105 | 
            +
                      else
         | 
| 106 | 
            +
                        @failure = blobs_association_matcher.failure_message
         | 
| 107 | 
            +
                        false
         | 
| 108 | 
            +
                      end
         | 
| 109 | 
            +
                    end
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                    def blobs_association_matcher
         | 
| 112 | 
            +
                      @_blobs_association_matcher ||=
         | 
| 113 | 
            +
                        AssociationMatcher.new(
         | 
| 114 | 
            +
                          :"has_#{macro}",
         | 
| 115 | 
            +
                          blobs_association_name,
         | 
| 116 | 
            +
                        ).
         | 
| 117 | 
            +
                          through(attachments_association_name).
         | 
| 118 | 
            +
                          class_name('ActiveStorage::Blob').
         | 
| 119 | 
            +
                          source(:blob)
         | 
| 120 | 
            +
                    end
         | 
| 121 | 
            +
             | 
| 122 | 
            +
                    def blobs_association_name
         | 
| 123 | 
            +
                      case macro
         | 
| 124 | 
            +
                      when :one then
         | 
| 125 | 
            +
                        "#{name}_blob"
         | 
| 126 | 
            +
                      when :many then
         | 
| 127 | 
            +
                        "#{name}_blobs"
         | 
| 128 | 
            +
                      end
         | 
| 129 | 
            +
                    end
         | 
| 130 | 
            +
             | 
| 131 | 
            +
                    def eager_loading_scope_exists?
         | 
| 132 | 
            +
                      if model_class.respond_to?("with_attached_#{name}")
         | 
| 133 | 
            +
                        true
         | 
| 134 | 
            +
                      else
         | 
| 135 | 
            +
                        @failure = "#{model_class.name} does not have a " \
         | 
| 136 | 
            +
                                   ":with_attached_#{name} scope."
         | 
| 137 | 
            +
                        false
         | 
| 138 | 
            +
                      end
         | 
| 139 | 
            +
                    end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                    def model_class
         | 
| 142 | 
            +
                      subject.class
         | 
| 143 | 
            +
                    end
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
                end
         | 
| 146 | 
            +
              end
         | 
| 147 | 
            +
            end
         |