2

I have a model Foo with a jsonb type column, which stores boolean pairs for attributes:

Schema:

...
add_column :foos,  :roles, :jsonb, null: :false, default:
  '{
    "bar"   : true,  
    "baz"   : false,
    "fizz"  : false
  }'
...

Model:

class Foo < ActiveRecord::Base
  store_accessor :roles, :bar, :baz, :fizz 
end

Is there a way to access these attributes as booleans without writing a method for each:

Be able to do:

foo = Foo.new 
foo.bar? #<= should return 'true'

Without adding:

class Foo < ActiveRecord::Base
...
  def bar?
    bar == true 
  end
...
end

2 Answers 2

1

ActiveRecord::Store source code outlines that the most common way enchance getter values is to define methods by hands (you already know that):

All stored values are automatically available through accessors on the Active Record object, but sometimes you want to specialize this behavior. This can be done by overwriting the default accessors (using the same name as the attribute) and calling super to actually change things.

Your case with question mark methods is a bit simpler though. As long as jsonb keys are basically strings, you could refactor them to have that question mark at the end (and have a corresponding "questioned" getter):

add_column :foos,  :roles, :jsonb, null: :false,
  default: '{ "bar?" : true, ... }'

class Foo < ActiveRecord::Base
  store_accessor :roles, :bar?

Another option, as @Ilya suggested, is to define those methods with some metaprogramming (although it would be tempting to move store_accessor :roles, method inside of the loop just to have all store-related definitions in one place).

And the last, but not the least, is to patch ActiveRecord::Store :) As long as store_accessor is basically just a shortcut which defines getter and setter methods, you could pass it some additional parameter and define questioned? methods based on its value.

Here is an implementation with a separate method to achieve the goal:

module ActiveRecord
  module Store
    module ClassMethods
      def store_accessor_with_question(store_attribute, *keys)
        store_accessor(store_attribute, keys)
        keys.flatten.each do |key|
          define_method("#{key}?") do
            send(key) == true
          end
        end
      end

Loading it inside of your config/initializers folder, you should be able to do:

class Foo < ActiveRecord::Base
  store_accessor_with_question :roles, :bar, :baz
end

foo = Foo.new 
foo.bar  #<= true
foo.bar? #<= true
Sign up to request clarification or add additional context in comments.

2 Comments

thanks! I'm thinking the Store module should go into /concerns since it extends ActiveRecord. What do you think?
you're welcome. /concerns are primaraly used for the modules which extend your own classes. I prefer to keep monkeypatches inside initializers as long as ActiveRecord is something different and you probably want it to be loaded as soon as possible.
1

I think that the closest way for you is define this methods dynamically, using define_method:

class Foo < ActiveRecord::Base
...
  %w(baz bar fizz).each do |method|
    define_method("#{method}?") do
      send(method) == true
    end
  end
...
end

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.