attr_json 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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.