squint 0.0.2 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](#contributors)
|
29
|
+
[](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>
|