Join the Stack Overflow Community
Stack Overflow is a community of 4.7 million programmers, just like you, helping each other.
Join them; it only takes a minute:
Sign up

My Product model has a jsonb field specs (which we're managing using ActiveRecord's store_accessor). Many of my products' specs have a spec in that hash called spec_options.

Before now, this spec_option field was just text. Now it needs to be an array.

The scope used before now to query products for this field was this:

scope :with_spec_options, ->(spec_options) { 
    where("'#{spec_options}'::jsonb \? (specs->>'spec_option')") 
}

Ruby equivalent (just to help understand what this is doing):

select{ |product| spec_options.include?(product.specs['spec_option']) }

ActiveRecord equivalent (if spec_option were a regular column):

where(spec_option: spec_options)

However, now that specs['spec_options'] is an array, I can't do that. I think I need to use postgres' ?| jsonb operator, but I can't work out how to get the right side of this operation into the correct format.

Ruby equivalent:

def self.with_spec_options(spec_options)
    all.select{|product| 
        if product.specs['spec_options'].present?
            product.specs['spec_options'].any?{|option|
                spec_options.include?(option)
            }
        else
            false
        end
    }
end

Anyone got ideas?

share|improve this question
up vote 3 down vote accepted

What you want to use is the @> operator, which tests whether your left-hand value contains the right-hand value. "Contains" works for both objects and arrays, so the following query would work:

SELECT * FROM products WHERE specs->'spec_options' @> '["spec1", "spec2"]';

Which I believe you can transform into ActiveRecord-compatible syntax like so:

scope :with_spec_options, ->(spec_options) { 
  where("specs->'spec_option' @> ?", spec_options.to_json) 
}
share|improve this answer
    
Thank you! That was almost it. Both sides of the operation needed to be jsonb, so this worked: where("specs->'spec_option' @> ?::jsonb", spec_options.to_json) – Obversity Mar 2 at 4:52
    
Scrap that, you don't need the extra ::jsonb conversion: where("specs->'spec_option' @> ?", spec_options.to_json) – Obversity Mar 2 at 5:07
    
Ugh, so I thought this was a solution, but it's not quite what I need. It needs to return results if any of the queried options match. This answer only returns results with all matching. – Obversity Mar 2 at 5:32
    
@Obversity: try this instead: where("specs->'spec_option' ?| :options", options: spec_options). – Robert Nubel Mar 2 at 19:17
1  
Sorry, I meant: where("specs->'spec_option' ?| array[:options]", options: spec_options). The interpolation into proper Postgres syntax is a bit tricky. – Robert Nubel Mar 2 at 19:23

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.