module Spree
  # Option types denote the different options for a variant. A typical option
  # type would be a size, with that option type’s values being something such
  # as "Small", "Medium" and "Large". Another typical option type could be a
  # color, such as "Red", "Green", or "Blue".
  #
  # A product can be assigned many option types, but must be assigned at least
  # one if you wish to create variants for that product.
  class OptionType < Spree::Base
    acts_as_list

    has_many :option_values, -> { order(:position) }, dependent: :destroy, inverse_of: :option_type
    has_many :product_option_types, dependent: :destroy, inverse_of: :option_type
    has_many :products, through: :product_option_types

    has_many :option_type_prototypes
    has_many :prototypes, through: :option_type_prototypes

    validates :name, presence: true, uniqueness: { allow_blank: true }
    validates :presentation, presence: true

    default_scope -> { order(:position) }

    accepts_nested_attributes_for :option_values, reject_if: lambda { |ov| ov[:name].blank? || ov[:presentation].blank? }, allow_destroy: true

    after_touch :touch_all_products
    after_save :touch_all_products

    def touch_all_products
      products.find_each(&:touch)
    end
  end
end

module Spree
  # Option types denote the different options for a variant. A typical option
  # type would be a size, with that option type’s values being something such
  # as "Small", "Medium" and "Large". Another typical option type could be a
  # color, such as "Red", "Green", or "Blue".
  #
  # A product can be assigned many option types, but must be assigned at least
  # one if you wish to create variants for that product.
  class OptionType < Spree::Base
    acts_as_list

    has_many :option_values, -> { order(:position) }, dependent: :destroy, inverse_of: :option_type
    has_many :product_option_types, dependent: :destroy, inverse_of: :option_type
    has_many :products, through: :product_option_types

    has_many :option_type_prototypes
    has_many :prototypes, through: :option_type_prototypes

    validates :name, presence: true, uniqueness: { allow_blank: true }
    validates :presentation, presence: true

    default_scope -> { order(:position) }

    accepts_nested_attributes_for :option_values, reject_if: lambda { |ov| ov[:name].blank? && ov[:presentation].blank? }, allow_destroy: true

    after_touch :touch_all_products
    after_save :touch_all_products

    def touch_all_products
      products.find_each(&:touch)
    end
  end
end

Change requirements for rejecting an option value
Change requirements for rejecting an option value

Before if either field was blank it would be rejected by the option type
model.  This led to a confusing behaviour where you could have one field
filled in and the other blank and try to update and be told it updated
successfully while it lost the option type you had started without
telling you that.  Now this will throw an error if you try that telling
you that the option type did not save.  However if both fields are blank
it will just reject the option type.
