squint 0.0.3 → 1.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 +4 -4
- data/.all-contributorsrc +86 -0
- data/lib/squint.rb +96 -74
- data/lib/squint/version.rb +1 -1
- data/readme.md +185 -0
- data/test/dummy/config/application.rb +3 -1
- data/test/dummy/config/environments/test.rb +7 -2
- data/test/dummy/log/development.log +39 -0
- data/test/dummy/log/test.log +62346 -0
- data/test/reports/TEST-SquintTest.xml +57 -0
- data/test/squint_test.rb +18 -14
- data/test/test_helper.rb +5 -1
- metadata +23 -7
- data/test/dummy/test/models/post_test.rb +0 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: df371ee0e87ea3faac9e9fc9a41b348f0ff30424
|
4
|
+
data.tar.gz: d44f014f0e8fa11d00f23bf19a6fa29d4954b035
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: b8459d887335bb272cbd9d0fb8e31f012d2c573e710c3dc2b2a4d6e07057187171c7dbc8e9fbd873cec58741199aae5804861698c9eba2bdfaf5fd6ae6e3f69b
|
7
|
+
data.tar.gz: d000d6ebde399bb8edaead6c27e43aeb78575b81072eb9d0f0aa96e860cfb61bf45d0e169a1830bf6f7428e4c0511cd4798ac1ae900f882a636276d3a35a2345
|
data/.all-contributorsrc
ADDED
@@ -0,0 +1,86 @@
|
|
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
|
+
]
|
28
|
+
},
|
29
|
+
{
|
30
|
+
"login": "Jaehdawg",
|
31
|
+
"name": "Matthew Jaeh",
|
32
|
+
"avatar_url": "https://avatars2.githubusercontent.com/u/1785682?v=3",
|
33
|
+
"profile": "https://github.com/Jaehdawg",
|
34
|
+
"contributions": [
|
35
|
+
"design",
|
36
|
+
"review"
|
37
|
+
]
|
38
|
+
},
|
39
|
+
{
|
40
|
+
"login": "rthbound",
|
41
|
+
"name": "Ryan T. Hosford",
|
42
|
+
"avatar_url": "https://avatars2.githubusercontent.com/u/708692?v=3",
|
43
|
+
"profile": "https://github.com/rthbound",
|
44
|
+
"contributions": [
|
45
|
+
"code"
|
46
|
+
]
|
47
|
+
},
|
48
|
+
{
|
49
|
+
"login": "licatajustin",
|
50
|
+
"name": "Justin Licata",
|
51
|
+
"avatar_url": "https://avatars0.githubusercontent.com/u/3933204?v=3",
|
52
|
+
"profile": "https://twitter.com/justinlicata",
|
53
|
+
"contributions": [
|
54
|
+
"code",
|
55
|
+
"design",
|
56
|
+
"doc",
|
57
|
+
"review"
|
58
|
+
]
|
59
|
+
},
|
60
|
+
{
|
61
|
+
"login": "dwilkins",
|
62
|
+
"name": "David H. Wilkins",
|
63
|
+
"avatar_url": "https://avatars2.githubusercontent.com/u/97011?v=3",
|
64
|
+
"profile": "http://conecuh.com",
|
65
|
+
"contributions": [
|
66
|
+
"question",
|
67
|
+
"bug",
|
68
|
+
"code",
|
69
|
+
"design",
|
70
|
+
"doc",
|
71
|
+
"example",
|
72
|
+
"review",
|
73
|
+
"test"
|
74
|
+
]
|
75
|
+
},
|
76
|
+
{
|
77
|
+
"login": "TheJayWright",
|
78
|
+
"name": "Jay Wright",
|
79
|
+
"avatar_url": "https://avatars3.githubusercontent.com/u/19173815?v=3",
|
80
|
+
"profile": "https://github.com/TheJayWright",
|
81
|
+
"contributions": [
|
82
|
+
"review"
|
83
|
+
]
|
84
|
+
}
|
85
|
+
]
|
86
|
+
}
|
data/lib/squint.rb
CHANGED
@@ -3,23 +3,34 @@ 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
28
|
if arg.is_a?(Hash)
|
18
29
|
arg.keys.each do |key|
|
19
30
|
if arg[key].is_a?(Hash) && HASH_DATA_COLUMNS[key]
|
20
|
-
memo <<
|
31
|
+
memo << klass.squint_hash_field_reln(key => arg[key])
|
21
32
|
else
|
22
|
-
|
33
|
+
save_args << { key => arg[key] }
|
23
34
|
end
|
24
35
|
end
|
25
36
|
elsif arg.present?
|
@@ -27,15 +38,51 @@ module Squint
|
|
27
38
|
end
|
28
39
|
memo
|
29
40
|
end
|
41
|
+
if ActiveRecord::VERSION::STRING > '5'
|
42
|
+
reln = ActiveRecord::Relation::WhereClause.new(reln, [])
|
43
|
+
save_args << [] if save_args.size == 1
|
44
|
+
end
|
30
45
|
reln += super(*save_args) unless save_args.empty?
|
31
46
|
reln
|
32
47
|
end
|
48
|
+
end
|
49
|
+
|
50
|
+
included do |base|
|
51
|
+
if ActiveRecord::VERSION::STRING < '5'
|
52
|
+
ar_reln_module = base::ActiveRecord_Relation
|
53
|
+
ar_association_module = base::ActiveRecord_AssociationRelation
|
54
|
+
elsif ActiveRecord::VERSION::STRING > '5.1'
|
55
|
+
# ActiveRecord_Relation is now a private_constant in 5.1.x
|
56
|
+
ar_reln_module = base.relation_delegate_class(ActiveRecord::Relation)::WhereClauseFactory
|
57
|
+
ar_association_module = nil
|
58
|
+
elsif ActiveRecord::VERSION::STRING > '5.0'
|
59
|
+
ar_reln_module = base::ActiveRecord_Relation::WhereClauseFactory
|
60
|
+
ar_association_module = nil
|
61
|
+
# ar_association_module = base::ActiveRecord_AssociationRelation
|
62
|
+
end
|
63
|
+
|
64
|
+
# put together a list of columns in this model
|
65
|
+
# that are hstore, json, or jsonb and will benefit from
|
66
|
+
# searchability
|
67
|
+
HASH_DATA_COLUMNS = base.columns_hash.keys.map do |col_name|
|
68
|
+
if %w[hstore json jsonb].include?(base.columns_hash[col_name].sql_type)
|
69
|
+
[col_name.to_sym, base.columns_hash[col_name].sql_type]
|
70
|
+
end
|
71
|
+
end.compact.to_h
|
33
72
|
|
34
|
-
|
73
|
+
ar_reln_module.class_eval do
|
74
|
+
prepend WhereMethods
|
75
|
+
end
|
76
|
+
|
77
|
+
ar_association_module.try(:class_eval) do
|
78
|
+
prepend WhereMethods
|
79
|
+
end
|
80
|
+
|
81
|
+
# squint_hash_field_reln
|
35
82
|
# return an Arel object with the appropriate query
|
36
83
|
# Strings want to be a SQL Literal, other things can be
|
37
84
|
# passed in bare to the eq or in operator
|
38
|
-
def
|
85
|
+
def self.squint_hash_field_reln(*args)
|
39
86
|
temp_attr = args[0]
|
40
87
|
contains_nil = false
|
41
88
|
column_type = HASH_DATA_COLUMNS[args[0].keys.first]
|
@@ -100,109 +147,84 @@ module Squint
|
|
100
147
|
# specified as a query value
|
101
148
|
if check_attr_missing
|
102
149
|
reln = if column_type == 'hstore'.freeze
|
103
|
-
|
150
|
+
squint_hstore_element_missing(column_name_segments, reln)
|
104
151
|
else
|
105
|
-
|
152
|
+
squint_jsonb_element_missing(column_name_segments, reln)
|
106
153
|
end
|
107
154
|
end
|
108
155
|
reln
|
109
156
|
end
|
110
|
-
end
|
111
|
-
|
112
|
-
included do |base|
|
113
|
-
ar_reln_module = base::ActiveRecord_Relation
|
114
|
-
ar_association_module = base::ActiveRecord_AssociationRelation
|
115
157
|
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
158
|
+
def self.squint_storext_default?(temp_attr, attribute_sym)
|
159
|
+
return false unless respond_to?(:storext_definitions)
|
160
|
+
if storext_definitions.keys.include?(attribute_sym) &&
|
161
|
+
!(storext_definitions[attribute_sym][:opts] &&
|
162
|
+
storext_definitions[attribute_sym][:opts][:default]).nil? &&
|
163
|
+
[temp_attr].compact.map(&:to_s).
|
164
|
+
flatten.
|
165
|
+
include?(storext_definitions[attribute_sym][:opts][:default].to_s)
|
166
|
+
true
|
122
167
|
end
|
123
|
-
end.compact.to_h
|
124
|
-
|
125
|
-
ar_reln_module.class_eval do
|
126
|
-
prepend WhereMethods
|
127
168
|
end
|
128
169
|
|
129
|
-
|
130
|
-
|
170
|
+
def self.squint_hstore_element_exists(element, attribute_hash_column, value)
|
171
|
+
Arel::Nodes::Equality.new(
|
172
|
+
Arel::Nodes::NamedFunction.new(
|
173
|
+
"exist",
|
174
|
+
[arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
175
|
+
Arel::Nodes::SqlLiteral.new(element)]
|
176
|
+
), value
|
177
|
+
)
|
131
178
|
end
|
132
179
|
|
133
|
-
def self.
|
180
|
+
def self.squint_hstore_element_missing(column_name_segments, reln)
|
134
181
|
element = column_name_segments.pop
|
135
182
|
attribute_hash_column = column_name_segments.join('->'.freeze)
|
136
183
|
# Query generated is equals default or attribute present is null or equals false
|
137
|
-
# * Is null happens
|
184
|
+
# * Is null happens the the column is null
|
138
185
|
# * equals false is when the column has jsonb data, but the key doesn't exist
|
139
186
|
# ("posts"."storext_attributes"->>'is_awesome' = 'false' OR
|
140
|
-
# (("posts"."storext_attributes"
|
141
|
-
# ("posts"."storext_attributes"
|
187
|
+
# (exists("posts"."storext_attributes", 'is_awesome') IS NULL OR
|
188
|
+
# exists("posts"."storext_attributes", 'is_awesome') = FALSE)
|
142
189
|
# )
|
143
190
|
Arel::Nodes::Grouping.new(
|
144
191
|
reln.or(
|
145
192
|
Arel::Nodes::Grouping.new(
|
146
|
-
Arel::Nodes::
|
147
|
-
|
148
|
-
|
149
|
-
Arel::Nodes::SqlLiteral.new('?'),
|
150
|
-
arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
151
|
-
Arel::Nodes::SqlLiteral.new(element)
|
152
|
-
)
|
153
|
-
), nil
|
154
|
-
).or(
|
155
|
-
Arel::Nodes::Equality.new(
|
156
|
-
Arel::Nodes::Grouping.new(
|
157
|
-
Arel::Nodes::InfixOperation.new(
|
158
|
-
Arel::Nodes::SqlLiteral.new('?'),
|
159
|
-
arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
160
|
-
Arel::Nodes::SqlLiteral.new(element)
|
161
|
-
)
|
162
|
-
), Arel::Nodes::False.new
|
163
|
-
)
|
164
|
-
)
|
193
|
+
squint_hstore_element_exists(element, attribute_hash_column, Arel::Nodes::False.new)
|
194
|
+
).or(
|
195
|
+
squint_hstore_element_exists(element, attribute_hash_column, nil)
|
165
196
|
)
|
166
197
|
)
|
167
198
|
)
|
168
199
|
end
|
169
200
|
|
170
|
-
def self.
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
201
|
+
def self.squint_jsonb_element_equality(element, attribute_hash_column, value)
|
202
|
+
Arel::Nodes::Equality.new(
|
203
|
+
Arel::Nodes::Grouping.new(
|
204
|
+
Arel::Nodes::InfixOperation.new(
|
205
|
+
Arel::Nodes::SqlLiteral.new('?'),
|
206
|
+
arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
207
|
+
Arel::Nodes::SqlLiteral.new(element)
|
208
|
+
)
|
209
|
+
), value
|
210
|
+
)
|
179
211
|
end
|
180
212
|
|
181
|
-
def self.
|
213
|
+
def self.squint_jsonb_element_missing(column_name_segments, reln)
|
182
214
|
element = column_name_segments.pop
|
183
215
|
attribute_hash_column = column_name_segments.join('->'.freeze)
|
184
216
|
# Query generated is equals default or attribute present is null or equals false
|
185
|
-
# * Is null happens the the column is null
|
217
|
+
# * Is null happens when the the whole column is null
|
186
218
|
# * equals false is when the column has jsonb data, but the key doesn't exist
|
187
219
|
# ("posts"."storext_attributes"->>'is_awesome' = 'false' OR
|
188
|
-
# (
|
189
|
-
#
|
220
|
+
# (("posts"."storext_attributes" ? 'is_awesome') IS NULL OR
|
221
|
+
# ("posts"."storext_attributes" ? 'is_awesome') = FALSE)
|
190
222
|
# )
|
191
223
|
Arel::Nodes::Grouping.new(
|
192
224
|
reln.or(
|
193
225
|
Arel::Nodes::Grouping.new(
|
194
|
-
|
195
|
-
|
196
|
-
[arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
197
|
-
Arel::Nodes::SqlLiteral.new(element)]
|
198
|
-
).eq(Arel::Nodes::False.new)
|
199
|
-
).or(
|
200
|
-
Arel::Nodes::Equality.new(
|
201
|
-
Arel::Nodes::NamedFunction.new(
|
202
|
-
"exist",
|
203
|
-
[arel_table[Arel::Nodes::SqlLiteral.new(attribute_hash_column)],
|
204
|
-
Arel::Nodes::SqlLiteral.new(element)]
|
205
|
-
), nil
|
226
|
+
squint_jsonb_element_equality(element, attribute_hash_column, nil).or(
|
227
|
+
squint_jsonb_element_equality(element, attribute_hash_column, Arel::Nodes::False.new)
|
206
228
|
)
|
207
229
|
)
|
208
230
|
)
|
data/lib/squint/version.rb
CHANGED
data/readme.md
ADDED
@@ -0,0 +1,185 @@
|
|
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") | [<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://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://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://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") | [<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") |
|
162
|
+
| :---: | :---: | :---: | :---: | :---: | :---: | :---: |
|
163
|
+
<!-- ALL-CONTRIBUTORS-LIST:END -->
|
164
|
+
|
165
|
+
This project follows the [all-contributors](https://github.com/kentcdodds/all-contributors) specification. Contributions of any kind welcome!
|
166
|
+
|
167
|
+
## Credits
|
168
|
+
|
169
|
+
Squint is maintained and funded by [ProctorU](https://twitter.com/ProctorUEng).
|
170
|
+
|
171
|
+
<br>
|
172
|
+
|
173
|
+
<p align="center">
|
174
|
+
<a href="https://twitter.com/ProctorUEng">
|
175
|
+
<img src="https://s3-us-west-2.amazonaws.com/dev-team-resources/procki-eyes.svg" width=108 height=72>
|
176
|
+
</a>
|
177
|
+
|
178
|
+
<h3 align="center">
|
179
|
+
<a href="https://twitter.com/ProctorUEng">ProctorU Engineering & Design</a>
|
180
|
+
</h3>
|
181
|
+
|
182
|
+
<p align="center">
|
183
|
+
A simple online proctoring service that allows you to take exams or certification tests at home.
|
184
|
+
</p>
|
185
|
+
</p>
|