attr_json 0.1.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 ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 72f8c96e1a2f0a885c25f6d606cf22a00b813245
4
+ data.tar.gz: 6b038484149947ae8458addd45d54eabbcff2447
5
+ SHA512:
6
+ metadata.gz: 893ca2a4eabb9457df9abeaaee9b4ac09a068bdd86eede605c76ded7dc3c1f41a39d80fb2ab1de29091808b35b31af749cbf68163ee59df7a006bd133e0c4c9d
7
+ data.tar.gz: ea4a9e617dabb6b4d5e5779e302f7fb55722588314740509b81ca9d3af64a98523ed41199219608e627b24c35b3673dc59fafaec57568cc6a431d60bfd25c392
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /spec/internal/tmp/cache
10
+ /tmp/
11
+ .byebug_history
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --require spec_helper
data/.travis.yml ADDED
@@ -0,0 +1,17 @@
1
+ #
2
+ dist: trusty
3
+ sudo: false
4
+ addons:
5
+ postgresql: '9.4'
6
+ chrome: stable
7
+ language: ruby
8
+ cache: bundler
9
+ rvm:
10
+ - 2.4
11
+ - 2.5.0
12
+ env:
13
+ - RAILS_GEM="~> 5.0.0" PG_GEM="~> 0.18"
14
+ - RAILS_GEM="~> 5.1.0"
15
+ - RAILS_GEM=">= 5.2.0.rc2,< 5.3.0"
16
+ before_install:
17
+ - gem install bundler -v 1.14.6
data/.yardopts ADDED
@@ -0,0 +1 @@
1
+ - doc_src/**/*.md --plugin activesupport-concern --markup=markdown
data/Gemfile ADDED
@@ -0,0 +1,42 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in attr_json.gemspec
4
+ gemspec
5
+
6
+ # for our integration test in a real rails env, we add em in development too,
7
+ # so we can bring up the app or a console in development to play with it.
8
+ group :test, :development do
9
+ gem 'combustion', '~> 0.9.0'
10
+ # all of rails is NOT a dependency, just activerecord.
11
+ # But we use it for integration testing with combustion. Hmm, a bit annoying
12
+ # that now our other tests can't be sure they're depending, this might not
13
+ # be the way to do it.
14
+ gem "rails", ENV["RAILS_GEM"] && ENV["RAILS_GEM"].split(",")
15
+
16
+ # Rails 5.0 won't work with pg 1.0, but that isn't actually in it's gemspec.
17
+ # So we specify a compatible PG_GEM spec when testing with rails 5.
18
+ ENV['PG_GEM'] ||= ">= 0.18.1"
19
+ gem "pg", ENV['PG_GEM']
20
+
21
+ gem "rspec-rails", "~> 3.7"
22
+ gem "simple_form", ">= 4.0"
23
+ gem 'cocoon', ">= 1.2"
24
+ gem 'jquery-rails'
25
+ gem 'capybara', "~> 3.0"
26
+ gem "chromedriver-helper"
27
+ gem "selenium-webdriver"
28
+ # rails 5.1+ includes it by default, but rails 5.0 needs it:
29
+ gem 'rails-ujs', require: false
30
+ end
31
+
32
+ if ENV['RAILS_GEM']
33
+ gem "activerecord", ENV['RAILS_GEM'].split(",")
34
+
35
+ # This shouldn't really be needed, but seems to maybe be a bundler bug,
36
+ # this makes standalone_migrations dependencies resolve properly even when our
37
+ # RAILS_REQ is for 5.2.0.rc2. If in the future you delete this and everything
38
+ # still passes, feel free to remove.
39
+ gem "railties", ENV['RAILS_GEM'].split(",")
40
+ end
41
+
42
+ gem "byebug"
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Jonathan Rochkind
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,426 @@
1
+ # AttrJson
2
+
3
+ ActiveRecord attributes stored serialized in a json column, super smooth. For Rails 5.0, 5.1, or 5.2.
4
+
5
+ Typed and cast like Active Record. Supporting [nested models](#nested), [dirty tracking](#dirty), some [querying](#querying) (with postgres [jsonb](https://www.postgresql.org/docs/9.5/static/datatype-json.html) contains), and [working smoothy with form builders](#forms).
6
+
7
+ Use your database as a typed object store via ActiveRecord, in the same models right next to ordinary ActiveRecord column-backed attributes and associations. Your json-serialized `attr_json` attributes use as much of the existing ActiveRecord architecture as we can.
8
+
9
+ [![Build Status](https://travis-ci.org/jrochkind/attr_json.svg?branch=master)](https://travis-ci.org/jrochkind/attr_json)
10
+
11
+ AttrJson is pre-1.0. The functionality that is documented here _is_ already implemented (these docs are real, not vaporware) and seems pretty solid. It may still have backwards-incompat changes before 1.0 release. Review and feedback is very welcome.
12
+
13
+ Developed for postgres, but most features should work with MySQL json columns too. Has not yet been tested.
14
+
15
+
16
+ ## Basic Use
17
+
18
+ ```ruby
19
+ # migration
20
+ class CreatMyModels < ActiveRecord::Migration[5.0]
21
+ def change
22
+ create_table :my_models do |t|
23
+ t.jsonb :json_attributes
24
+ end
25
+
26
+ # If you plan to do any querying with jsonb_contains below..
27
+ add_index :my_models, :json_attributes, using: :gin
28
+ end
29
+ end
30
+
31
+ class MyModel < ActiveRecord::Base
32
+ include AttrJson::Record
33
+
34
+ # use any ActiveModel::Type types: string, integer, decimal (BigDecimal),
35
+ # float, datetime, boolean.
36
+ attr_json :my_string, :string
37
+ attr_json :my_integer, :integer
38
+ attr_json :my_datetime, :datetime
39
+
40
+ # You can have an _array_ of those things too.
41
+ attr_json :int_array, :integer, array: true
42
+
43
+ #and/or defaults
44
+ attr_json :int_with_default, :integer, default: 100
45
+ end
46
+ ```
47
+
48
+ These attributes have type-casting behavior very much like ordinary ActiveRecord values.
49
+
50
+ ```ruby
51
+ model = MyModel.new
52
+ model.my_integer = "12"
53
+ model.my_integer # => 12
54
+ model.int_array = "12"
55
+ model.int_array # => [12]
56
+ model.my_datetime = "2016-01-01 17:45"
57
+ model.my_datetime # => a Time object representing that, just like AR would cast
58
+ ```
59
+
60
+ You can use ordinary ActiveRecord validation methods with `attr_json` attributes.
61
+
62
+ All the `attr_json` attributes are serialized to json as keys in a hash, in a database jsonb/json column. By default, in a column `json_attributes`.
63
+ If you look at `model.json_attributes`, you'll see values already cast to their ruby representations.
64
+
65
+ But one way to see something like what it's really like in the db is to
66
+ save the record and then use the standard Rails `*_before_type_cast` method.
67
+
68
+ ```ruby
69
+ model.save!
70
+ model.attr_jsons_before_type_cast
71
+ # => string containing: {"my_integer":12,"int_array":[12],"my_datetime":"2016-01-01T17:45:00.000Z"}
72
+ ```
73
+
74
+ ## Specifying db column to use
75
+
76
+ While the default is to assume you want to serialize in a column called
77
+ `json_attributes`, no worries, of course you can pick whatever named
78
+ jsonb column you like.
79
+
80
+ ```ruby
81
+ class OtherModel < ActiveRecord::Base
82
+ include AttrJson::Record
83
+
84
+ # as a default for the model
85
+ self.default_json_container_attribute = :some_other_column_name
86
+
87
+ # now this is going to serialize to column 'some_other_column_name'
88
+ attr_json :my_int, :integer
89
+
90
+ # Or on a per-attribute basis
91
+ attr_json :my_int, :integer, container_attribute: "yet_another_column_name"
92
+ end
93
+ ```
94
+
95
+ ## store key different than attribute name
96
+
97
+ You can also specify that the serialized JSON key
98
+ should be different than the attribute name with the `store_key` argument.
99
+
100
+ ```ruby
101
+ class MyModel < ActiveRecord::Base
102
+ include AttrJson::Record
103
+
104
+ attr_json :special_string, :string, store_key: "__my_string"
105
+ end
106
+
107
+ model = MyModel.new
108
+ model.special_string = "foo"
109
+ model.attr_jsons # => {"__my_string"=>"foo"}
110
+ model.save!
111
+ model.attr_jsons_before_type_cast # => string containing: {"__my_string":"foo"}
112
+ ```
113
+
114
+ You can of course combine `array`, `default`, `store_key`, and `container_attribute`
115
+ params however you like, with whatever types you like: symbols resolvable
116
+ with `ActiveModel::Type.lookup`, or any [ActiveModel::Type::Value](https://apidock.com/rails/ActiveRecord/Attributes/ClassMethods/attribute) subclass, built-in or custom.
117
+
118
+ <a name="querying"></a>
119
+ ## Querying
120
+
121
+ There is some built-in support for querying using [postgres jsonb containment](https://www.postgresql.org/docs/9.5/static/functions-json.html)
122
+ (`@>`) operator. (or see [here](https://blog.hasura.io/the-unofficial-guide-to-jsonb-operators-in-postgres-part-1-7ad830485ddf) or [here](https://hackernoon.com/how-to-query-jsonb-beginner-sheet-cheat-4da3aa5082a3)). For now you need to additionally `include AttrJson::Record::QueryScopes`
123
+ to get this behavior.
124
+
125
+ ```ruby
126
+ model = MyModel.create(my_string: "foo", my_integer: 100)
127
+
128
+ MyModel.jsonb_contains(my_string: "foo", my_integer: 100).to_sql
129
+ # SELECT "products".* FROM "products" WHERE (products.json_attributes @> ('{"my_string":"foo","my_integer":100}')::jsonb)
130
+ MyModel.jsonb_contains(my_string: "foo", my_integer: 100).first
131
+ # Implemented with scopes, this is an ordinary relation, you can
132
+ # combine it with whatever, just like ordinary `where`.
133
+
134
+ # typecasts much like ActiveRecord on query too:
135
+ MyModel.jsonb_contains(my_string: "foo", my_integer: "100")
136
+ # no problem
137
+
138
+ # works for arrays too
139
+ model = MyModel.create(int_array: [10, 20, 30])
140
+ MyModel.jsonb_contains(int_array: 10) # finds it
141
+ MyModel.jsonb_contains(int_array: [10]) # still finds it
142
+ MyModel.jsonb_contains(int_array: [10, 20]) # it contains both, so still finds it
143
+ MyModel.jsonb_contains(int_array: [10, 1000]) # nope, returns nil, has to contain ALL listed in query for array args
144
+ ```
145
+
146
+ `jsonb_contains` will handlesany `store_key` you have set -- you should specify
147
+ attribute name, it'll actually query on store_key. And properly handles any
148
+ `container_attribute` -- it'll look in the proper jsonb column.
149
+
150
+ Anything you can do with `jsonb_contains` should be handled
151
+ by a [postgres `USING GIN` index](https://www.postgresql.org/docs/9.5/static/datatype-json.html#JSON-INDEXING)
152
+ (I think! can anyone help confirm/deny?). To be sure, I recommend you
153
+ investigate: Check out `to_sql` on any query to see what jsonb SQL it generates,
154
+ and explore if you have the indexes you need.
155
+
156
+ <a name="nested"></a>
157
+ ## Nested models -- Structured/compound data
158
+
159
+ The `AttrJson::Model` mix-in lets you make ActiveModel::Model objects that can be round-trip serialized to a json hash, and they can be used as types for your top-level AttrJson::Record.
160
+ `AttrJson::Model`s can contain other AJ::Models, singly or as arrays, nested as many levels as you like.
161
+
162
+ That is, you can serialize complex object-oriented graphs of models into a single
163
+ jsonb column, and get them back as they went in.
164
+
165
+ `AttrJson::Model` has an identical `attr_json` api to
166
+ `AttrJson::Record`, with the exception that `container_attribute` is not supported.
167
+
168
+ ```ruby
169
+ class LangAndValue
170
+ include AttrJson::Model
171
+
172
+ attr_json :lang, :string, default: "en"
173
+ attr_json :value, :string
174
+
175
+ # Validations work fine, and will post up to parent record
176
+ validates :lang, inclusion_in: I18n.config.available_locales.collect(&:to_s)
177
+ end
178
+
179
+ class MyModel < ActiveRecord::Base
180
+ include AttrJson::Record
181
+ include AttrJson::Record::QueryScopes
182
+
183
+ attr_json :lang_and_value, LangAndValue.to_type
184
+
185
+ # YES, you can even have an array of them
186
+ attr_json :lang_and_value_array, LangAndValue.to_type, array: true
187
+ end
188
+
189
+ # Set with a model object, in initializer or writer
190
+ m = MyModel.new(lang_and_value: LangAndValue.new(lang: "fr", value: "S'il vous plaît"))
191
+ m.lang_and_value = LangAndValue.new(lang: "es", value: "hola")
192
+ m.lang_and_value
193
+ # => #<LangAndValue:0x007fb64f12bb70 @attributes={"lang"=>"es", "value"=>"hola"}>
194
+ m.save!
195
+ m.attr_jsons_before_type_cast
196
+ # => string containing: {"lang_and_value":{"lang":"es","value":"hola"}}
197
+
198
+ # Or with a hash, no problem.
199
+
200
+ m = MyModel.new(lang_and_value: { lang: 'fr', value: "S'il vous plaît"})
201
+ m.lang_and_value = { lang: 'en', value: "Hey there" }
202
+ m.save!
203
+ m.attr_jsons_before_type_cast
204
+ # => string containing: {"lang_and_value":{"lang":"en","value":"Hey there"}}
205
+ found = MyModel.find(m.id)
206
+ m.lang_and_value
207
+ # => #<LangAndValue:0x007fb64eb78e58 @attributes={"lang"=>"en", "value"=>"Hey there"}>
208
+
209
+ # Arrays too, yup
210
+
211
+ m = MyModel.new(lang_and_value_array: [{ lang: 'fr', value: "S'il vous plaît"}, { lang: 'en', value: "Hey there" }])
212
+ m.lang_and_value_array
213
+ # => [#<LangAndValue:0x007f89b4f08f30 @attributes={"lang"=>"fr", "value"=>"S'il vous plaît"}>, #<LangAndValue:0x007f89b4f086e8 @attributes={"lang"=>"en", "value"=>"Hey there"}>]
214
+ m.save!
215
+ m.attr_jsons_before_type_cast
216
+ # => string containing: {"lang_and_value_array":[{"lang":"fr","value":"S'il vous plaît"},{"lang":"en","value":"Hey there"}]}
217
+ ```
218
+
219
+ You can nest AttrJson::Model objects inside each other, as deeply as you like.
220
+
221
+ ```ruby
222
+ class SomeLabels
223
+ include AttrJson::Model
224
+
225
+ attr_json :hello, LangAndValue.to_type, array: true
226
+ attr_json :goodbye, LangAndValue.to_type, array: true
227
+ end
228
+ class MyModel < ActiveRecord::Base
229
+ include AttrJson::Record
230
+ include AttrJson::Record::QueryScopes
231
+
232
+ attr_json :my_labels, SomeLabels.to_type
233
+ end
234
+
235
+ m = MyModel.new
236
+ m.my_labels = {}
237
+ m.my_labels
238
+ # => #<SomeLabels:0x007fed2a3b1a18>
239
+ m.my_labels.hello = [{lang: 'en', value: 'hello'}, {lang: 'es', value: 'hola'}]
240
+ m.my_labels
241
+ # => #<SomeLabels:0x007fed2a3b1a18 @attributes={"hello"=>[#<LangAndValue:0x007fed2a0eafc8 @attributes={"lang"=>"en", "value"=>"hello"}>, #<LangAndValue:0x007fed2a0bb4d0 @attributes={"lang"=>"es", "value"=>"hola"}>]}>
242
+ m.my_labels.hello.find { |l| l.lang == "en" }.value = "Howdy"
243
+ m.save!
244
+ m.attr_jsons
245
+ # => {"my_labels"=>#<SomeLabels:0x007fed2a714e80 @attributes={"hello"=>[#<LangAndValue:0x007fed2a714cf0 @attributes={"lang"=>"en", "value"=>"Howdy"}>, #<LangAndValue:0x007fed2a714ac0 @attributes={"lang"=>"es", "value"=>"hola"}>]}>}
246
+ m.attr_jsons_before_type_cast
247
+ # => string containing: {"my_labels":{"hello":[{"lang":"en","value":"Howdy"},{"lang":"es","value":"hola"}]}}
248
+ ```
249
+
250
+ **GUESS WHAT?** You can **QUERY** nested structures with `jsonb_contains`,
251
+ using a dot-keypath notation, even through arrays as in this case. Your specific
252
+ defined `attr_json` types determine the query and type-casting.
253
+
254
+ ```ruby
255
+ MyModel.jsonb_contains("my_labels.hello.lang" => "en").to_sql
256
+ # => SELECT "products".* FROM "products" WHERE (products.json_attributes @> ('{"my_labels":{"hello":[{"lang":"en"}]}}')::jsonb)
257
+ MyModel.jsonb_contains("my_labels.hello.lang" => "en").first
258
+
259
+
260
+ # also can give hashes, at any level, or models themselves. They will
261
+ # be cast. Trying to make everything super consistent with no surprises.
262
+
263
+ MyModel.jsonb_contains("my_labels.hello" => LangAndValue.new(lang: 'en')).to_sql
264
+ # => SELECT "products".* FROM "products" WHERE (products.json_attributes @> ('{"my_labels":{"hello":[{"lang":"en"}]}}')::jsonb)
265
+
266
+ MyModel.jsonb_contains("my_labels.hello" => {"lang" => "en"}).to_sql
267
+ # => SELECT "products".* FROM "products" WHERE (products.json_attributes @> ('{"my_labels":{"hello":[{"lang":"en"}]}}')::jsonb)
268
+
269
+ ```
270
+
271
+ Remember, we're using a postgres containment (`@>`) operator, so queries
272
+ always mean 'contains' -- the previous query needs a `my_labels.hello`
273
+ which is a hash that includes the key/value, `lang: en`, it can have
274
+ other key/values in it too. String values will need to match exactly.
275
+
276
+
277
+ <a name="forms"></a>
278
+ ## Forms and Form Builders
279
+
280
+ Use with Rails form builders is supported pretty painlessly. Including with [simple_form](https://github.com/plataformatec/simple_form) and [cocoon](https://github.com/nathanvda/cocoon) (integration-tested in CI).
281
+
282
+ If you have nested AttrJson::Models you'd like to use in your forms much like Rails associated records: Where you would use Rails `accept_nested_attributes_for`, instead `include AttrJson::NestedAttributes` and use `attr_json_accepts_nested_attributes_for`. Multiple levels of nesting are supported.
283
+
284
+ To get simple_form to properly detect your attribute types, define your attributes with `rails_attribute: true`.
285
+
286
+ For more info, see doc page on [Use with Forms and Form Builders](doc_src/forms.md).
287
+
288
+ <a name="dirty"></a>
289
+ ## Dirty tracking
290
+
291
+ Full change-tracking, ActiveRecord::Attributes::Dirty-style, is available in
292
+ Rails 5.1+ on `attr_json`s on your ActiveRecord classes that include
293
+ `AttrJson::Record`, by including `AttrJson::Record::Dirty`.
294
+ Change-tracking methods are available off the `attr_json_changes` method.
295
+
296
+ class MyModel < ActiveRecord::Base
297
+ include AttrJson::Record
298
+ include AttrJson::Record::Dirty
299
+
300
+ attr_json :str, :string
301
+ end
302
+
303
+ model = MyModel.new
304
+ model.str = "old"
305
+ model.save
306
+ model.str = "new"
307
+
308
+ # All and only "new" style dirty tracking methods (Raisl 5.1+)
309
+ # are available:
310
+
311
+ model.attr_json_changes.saved_changes
312
+ model.attr_json_changes.changes_to_save
313
+ model.attr_json_changes.saved_change_to_str?
314
+ model.attr_json_changes.saved_change_to_str
315
+ model.attr_json_changes.will_save_change_to_str?
316
+ # etc
317
+
318
+ More options are available, including merging changes from 'ordinary'
319
+ ActiveRecord attributes in. See docs on [Dirty Tracking](./doc_src/dirty_tracking.md)
320
+
321
+ ## Do you want this?
322
+
323
+ Why might you want this?
324
+
325
+ * You have complicated data, which you want to access in object-oriented
326
+ fashion, but want to avoid very complicated normalized rdbms schema --
327
+ and are willing to trade the powerful complex querying support normalized rdbms
328
+ schema gives you.
329
+
330
+ * Single-Table Inheritance, with sub-classes that have non-shared
331
+ data fields. You rather not make all those columns, some of which will then also appear
332
+ to inapplicable sub-classes.
333
+
334
+ * A "content management system" type project, where you need complex
335
+ structured data of various types, maybe needs to be vary depending
336
+ on plugins or configuration, or for different article types -- but
337
+ doesn't need to be very queryable generally.
338
+
339
+ * You want to version your models, which is tricky with associations between models.
340
+ Minimize associations by inlining the complex data into one table row.
341
+
342
+ * Generally, we're turning postgres into a _simple_ object-oriented
343
+ document store. That can be mixed with an rdbms. The very same
344
+ row in a table in your db can have document-oriented json data _and_ foreign keys
345
+ and real rdbms associations to other rows. And it all just
346
+ feels like ActiveRecord, mostly.
347
+
348
+ Why might you _not_ want this?
349
+
350
+ * An rdbms and SQL is a wonderful thing, if you need sophisticated
351
+ querying and reporting with reasonable performance, complex data
352
+ in a single jsonb probably isn't gonna be the best.
353
+
354
+ * This is pretty well-designed code that _mostly_ only uses
355
+ fairly stable and public Rails API, but there is still some
356
+ risk of tying your boat to it, it's not Rails itself, and there is
357
+ some risk it won't keep up with Rails in the future.
358
+
359
+
360
+ ## Note on Optimistic Locking
361
+
362
+ When you save a record with any changes to any attr_jsons, it will
363
+ overwrite the _whole json structure_ in the relevant column for that row.
364
+ Unlike ordinary AR attributes where updates just touch changed attributes.
365
+
366
+ Becuase of this, you probably want to seriously consider using ActiveRecord
367
+ [Optimistic Locking](http://api.rubyonrails.org/classes/ActiveRecord/Locking/Optimistic.html)
368
+ to prevent overwriting other updates from processes.
369
+
370
+ ## State of Code, and To Be Done
371
+
372
+ This is a pre-1.0 work in progress. But the functionality that is here seems pretty solid.
373
+
374
+ Backwards incompatible changes are possible before 1.0. Once I tag something 1.0, I'm pretty serious about minimizing backwards incompats.
375
+
376
+ I do not yet use this myself in production, and may not for a while. I generally am reluctant to release something as 1.0 with implied suitable for production when I'm not yet using it in production myself, but may with enough feedback. A couple others are already using in production.
377
+
378
+ Feedback of any kind of _very welcome_, please feel free to use the issue tracker.
379
+
380
+ Except for the jsonb_contains stuff using postgres jsonb contains operator, I don't believe any postgres-specific features are used. It ought to work with MySQL, testing and feedback welcome. (Or a PR to test on MySQL?). My own interest is postgres.
381
+
382
+ ### Possible future features:
383
+
384
+ * Polymorphic JSON attributes.
385
+
386
+ * partial updates for json hashes would be really nice: Using postgres jsonb merge operators to only overwrite what changed. In my initial attempts, AR doesn't make it easy to customize this.
387
+
388
+ * seamless compatibility with ransack
389
+
390
+ * Should we give AttrJson::Model a before_serialize hook that you might
391
+ want to use similar to AR before_save? Should AttrJson::Models
392
+ raise on trying to serialize an invalid model?
393
+
394
+ * There are limits to what you can do with just jsonb_contains
395
+ queries. We could support operations like `>`, `<`, `<>`
396
+ as [jsonb_accessor](https://github.com/devmynd/jsonb_accessor),
397
+ even accross keypaths. (At present, you could use a
398
+ before_savee to denormalize/renormalize copy your data into
399
+ ordinary AR columns/associations for searching. Or perhaps a postgres ts_vector for text searching. Needs to be worked out.)
400
+
401
+ * We could/should probably support `jsonb_order` clauses, even
402
+ accross key paths, like jsonb_accessor.
403
+
404
+ * Could we make these attributes work in ordinary AR where, same
405
+ as they do in jsonb_contains? Maybe.
406
+
407
+ ## Acknowledements and Prior Art
408
+
409
+ * The excellent work [Sean Griffin](https://twitter.com/sgrif) did on ActiveModel::Type
410
+ really lays the groundwork and makes this possible. Plus many other Rails developers.
411
+ Rails has a reputation for being composed of messy or poorly designed code, but
412
+ it's some really nice design in Rails that allows us to do some pretty powerful
413
+ stuff here, in surprisingly few lines of code.
414
+
415
+ * The existing [jsonb_accessor](https://github.com/devmynd/jsonb_accessor) was
416
+ an inspiration, and provided some good examples of how to do some things
417
+ with AR and ActiveModel::Types. I [started out trying to figure out](https://github.com/devmynd/jsonb_accessor/issues/69#issuecomment-294081059)
418
+ how to fit in nested hashes to jsonb_accessor... but ended up pretty much rewriting it entirely,
419
+ to lean on object-oriented polymorphism and ActiveModel::Type a lot heavier and have
420
+ the API and internals I wanted/imagined.
421
+
422
+ * Took a look at existing [active_model_attributes](https://github.com/Azdaroth/active_model_attributes) too.
423
+
424
+ * Didn't actually notice existing [json_attributes](https://github.com/joel/json_attributes)
425
+ until I was well on my way here. I think it's not updated for Rails5 or type-aware,
426
+ haven't looked at it too much.