squint 0.0.2 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/.all-contributorsrc +108 -0
- data/lib/squint.rb +107 -87
- data/lib/squint/version.rb +1 -1
- data/readme.md +186 -0
- data/test/dummy/app/assets/config/manifest.js +1 -0
- data/test/dummy/app/models/post.rb +2 -0
- data/test/dummy/app/models/user.rb +6 -0
- data/test/dummy/config/application.rb +3 -1
- data/test/dummy/config/environments/test.rb +7 -2
- data/test/dummy/db/migrate/20200318185942_create_users.rb +9 -0
- data/test/dummy/db/migrate/20200318185943_add_settings_to_posts.rb +7 -0
- data/test/dummy/db/schema.rb +11 -6
- data/test/dummy/user.rb +6 -0
- data/test/squint_test.rb +43 -13
- data/test/test_helper.rb +5 -1
- metadata +33 -26
- data/test/dummy/app/models/post.rb~ +0 -2
- data/test/dummy/config/database.yml~ +0 -25
- data/test/dummy/db/migrate/20170512185941_create_posts.rb~ +0 -12
- data/test/dummy/log/development.log +0 -351
- data/test/dummy/log/test.log +0 -29428
- data/test/dummy/test/fixtures/posts.yml~ +0 -13
- data/test/dummy/test/models/post_test.rb +0 -4
- data/test/dummy/test/models/post_test.rb~ +0 -17
- data/test/test_helper.rb~ +0 -20
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
|
-
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: f1a001a14acd2fc3e2ca16adbb14999e7c0955f7588f7bf2b24ea6b459020cd7
|
4
|
+
data.tar.gz: 68a54fa15e06555a8c0320aadac4a9578fbb134f2abb34a4908d9a4f2c034d98
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f9fcb29b013d12a51aaff4dfed8674a76471ff5eb9e6d92727eeae7ff585c51dc4edb0ef53cb4fdefcf7b6b059391e5bf950c03d659b26d0057c6c32b4567c97
|
7
|
+
data.tar.gz: 86cbf3a386e89e2d0d2da91fe585c94dc0d53eeaad4fdfda75ccff26dc14147056500a369fa8380f1fc4e85b941d92a86d61d9b74a79b0353ea551a5b76e4f75
|
data/.all-contributorsrc
ADDED
@@ -0,0 +1,108 @@
|
|
1
|
+
{
|
2
|
+
"projectName": "squint",
|
3
|
+
"projectOwner": "ProctorU",
|
4
|
+
"files": [
|
5
|
+
"readme.md"
|
6
|
+
],
|
7
|
+
"imageSize": 100,
|
8
|
+
"commit": true,
|
9
|
+
"contributors": [
|
10
|
+
{
|
11
|
+
"login": "chevinbrown",
|
12
|
+
"name": "Kevin Brown",
|
13
|
+
"avatar_url": "https://avatars2.githubusercontent.com/u/864581?v=3",
|
14
|
+
"profile": "https://github.com/chevinbrown",
|
15
|
+
"contributions": [
|
16
|
+
"design",
|
17
|
+
"review"
|
18
|
+
]
|
19
|
+
},
|
20
|
+
{
|
21
|
+
"login": "king601",
|
22
|
+
"name": "Andrew Fomera",
|
23
|
+
"avatar_url": "https://avatars2.githubusercontent.com/u/1741179?v=3",
|
24
|
+
"profile": "http://andrewfomera.com",
|
25
|
+
"contributions": [
|
26
|
+
"review",
|
27
|
+
"code"
|
28
|
+
]
|
29
|
+
},
|
30
|
+
{
|
31
|
+
"login": "rthbound",
|
32
|
+
"name": "Ryan T. Hosford",
|
33
|
+
"avatar_url": "https://avatars2.githubusercontent.com/u/708692?v=3",
|
34
|
+
"profile": "https://github.com/rthbound",
|
35
|
+
"contributions": [
|
36
|
+
"code"
|
37
|
+
]
|
38
|
+
},
|
39
|
+
{
|
40
|
+
"login": "Jaehdawg",
|
41
|
+
"name": "Matthew Jaeh",
|
42
|
+
"avatar_url": "https://avatars2.githubusercontent.com/u/1785682?v=3",
|
43
|
+
"profile": "https://github.com/Jaehdawg",
|
44
|
+
"contributions": [
|
45
|
+
"design",
|
46
|
+
"review"
|
47
|
+
]
|
48
|
+
},
|
49
|
+
{
|
50
|
+
"login": "licatajustin",
|
51
|
+
"name": "Justin Licata",
|
52
|
+
"avatar_url": "https://avatars0.githubusercontent.com/u/3933204?v=3",
|
53
|
+
"profile": "https://twitter.com/justinlicata",
|
54
|
+
"contributions": [
|
55
|
+
"code",
|
56
|
+
"design",
|
57
|
+
"doc",
|
58
|
+
"review"
|
59
|
+
]
|
60
|
+
},
|
61
|
+
{
|
62
|
+
"login": "kmiracle86",
|
63
|
+
"name": "Kyle Miracle",
|
64
|
+
"avatar_url": "https://avatars3.githubusercontent.com/u/24704300?v=4",
|
65
|
+
"profile": "https://github.com/kmiracle86",
|
66
|
+
"contributions": [
|
67
|
+
"bug",
|
68
|
+
"review"
|
69
|
+
]
|
70
|
+
},
|
71
|
+
{
|
72
|
+
"login": "dwilkins",
|
73
|
+
"name": "David H. Wilkins",
|
74
|
+
"avatar_url": "https://avatars2.githubusercontent.com/u/97011?v=3",
|
75
|
+
"profile": "http://conecuh.com",
|
76
|
+
"contributions": [
|
77
|
+
"question",
|
78
|
+
"bug",
|
79
|
+
"code",
|
80
|
+
"design",
|
81
|
+
"doc",
|
82
|
+
"example",
|
83
|
+
"review",
|
84
|
+
"test"
|
85
|
+
]
|
86
|
+
},
|
87
|
+
{
|
88
|
+
"login": "TheJayWright",
|
89
|
+
"name": "Jay Wright",
|
90
|
+
"avatar_url": "https://avatars3.githubusercontent.com/u/19173815?v=3",
|
91
|
+
"profile": "https://github.com/TheJayWright",
|
92
|
+
"contributions": [
|
93
|
+
"review"
|
94
|
+
]
|
95
|
+
},
|
96
|
+
{
|
97
|
+
"login": "jamescook",
|
98
|
+
"name": "James Cook",
|
99
|
+
"avatar_url": "https://avatars1.githubusercontent.com/u/4067?s=460&u=cb404cc0f1737c2fc53411e300cc8e158ef29295&v=4",
|
100
|
+
"profile": "https://github.com/jamescook",
|
101
|
+
"contributions": [
|
102
|
+
"code",
|
103
|
+
"test",
|
104
|
+
"review"
|
105
|
+
]
|
106
|
+
}
|
107
|
+
]
|
108
|
+
}
|
data/lib/squint.rb
CHANGED
@@ -3,50 +3,95 @@ require 'active_support/concern'
|
|
3
3
|
# Squint json, jsonb, hstore queries
|
4
4
|
module Squint
|
5
5
|
extend ActiveSupport::Concern
|
6
|
-
|
6
|
+
if ActiveRecord::VERSION::STRING < '5'
|
7
|
+
include ::ActiveRecord::QueryMethods
|
8
|
+
end
|
7
9
|
|
8
10
|
module WhereMethods
|
9
|
-
# Args may be passed to build_where like:
|
11
|
+
# Args may be passed to build/build_where like:
|
10
12
|
# build_where(jsonb_column: {key1: value1})
|
11
13
|
# build_where(jsonb_column: {key1: value1}, jsonb_column: {key2: value2})
|
12
14
|
# build_where(jsonb_column: {key1: value1}, regular_column: value)
|
13
15
|
# build_where(jsonb_column: {key1: value1}, association: {column: value))
|
14
|
-
|
16
|
+
if ActiveRecord::VERSION::STRING > '5'
|
17
|
+
method_name = :build
|
18
|
+
elsif ActiveRecord::VERSION::STRING < '5'
|
19
|
+
method_name = :build_where
|
20
|
+
end
|
21
|
+
send :define_method, method_name do |*args|
|
22
|
+
# For Rails 5, we end up monkey patching WhereClauseFactory for everyone
|
23
|
+
# so need to return super if our methods aren't on the AR class
|
24
|
+
# doesn't hurt for 4.2.x either
|
25
|
+
return super(*args) unless klass.respond_to?(:squint_hash_field_reln)
|
15
26
|
save_args = []
|
16
27
|
reln = args.inject([]) do |memo, arg|
|
17
|
-
if
|
18
|
-
save_args << arg
|
19
|
-
memo += super(save_args)
|
20
|
-
save_args = []
|
21
|
-
elsif arg.is_a?(Hash)
|
28
|
+
if arg.is_a?(Hash)
|
22
29
|
arg.keys.each do |key|
|
23
|
-
if arg[key].is_a?(Hash) && HASH_DATA_COLUMNS[key]
|
24
|
-
memo <<
|
30
|
+
if arg[key].is_a?(Hash) && klass::HASH_DATA_COLUMNS[key]
|
31
|
+
memo << klass.squint_hash_field_reln(key => arg[key])
|
25
32
|
else
|
26
|
-
|
33
|
+
save_args[0] ||= {}
|
34
|
+
save_args[0][key] = arg[key]
|
27
35
|
end
|
28
36
|
end
|
29
37
|
elsif arg.present?
|
30
|
-
|
31
|
-
save_args << arg
|
32
|
-
else
|
33
|
-
memo += super(arg)
|
34
|
-
end
|
38
|
+
save_args << arg
|
35
39
|
end
|
36
40
|
memo
|
37
41
|
end
|
38
|
-
|
42
|
+
if ActiveRecord::VERSION::STRING >= '5.2'
|
43
|
+
# In 5.2 the WhereClause private class no longer takes two arguments.
|
44
|
+
# Commit where it was removed:
|
45
|
+
# https://github.com/rails/rails/commit/213796fb4936dce1da2f0c097a054e1af5c25c2c#diff-c9d167bac00ff2f45c5b5e035e8a80e8
|
46
|
+
reln = ActiveRecord::Relation::WhereClause.new(reln)
|
47
|
+
elsif ActiveRecord::VERSION::STRING > '5'
|
48
|
+
reln = ActiveRecord::Relation::WhereClause.new(reln, [])
|
49
|
+
end
|
50
|
+
save_args << [] if save_args.size == 1
|
51
|
+
reln += super(*save_args) unless save_args.empty?
|
39
52
|
reln
|
40
53
|
end
|
54
|
+
end
|
55
|
+
|
56
|
+
included do |base|
|
57
|
+
if ActiveRecord::VERSION::STRING < '5'
|
58
|
+
ar_reln_module = base::ActiveRecord_Relation
|
59
|
+
ar_association_module = base::ActiveRecord_AssociationRelation
|
60
|
+
elsif ActiveRecord::VERSION::STRING > '5.1'
|
61
|
+
# ActiveRecord_Relation is now a private_constant in 5.1.x
|
62
|
+
ar_reln_module = base.relation_delegate_class(ActiveRecord::Relation)::WhereClauseFactory
|
63
|
+
ar_association_module = nil
|
64
|
+
elsif ActiveRecord::VERSION::STRING > '5.0'
|
65
|
+
ar_reln_module = base::ActiveRecord_Relation::WhereClauseFactory
|
66
|
+
ar_association_module = nil
|
67
|
+
# ar_association_module = base::ActiveRecord_AssociationRelation
|
68
|
+
end
|
69
|
+
|
70
|
+
# put together a list of columns in this model
|
71
|
+
# that are hstore, json, or jsonb and will benefit from
|
72
|
+
# searchability
|
73
|
+
base::HASH_DATA_COLUMNS = base.columns_hash.keys.map do |col_name|
|
74
|
+
if %w[hstore json jsonb].include?(base.columns_hash[col_name].sql_type)
|
75
|
+
[col_name.to_sym, base.columns_hash[col_name].sql_type]
|
76
|
+
end
|
77
|
+
end.compact.to_h
|
78
|
+
|
79
|
+
ar_reln_module.class_eval do
|
80
|
+
prepend WhereMethods
|
81
|
+
end
|
41
82
|
|
42
|
-
|
83
|
+
ar_association_module.try(:class_eval) do
|
84
|
+
prepend WhereMethods
|
85
|
+
end
|
86
|
+
|
87
|
+
# squint_hash_field_reln
|
43
88
|
# return an Arel object with the appropriate query
|
44
89
|
# Strings want to be a SQL Literal, other things can be
|
45
90
|
# passed in bare to the eq or in operator
|
46
|
-
def
|
91
|
+
def self.squint_hash_field_reln(*args)
|
47
92
|
temp_attr = args[0]
|
48
93
|
contains_nil = false
|
49
|
-
column_type = HASH_DATA_COLUMNS[args[0].keys.first]
|
94
|
+
column_type = self::HASH_DATA_COLUMNS[args[0].keys.first]
|
50
95
|
column_name_segments = []
|
51
96
|
quote_char = '"'.freeze
|
52
97
|
while temp_attr.is_a?(Hash)
|
@@ -108,109 +153,84 @@ module Squint
|
|
108
153
|
# specified as a query value
|
109
154
|
if check_attr_missing
|
110
155
|
reln = if column_type == 'hstore'.freeze
|
111
|
-
|
156
|
+
squint_hstore_element_missing(column_name_segments, reln)
|
112
157
|
else
|
113
|
-
|
158
|
+
squint_jsonb_element_missing(column_name_segments, reln)
|
114
159
|
end
|
115
160
|
end
|
116
161
|
reln
|
117
162
|
end
|
118
|
-
end
|
119
|
-
|
120
|
-
included do |base|
|
121
|
-
ar_reln_module = base::ActiveRecord_Relation
|
122
|
-
ar_association_module = base::ActiveRecord_AssociationRelation
|
123
163
|
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
164
|
+
def self.squint_storext_default?(temp_attr, attribute_sym)
|
165
|
+
return false unless respond_to?(:storext_definitions)
|
166
|
+
if storext_definitions.keys.include?(attribute_sym) &&
|
167
|
+
!(storext_definitions[attribute_sym][:opts] &&
|
168
|
+
storext_definitions[attribute_sym][:opts][:default]).nil? &&
|
169
|
+
[temp_attr].compact.map(&:to_s).
|
170
|
+
flatten.
|
171
|
+
include?(storext_definitions[attribute_sym][:opts][:default].to_s)
|
172
|
+
true
|
130
173
|
end
|
131
|
-
end.compact.to_h
|
132
|
-
|
133
|
-
ar_reln_module.class_eval do
|
134
|
-
prepend WhereMethods
|
135
174
|
end
|
136
175
|
|
137
|
-
|
138
|
-
|
176
|
+
def self.squint_hstore_element_exists(element, attribute_hash_column, value)
|
177
|
+
Arel::Nodes::Equality.new(
|
178
|
+
Arel::Nodes::NamedFunction.new(
|
179
|
+
"exist",
|
180
|
+
[arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
181
|
+
Arel::Nodes::SqlLiteral.new(element)]
|
182
|
+
), value
|
183
|
+
)
|
139
184
|
end
|
140
185
|
|
141
|
-
def self.
|
186
|
+
def self.squint_hstore_element_missing(column_name_segments, reln)
|
142
187
|
element = column_name_segments.pop
|
143
188
|
attribute_hash_column = column_name_segments.join('->'.freeze)
|
144
189
|
# Query generated is equals default or attribute present is null or equals false
|
145
|
-
# * Is null happens
|
190
|
+
# * Is null happens the the column is null
|
146
191
|
# * equals false is when the column has jsonb data, but the key doesn't exist
|
147
192
|
# ("posts"."storext_attributes"->>'is_awesome' = 'false' OR
|
148
|
-
# (("posts"."storext_attributes"
|
149
|
-
# ("posts"."storext_attributes"
|
193
|
+
# (exists("posts"."storext_attributes", 'is_awesome') IS NULL OR
|
194
|
+
# exists("posts"."storext_attributes", 'is_awesome') = FALSE)
|
150
195
|
# )
|
151
196
|
Arel::Nodes::Grouping.new(
|
152
197
|
reln.or(
|
153
198
|
Arel::Nodes::Grouping.new(
|
154
|
-
Arel::Nodes::
|
155
|
-
|
156
|
-
|
157
|
-
Arel::Nodes::SqlLiteral.new('?'),
|
158
|
-
arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
159
|
-
Arel::Nodes::SqlLiteral.new(element)
|
160
|
-
)
|
161
|
-
), nil
|
162
|
-
).or(
|
163
|
-
Arel::Nodes::Equality.new(
|
164
|
-
Arel::Nodes::Grouping.new(
|
165
|
-
Arel::Nodes::InfixOperation.new(
|
166
|
-
Arel::Nodes::SqlLiteral.new('?'),
|
167
|
-
arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
168
|
-
Arel::Nodes::SqlLiteral.new(element)
|
169
|
-
)
|
170
|
-
), Arel::Nodes::False.new
|
171
|
-
)
|
172
|
-
)
|
199
|
+
squint_hstore_element_exists(element, attribute_hash_column, Arel::Nodes::False.new)
|
200
|
+
).or(
|
201
|
+
squint_hstore_element_exists(element, attribute_hash_column, nil)
|
173
202
|
)
|
174
203
|
)
|
175
204
|
)
|
176
205
|
end
|
177
206
|
|
178
|
-
def self.
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
207
|
+
def self.squint_jsonb_element_equality(element, attribute_hash_column, value)
|
208
|
+
Arel::Nodes::Equality.new(
|
209
|
+
Arel::Nodes::Grouping.new(
|
210
|
+
Arel::Nodes::InfixOperation.new(
|
211
|
+
Arel::Nodes::SqlLiteral.new('?'),
|
212
|
+
arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
213
|
+
Arel::Nodes::SqlLiteral.new(element)
|
214
|
+
)
|
215
|
+
), value
|
216
|
+
)
|
187
217
|
end
|
188
218
|
|
189
|
-
def self.
|
219
|
+
def self.squint_jsonb_element_missing(column_name_segments, reln)
|
190
220
|
element = column_name_segments.pop
|
191
221
|
attribute_hash_column = column_name_segments.join('->'.freeze)
|
192
222
|
# Query generated is equals default or attribute present is null or equals false
|
193
|
-
# * Is null happens the the column is null
|
223
|
+
# * Is null happens when the the whole column is null
|
194
224
|
# * equals false is when the column has jsonb data, but the key doesn't exist
|
195
225
|
# ("posts"."storext_attributes"->>'is_awesome' = 'false' OR
|
196
|
-
# (
|
197
|
-
#
|
226
|
+
# (("posts"."storext_attributes" ? 'is_awesome') IS NULL OR
|
227
|
+
# ("posts"."storext_attributes" ? 'is_awesome') = FALSE)
|
198
228
|
# )
|
199
229
|
Arel::Nodes::Grouping.new(
|
200
230
|
reln.or(
|
201
231
|
Arel::Nodes::Grouping.new(
|
202
|
-
|
203
|
-
|
204
|
-
[arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
205
|
-
Arel::Nodes::SqlLiteral.new(element)]
|
206
|
-
).eq(Arel::Nodes::False.new)
|
207
|
-
).or(
|
208
|
-
Arel::Nodes::Equality.new(
|
209
|
-
Arel::Nodes::NamedFunction.new(
|
210
|
-
"exist",
|
211
|
-
[arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
212
|
-
Arel::Nodes::SqlLiteral.new(element)]
|
213
|
-
), nil
|
232
|
+
squint_jsonb_element_equality(element, attribute_hash_column, nil).or(
|
233
|
+
squint_jsonb_element_equality(element, attribute_hash_column, Arel::Nodes::False.new)
|
214
234
|
)
|
215
235
|
)
|
216
236
|
)
|
data/lib/squint/version.rb
CHANGED
data/readme.md
ADDED
@@ -0,0 +1,186 @@
|
|
1
|
+
<p align="center">
|
2
|
+
<a href="https://twitter.com/ProctorUEng">
|
3
|
+
<img src="https://s3-us-west-2.amazonaws.com/dev-team-resources/squint-wordmark.svg" width=198 height=72>
|
4
|
+
</a>
|
5
|
+
|
6
|
+
<p align="center">
|
7
|
+
Search PostgreSQL <code>jsonb</code> and <code>hstore</code> columns.
|
8
|
+
</p>
|
9
|
+
</p>
|
10
|
+
|
11
|
+
<br>
|
12
|
+
|
13
|
+
> Full database searching inside columns containing semi-structured data like `json`,
|
14
|
+
`jsonb` and `hstore`. <strong>Compatible with the awesome
|
15
|
+
<a href="https://github.com/G5/storext">storext</a> gem</strong>.
|
16
|
+
|
17
|
+
## Table of contents
|
18
|
+
|
19
|
+
- [Status](#status)
|
20
|
+
- [Quick start](#quick-start)
|
21
|
+
- [Performance](#performance)
|
22
|
+
- [Storext attributes](#storext-attributes)
|
23
|
+
- [Developing](#developing)
|
24
|
+
- [Contributors](#contributors)
|
25
|
+
- [Credits](#credits)
|
26
|
+
|
27
|
+
## Status
|
28
|
+
[![All Contributors](https://img.shields.io/badge/all_contributors-9-orange.svg?style=flat-square)](#contributors)
|
29
|
+
[![CircleCI](https://circleci.com/gh/ProctorU/squint.svg?style=svg)](https://circleci.com/gh/ProctorU/squint)
|
30
|
+
|
31
|
+
## Quick Start
|
32
|
+
|
33
|
+
Add to your Gemfile:
|
34
|
+
|
35
|
+
```ruby
|
36
|
+
gem 'squint'
|
37
|
+
```
|
38
|
+
|
39
|
+
Include it in your models:
|
40
|
+
|
41
|
+
```ruby
|
42
|
+
class Post < ActiveRecord::Base
|
43
|
+
include Squint
|
44
|
+
# ...
|
45
|
+
end
|
46
|
+
```
|
47
|
+
|
48
|
+
Assuming a table with the following structure:
|
49
|
+
```
|
50
|
+
Table "public.posts"
|
51
|
+
Column | Type | Modifiers
|
52
|
+
---------------------------+-----------------------------+----------------------------------------------------
|
53
|
+
id | integer | not null default nextval('posts_id_seq'::regclass)
|
54
|
+
title | character varying |
|
55
|
+
body | character varying |
|
56
|
+
request_info | jsonb |
|
57
|
+
properties | hstore |
|
58
|
+
storext_jsonb_attributes | jsonb |
|
59
|
+
storext_hstore_attributes | jsonb |
|
60
|
+
created_at | timestamp without time zone | not null
|
61
|
+
updated_at | timestamp without time zone | not null
|
62
|
+
Indexes:
|
63
|
+
"posts_pkey" PRIMARY KEY, btree (id)
|
64
|
+
```
|
65
|
+
|
66
|
+
In your code use queries like:
|
67
|
+
```ruby
|
68
|
+
Post.where(properties: { referer: 'http://example.com/one' } )
|
69
|
+
# SELECT "posts".* FROM "posts" WHERE "posts"."properties"->'referer' = 'http://example.com/one'
|
70
|
+
|
71
|
+
Post.where(properties: { referer: nil } )
|
72
|
+
# SELECT "posts".* FROM "posts" WHERE "posts"."properties"->'referer' IS NULL
|
73
|
+
|
74
|
+
Post.where(properties: { referer: ['http://example.com/one',nil] } )
|
75
|
+
# SELECT "posts".* FROM "posts" WHERE ("posts"."properties"->'referer' = 'http://example.com/one'
|
76
|
+
# OR "posts"."properties"->'referer' IS NULL)
|
77
|
+
|
78
|
+
Post.where(request_info: { referer: ['http://example.com/one',nil] } )
|
79
|
+
# SELECT "posts".* FROM "posts" WHERE ("posts"."request_info"->>'referer' = 'http://example.com/one'
|
80
|
+
# OR "posts"."request_info"->>'referer' IS NULL)
|
81
|
+
```
|
82
|
+
|
83
|
+
Squint only operates on json, jsonb and hstore columns. ActiveRecord
|
84
|
+
will throw a StatementInvalid exception like always if the column type is unsupported by
|
85
|
+
Squint.
|
86
|
+
|
87
|
+
```ruby
|
88
|
+
Post.where(title: { not_there: "any value will do" } )
|
89
|
+
```
|
90
|
+
|
91
|
+
```
|
92
|
+
ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "title"
|
93
|
+
LINE 1: SELECT COUNT(*) FROM "posts" WHERE "title"."not_there" = 'an...
|
94
|
+
^
|
95
|
+
: SELECT COUNT(*) FROM "posts" WHERE "title"."not_there" = 'any value will do'
|
96
|
+
```
|
97
|
+
|
98
|
+
## Performance
|
99
|
+
To get the most performance out searching jsonb/hstore attributes, add a GIN (preferred) or
|
100
|
+
GIST index to those columns. Find out more
|
101
|
+
[here](https://www.postgresql.org/docs/9.5/static/textsearch-indexes.html)
|
102
|
+
|
103
|
+
TL;DR:
|
104
|
+
|
105
|
+
SQL: 'CREATE INDEX name ON table USING GIN (column);'
|
106
|
+
|
107
|
+
Rails Migration: `add_index(:table, :column_name, using: 'gin')`
|
108
|
+
|
109
|
+
|
110
|
+
## Storext attributes
|
111
|
+
Assuming the database schema above and a model like so:
|
112
|
+
```ruby
|
113
|
+
class Post < ActiveRecord::Base
|
114
|
+
include Storext.model
|
115
|
+
include Squint
|
116
|
+
|
117
|
+
store_attribute :storext_jsonb_attributes, :zip_code, String, default: '90210'
|
118
|
+
store_attribute :storext_jsonb_attributes, :friend_count, Integer, default: 0
|
119
|
+
end
|
120
|
+
```
|
121
|
+
|
122
|
+
Example using StoreXT with a default value:
|
123
|
+
```ruby
|
124
|
+
Post.where(storext_jsonb_attributes: { zip_code: '90210' } )
|
125
|
+
# -- jsonb
|
126
|
+
# SELECT "posts".* FROM "posts" WHERE ("posts"."storext_jsonb_attributes"->>'zip_code' = '90210' OR
|
127
|
+
# (("posts"."storext_jsonb_attributes" ? 'zip_code') IS NULL OR
|
128
|
+
# ("posts"."storext_jsonb_attributes" ? 'zip_code') = FALSE))
|
129
|
+
# -- hstore
|
130
|
+
# SELECT "posts".* FROM "posts" WHERE ("posts"."storext_hstore_attributes"->'zip_code' = '90210' OR
|
131
|
+
# ((exist("posts"."storext_hstore_attributes", 'zip_code') = FALSE) OR
|
132
|
+
# exist("posts"."storext_hstore_attributes", 'zip_code') IS NULL))
|
133
|
+
#
|
134
|
+
#
|
135
|
+
```
|
136
|
+
If (as in the example above) the default value for the StoreXT attribute is specified, then extra
|
137
|
+
checks for missing column ( `("posts"."storext_jsonb_attributes" ? 'zip_code') IS NULL` ) or
|
138
|
+
missing key ( `("posts"."storext_jsonb_attributes" ? 'zip_code') = FALSE)` ) are added
|
139
|
+
|
140
|
+
When non-default storext values are specified, these extra checks won't be added.
|
141
|
+
|
142
|
+
The Postgres SQL for jsonb and hstore is different. No support for checking for missing `json`
|
143
|
+
columns exists, so don't use those with StoreXT + Squint
|
144
|
+
|
145
|
+
## Developing
|
146
|
+
|
147
|
+
1. Thank you!
|
148
|
+
1. Clone the repository
|
149
|
+
1. `bundle`
|
150
|
+
1. `bundle exec rake --rakefile test/dummy/Rakefile db:setup` # create the db for tests
|
151
|
+
1. `bundle exec rake` # run the tests
|
152
|
+
1. make your changes in a thoughtfully named branch
|
153
|
+
1. ensure good test coverage
|
154
|
+
1. submit a Pull Request
|
155
|
+
|
156
|
+
## Contributors
|
157
|
+
|
158
|
+
Thanks goes to these wonderful people ([emoji key](https://github.com/kentcdodds/all-contributors#emoji-key)):
|
159
|
+
|
160
|
+
<!-- ALL-CONTRIBUTORS-LIST:START - Do not remove or modify this section -->
|
161
|
+
| [<img src="https://avatars2.githubusercontent.com/u/864581?v=3" width="100px;"/><br /><sub>Kevin Brown</sub>](https://github.com/chevinbrown)<br />[🎨](#design-chevinbrown "Design") [👀](#review-chevinbrown "Reviewed Pull Requests") | [<img src="https://avatars2.githubusercontent.com/u/1741179?v=3" width="100px;"/><br /><sub>Andrew Fomera</sub>](http://andrewfomera.com)<br />[👀](#review-king601 "Reviewed Pull Requests") [💻](https://github.com/ProctorU/squint/commits?author=king601 "Code") | [<img src="https://avatars2.githubusercontent.com/u/708692?v=3" width="100px;"/><br /><sub>Ryan T. Hosford</sub>](https://github.com/rthbound)<br />[💻](https://github.com/ProctorU/squint/commits?author=rthbound "Code") | [<img src="https://avatars2.githubusercontent.com/u/1785682?v=3" width="100px;"/><br /><sub>Matthew Jaeh</sub>](https://github.com/Jaehdawg)<br />[🎨](#design-Jaehdawg "Design") [👀](#review-Jaehdawg "Reviewed Pull Requests") | [<img src="https://avatars0.githubusercontent.com/u/3933204?v=3" width="100px;"/><br /><sub>Justin Licata</sub>](https://twitter.com/justinlicata)<br />[💻](https://github.com/ProctorU/squint/commits?author=licatajustin "Code") [🎨](#design-licatajustin "Design") [📖](https://github.com/ProctorU/squint/commits?author=licatajustin "Documentation") [👀](#review-licatajustin "Reviewed Pull Requests") | [<img src="https://avatars3.githubusercontent.com/u/24704300?v=4" width="100px;"/><br /><sub>Kyle Miracle</sub>](https://github.com/kmiracle86)<br />[🐛](https://github.com/ProctorU/squint/issues?q=author%3Akmiracle86 "Bug reports") [👀](#review-kmiracle86 "Reviewed Pull Requests") | [<img src="https://avatars2.githubusercontent.com/u/97011?v=3" width="100px;"/><br /><sub>David H. Wilkins</sub>](http://conecuh.com)<br />[💬](#question-dwilkins "Answering Questions") [🐛](https://github.com/ProctorU/squint/issues?q=author%3Adwilkins "Bug reports") [💻](https://github.com/ProctorU/squint/commits?author=dwilkins "Code") [🎨](#design-dwilkins "Design") [📖](https://github.com/ProctorU/squint/commits?author=dwilkins "Documentation") [💡](#example-dwilkins "Examples") [👀](#review-dwilkins "Reviewed Pull Requests") [⚠️](https://github.com/ProctorU/squint/commits?author=dwilkins "Tests") |
|
162
|
+
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
|
163
|
+
| [<img src="https://avatars3.githubusercontent.com/u/19173815?v=3" width="100px;"/><br /><sub>Jay Wright</sub>](https://github.com/TheJayWright)<br />[👀](#review-TheJayWright "Reviewed Pull Requests") | [<img src="https://avatars1.githubusercontent.com/u/4067?s=460&u=cb404cc0f1737c2fc53411e300cc8e158ef29295&v=4" width="100px;"/><br /><sub>James Cook</sub>](https://github.com/jamescook)<br />[💻](https://github.com/ProctorU/squint/commits?author=jamescook "Code") [⚠️](https://github.com/ProctorU/squint/commits?author=jamescook "Tests") [👀](#review-jamescook "Reviewed Pull Requests") |
|
164
|
+
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
165
|
+
|
166
|
+
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
167
|
+
|
168
|
+
## Credits
|
169
|
+
|
170
|
+
Squint is maintained and funded by [ProctorU](https://twitter.com/ProctorUEng).
|
171
|
+
|
172
|
+
<br>
|
173
|
+
|
174
|
+
<p align="center">
|
175
|
+
<a href="https://twitter.com/ProctorUEng">
|
176
|
+
<img src="https://s3-us-west-2.amazonaws.com/dev-team-resources/procki-eyes.svg" width=108 height=72>
|
177
|
+
</a>
|
178
|
+
|
179
|
+
<h3 align="center">
|
180
|
+
<a href="https://twitter.com/ProctorUEng">ProctorU Engineering & Design</a>
|
181
|
+
</h3>
|
182
|
+
|
183
|
+
<p align="center">
|
184
|
+
A simple online proctoring service that allows you to take exams or certification tests at home.
|
185
|
+
</p>
|
186
|
+
</p>
|