I am having an issue, I have the next class
class Question < ApplicationRecord
include Mappable
...
end
so my problem is at the time to create a Question, I need to keep the Question validations but skip the ones that are incoming from Mappable
because by now I am using question.save(validate: false)
and I have to update that to something like question.save(mappable_validate: false)
, just to skip the validations from Mappable
EDIT
Mappable:
module Mappable
extend ActiveSupport::Concern
included do
attr_accessor :skip_map
has_one :map_location, dependent: :destroy
accepts_nested_attributes_for :map_location, allow_destroy: true, reject_if: :all_blank
validate :map_must_be_valid, on: :create, if: :feature_maps?
def map_must_be_valid
return true if skip_map?
unless map_location.try(:available?)
skip_map_error = "Map error"
errors.add(:skip_map, skip_map_error)
end
end
def feature_maps?
Setting["feature.map"].present?
end
def skip_map?
skip_map == "1"
end
end
end
CodePudding user response:
There are quite a few ways to solve this. But no reliable ones that don't involve modifying the module.
One would be simply to use composition and move the validations to its own module:
module Mappable
module Validations
extend ActiveSupport::Concern
included do
validate :map_must_be_valid, on: :create, if: :feature_maps?
end
end
end
class Question < ApplicationRecord
include Mappable
include Mappable
end
class Foo < ApplicationRecord
include Mappable
include Mappable::Validations
end
Another very common way to make the behavior provided by a module customizeable is to not just cram all your code into the Module#included
hook which doesn't let you pass options.
Instead create a class method:
module Mappable
extend ActiveSupport::Concern
def map_must_be_valid
return true if skip_map?
unless map_location.try(:available?)
skip_map_error = "Map error"
errors.add(:skip_map, skip_map_error)
end
end
def feature_maps?
Setting["feature.map"].present?
end
def skip_map?
skip_map == "1"
end
module ClassMethods
def make_mappable(validate: true)
attr_accessor :skip_map
has_one :map_location, dependent: :destroy
accepts_nested_attributes_for :map_location,
allow_destroy: true, reject_if: :all_blank
if validate
validate :map_must_be_valid, on: :create, if: :feature_maps?
end
end
end
end
And then just call the class method in the class you want to modify.
class Question < ApplicationRecord
include Mappable
make_mappable(validate: false)
end
This pattern can be found everywhere in Rails and Ruby in general and lets you make the functionality you're providing much flexible.
I understand that this might not seem to be immediately helpful as the code is coming from a gem. But it can help you understand what to do to fix the gem or evaluate if its actually worthwhile/needed.