Skip to content

Instantly share code, notes, and snippets.

@ozgun
Last active November 2, 2022 13:02
Show Gist options
  • Save ozgun/e062ff629708286f7211 to your computer and use it in GitHub Desktop.
Save ozgun/e062ff629708286f7211 to your computer and use it in GitHub Desktop.
Using ActiveRecord::Store to store boolean fields
# File: app/models/concerns/boolean_store_accessor.rb
#
# When we submit a form in order to update a model, a booelan/checkbox field is posted
# as '1' or '0', and if we are using ActiveRecord::Store, posted value is stored in
# database as '1' or '0'. By the help of this module, we store '1' and '0'
# values as `true` or `false`.
#
# Example usage:
#
# ```
# class Page < ActiveRecord::Base
# extend BooleanStoreAccessor
# store :settings, accessors: [:hide_from_navigation]
# boolean_store_accessor :hide_from_navigation
# end
#
# page = Page.first
# page.hide_from_navigation? #=> false
# page.hide_from_navigation = true
# page.save
# page.hide_from_navigation? #=> true
# page.settings #=> {"hide_from_navigation"=>true}
# ```
#
module BooleanStoreAccessor
def boolean_store_accessor(attr_name)
define_method "#{attr_name}=".to_sym do |value|
values = ['1', true]
super((values.include?(value)) ? true : false)
end
define_method attr_name do
values = [nil, false, '0']
(values.include?(super())) ? false : true
end
define_method "#{attr_name}?".to_sym do
send(attr_name)
end
end
end
@esale
Copy link

esale commented Jul 27, 2021

Great, thanks! I tried to make it work with attr_accessor but it didn't. Any advise? Regards.

@ozgun
Copy link
Author

ozgun commented Jul 27, 2021

Hi @esale, thanks for the feedback. I don't think I've tested it with attr_accessor. I wonder if you could paste your code here so I can try to make BooleanStoreAccessor work with attr_accessor. Thanks.

@ozgun
Copy link
Author

ozgun commented Jul 27, 2021

@esale give this a try:

module BooleanStoreAccessor
  def boolean_store_accessor(attr_name)
    define_method "#{attr_name}=".to_sym do |value|
      values = ['1', true]
      new_value = values.include?(value) ? true : false
      begin
        super(new_value)
      rescue NoMethodError => e # This is hack for `attr_accessor`
        if e.message.match?(/super: no superclass method/)
          instance_variable_set("@#{attr_name}", new_value)
        else
          raise e
        end
      end
    end

    define_method attr_name do
      values = [nil, false, '0']
      stored_value =
        begin
          super()
        rescue NoMethodError => e # This is hack for `attr_accessor`
          if e.message.match?(/super: no superclass method/)
            instance_variable_get("@#{attr_name}")
          else
            raise e
          end
        end
      (values.include?(stored_value)) ? false : true
    end

    define_method "#{attr_name}?".to_sym do
      send(attr_name)
    end
  end
end

@esale
Copy link

esale commented Jul 27, 2021

Of course!
This is how I use your module:

store_accessor :hstore_field, :max, :min, :avg, :sent
extend BooleanStoreAccessor
boolean_store_accessor :sent

and it works as a charm.
Sadly, when I try this (extend inserted before):

attr_accessor :prueba
boolean_store_accessor :prueba

It doesn't work. I also tried attr_reader instead of attr_accessor without success. The error I get is:

super: no superclass method `prueba=' for #<Modelo:0x00007f0b30042e40> Did you mean? prueba prueba?

Thanks.

@esale
Copy link

esale commented Jul 27, 2021

@ozgun, your new code works!

Thanks again!

@ozgun
Copy link
Author

ozgun commented Jul 28, 2021

@esale great, thanks!

@armiiller
Copy link

Thanks for this!

@ozgun
Copy link
Author

ozgun commented Apr 13, 2022

Thanks for this!

My pleasure :)

@dylanfisher
Copy link

Thanks for this! Here's a small change that allows defining a series of multiple attributes in the same signature as store_accessor.

boolean_store_accessor :eula_agreement, :subscribe
module BooleanStoreAccessor
  def boolean_store_accessor(*attr_names)
    attr_names.each do |attr_name|
      define_method "#{attr_name}=".to_sym do |value|
        values = ['1', true]
        new_value = values.include?(value) ? true : false
        begin
          super(new_value)
        rescue NoMethodError => e # This is hack for `attr_accessor`
          if e.message.match?(/super: no superclass method/)
            instance_variable_set("@#{attr_name}", new_value)
          else
            raise e
          end
        end
      end

      define_method attr_name do
        values = [nil, false, '0']
        stored_value =
          begin
            super()
          rescue NoMethodError => e # This is hack for `attr_accessor`
            if e.message.match?(/super: no superclass method/)
              instance_variable_get("@#{attr_name}")
            else
              raise e
            end
          end
        (values.include?(stored_value)) ? false : true
      end

      define_method "#{attr_name}?".to_sym do
        send(attr_name)
      end
    end
  end
end

@ozgun
Copy link
Author

ozgun commented Sep 23, 2022

Thanks @dylanfisher . I haven't tested it yet but it looks good to me at first glance. 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment