schemattr 0.1.0 → 0.2.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/CHANGELOG.md +0 -0
- data/{MIT.LICENSE → MIT-LICENSE} +1 -3
- data/README.md +63 -31
- data/lib/schemattr/active_record_extension.rb +2 -1
- data/lib/schemattr/attribute.rb +2 -1
- data/lib/schemattr/dsl.rb +16 -3
- data/lib/schemattr/version.rb +15 -1
- data/lib/schemattr.rb +3 -1
- metadata +36 -17
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ed48f215b1651cfcaca945564ee28889209196633128c453c25050c93948092
|
4
|
+
data.tar.gz: 1d93e1999e211d8168b2f1dea9bb650043afe606a0c6c2eeb006689a6f4184eb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: e38303d1c00e993a70b4f64dd37287aef23b3eac18a4a233482adc01e9ed90714a71b50128208eb8f46ec26d2e0c5e7c965470cf9e4cbd7b328a663ca0492f6e
|
7
|
+
data.tar.gz: 43361ee53cabc37ca4f3e538cf93d1fb4328584e4564e0230cb7a352a375d59895c7ca9503fa71e3287638490780bc7cd0b80de1fd56a40ffbfc0f2a311fd302
|
data/CHANGELOG.md
ADDED
File without changes
|
data/{MIT.LICENSE → MIT-LICENSE}
RENAMED
data/README.md
CHANGED
@@ -1,16 +1,19 @@
|
|
1
|
-
Schemattr
|
2
|
-
=========
|
1
|
+
# Schemattr - Schema-ish ActiveRecord attributes
|
3
2
|
|
4
|
-
|
5
|
-
|
6
|
-
[](https://rubygems.org/gems/schemattr)
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
7
|
+
[](https://github.com/jejacks0n/schemattr/actions/workflows/ci.yml)
|
8
|
+
[](https://codeclimate.com/github/jejacks0n/schemattr/maintainability)
|
9
|
+
[](https://codeclimate.com/github/jejacks0n/schemattr/test_coverage)
|
10
|
+
[](https://rubygems.org/gems/schemattr)
|
9
11
|
|
10
12
|
Schemattr is an ActiveRecord extension that provides a helpful schema-less attribute DSL. It can be used to define a
|
11
13
|
simple schema for a single attribute that can change over time without having to migrate existing data.
|
12
14
|
|
13
15
|
### Background
|
16
|
+
|
14
17
|
Let's say you have a User model, and that model has a simple concept of settings -- just one for now. It's a boolean
|
15
18
|
named `opted_in`, and it means that the user is opted in to receive email updates. Sweet, we go add a migration for this
|
16
19
|
setting and migrate. Ship it, we're done with that feature.
|
@@ -32,19 +35,33 @@ methods, can keep a real column synced with one if its fields, and more.
|
|
32
35
|
If you're using Schemattr and want to add a new setting field, it's as simple as adding a new field to the attribute
|
33
36
|
schema and setting a default right there in the code. No migrations, no hassles, easy deployment.
|
34
37
|
|
35
|
-
## Installation
|
38
|
+
## Download and Installation
|
39
|
+
|
40
|
+
Add this line to your Gemfile:
|
41
|
+
|
36
42
|
```ruby
|
37
43
|
gem "schemattr"
|
38
44
|
```
|
39
45
|
|
46
|
+
Or install the latest version with RubyGems:
|
47
|
+
|
48
|
+
```bash
|
49
|
+
gem install schemattr
|
50
|
+
```
|
51
|
+
|
52
|
+
Source code can be downloaded as part of the project on GitHub:
|
53
|
+
|
54
|
+
* https://github.com/jejacks0n/schemattr
|
55
|
+
|
40
56
|
## Usage
|
57
|
+
|
41
58
|
In the examples we assume there's already a User model and table.
|
42
59
|
|
43
60
|
First, let's create a migration to add your schema-less attribute. In postgres you can use a JSON column. We use the
|
44
61
|
postgres JSON type as our example because the JSON type allows queries and indexing, and hstore does annoying things to
|
45
62
|
booleans. We don't need to set our default value to an empty object because Schemattr handles that for us.
|
46
63
|
|
47
|
-
|
64
|
+
_Note_: If you're using a different database provider, like sqlite3 for instance, you can use a text column and tell
|
48
65
|
ActiveRecord to serialize that column (e.g. `serialize :settings` in your model). Though, you won't be able to easily
|
49
66
|
query in these cases so consider your options.
|
50
67
|
|
@@ -75,8 +92,8 @@ Notice that we've done nothing else, but we already have a working version of wh
|
|
75
92
|
```
|
76
93
|
user = User.new
|
77
94
|
user.settings.opted_in? # => true
|
78
|
-
user.settings.email_group_advanced? # => false
|
79
|
-
user.settings.email_group_expert? # => false
|
95
|
+
user.settings.email_group_advanced? # => false
|
96
|
+
user.settings.email_group_expert? # => false
|
80
97
|
```
|
81
98
|
|
82
99
|
If we save the user at this point, these settings will be persisted. We can also make changes to them at this point, and
|
@@ -84,27 +101,30 @@ when they're persisted they'll include whatever we've changed them to be. If we
|
|
84
101
|
they'll just be the defaults if we ever ask again.
|
85
102
|
|
86
103
|
### Field types
|
104
|
+
|
87
105
|
The various field types are outlined below. When you define a string field for instance, the value will be coerced into
|
88
106
|
a string at the time that it's set.
|
89
107
|
|
90
|
-
type | description
|
91
|
-
|
92
|
-
boolean | boolean value
|
93
|
-
string | string value
|
94
|
-
text | same as string type
|
95
|
-
integer | number value
|
96
|
-
bigint | same as integer
|
97
|
-
float | floating point number value
|
98
|
-
decimal | same as float
|
99
|
-
datetime | datetime object
|
100
|
-
time | time object (stored the same as datetime)
|
101
|
-
date | date object
|
108
|
+
| type | description |
|
109
|
+
|----------|-------------------------------------------|
|
110
|
+
| boolean | boolean value |
|
111
|
+
| string | string value |
|
112
|
+
| text | same as string type |
|
113
|
+
| integer | number value |
|
114
|
+
| bigint | same as integer |
|
115
|
+
| float | floating point number value |
|
116
|
+
| decimal | same as float |
|
117
|
+
| datetime | datetime object |
|
118
|
+
| time | time object (stored the same as datetime) |
|
119
|
+
| date | date object |
|
120
|
+
| hash | hash object |
|
102
121
|
|
103
122
|
You can additionally define your own types using `field :foo, :custom_type` and there will no coercion at the time the
|
104
123
|
field is set -- this is intended for when you need something that doesn't care what type it is. This generally makes it
|
105
124
|
harder to use in forms however.
|
106
125
|
|
107
126
|
### Delegating
|
127
|
+
|
108
128
|
If you don't like the idea of having to access these attributes at `user.settings` you can specify that you'd like them
|
109
129
|
delegated. This adds delegation of the methods that exist on settings to the User instances.
|
110
130
|
|
@@ -122,10 +142,11 @@ user.opted_in? # => false
|
|
122
142
|
```
|
123
143
|
|
124
144
|
### Strict mode vs. arbitrary fields
|
145
|
+
|
125
146
|
By default, Schemattr doesn't allow arbitrary fields to be added, but it supports it. When strict mode is disabled, it
|
126
147
|
allows any arbitrary field to be set or asked for.
|
127
148
|
|
128
|
-
|
149
|
+
_Note_: When delegated and strict mode is disabled, you cannot set arbitrary fields on the model directly and must
|
129
150
|
access them through the attribute that you've defined -- in our case, it's `settings`.
|
130
151
|
|
131
152
|
```ruby
|
@@ -143,6 +164,7 @@ user.foo # => NoMethodError
|
|
143
164
|
```
|
144
165
|
|
145
166
|
### Overriding
|
167
|
+
|
146
168
|
Schemattr provides the ability to specify your own attribute class. By doing so you can provide your own getters and
|
147
169
|
setters and do more complex logic. In this example we're providing the inverse of `opted_in` with an `opted_out` psuedo
|
148
170
|
field.
|
@@ -153,7 +175,7 @@ class UserSettings < Schemattr::Attribute
|
|
153
175
|
!self[:opted_in]
|
154
176
|
end
|
155
177
|
alias_method :opted_out, :opted_out?
|
156
|
-
|
178
|
+
|
157
179
|
def opted_out=(val)
|
158
180
|
self.opted_in = !val
|
159
181
|
end
|
@@ -178,6 +200,7 @@ Our custom `opted_out` psuedo field won't be persisted, because it's not a defin
|
|
178
200
|
existing field that is persisted (`opted_in`).
|
179
201
|
|
180
202
|
#### Getters and setters
|
203
|
+
|
181
204
|
When overriding the attribute class with your own, you can provide your own custom getters and setters as well. These
|
182
205
|
will not be overridden by whatever Schemattr thinks they should do. Take this example, where when someone turns on or
|
183
206
|
off a setting we want to subscribe/unsubscribe them to an email list via a third party.
|
@@ -196,9 +219,10 @@ class UserSettings < Schemattr::Attribute
|
|
196
219
|
end
|
197
220
|
```
|
198
221
|
|
199
|
-
|
222
|
+
_Note_: This is not a real world scenario but serves our purposes of describing an example.
|
200
223
|
|
201
224
|
### Renaming fields
|
225
|
+
|
202
226
|
Schemattr makes it easy to rename fields as well. Let's say you've got a field named `opted_in`, as the examples have
|
203
227
|
shown thus far. But you've added new email lists, and you think `opted_in` is too vague. Like, opted in for what?
|
204
228
|
|
@@ -207,14 +231,16 @@ We can create a new field that is correctly named, and specify what attribute we
|
|
207
231
|
```ruby
|
208
232
|
attribute_schema :settings do
|
209
233
|
# field :opted_in, :boolean, default: true
|
210
|
-
field :email_list_beginner, :boolean,
|
234
|
+
field :email_list_beginner, :boolean, value_from: :opted_in, default: true
|
211
235
|
end
|
212
236
|
```
|
213
237
|
|
214
|
-
Specifying the `
|
238
|
+
Specifying the `value_from: :opted_in` option will tell Schemattr to look for the value that may have already been defined in
|
215
239
|
`opted_in` before the rename. This allows for slow migrations, but you can also write a migration to ensure this happens
|
216
240
|
quickly.
|
217
241
|
|
242
|
+
This previously used the keyword `from`, and this keyword is deprecated.
|
243
|
+
|
218
244
|
### Syncing attributes
|
219
245
|
|
220
246
|
There's a down side to keeping some things internal to this settings attribute. You can query JSON types in postgres,
|
@@ -247,6 +273,7 @@ using things like `user.update_column(:opted_in, false)`, and `User.update_all(o
|
|
247
273
|
get out of sync.
|
248
274
|
|
249
275
|
## Querying a JSON column
|
276
|
+
|
250
277
|
This has come up a little bit, and so it's worth documenting -- though it has very little to do with Schemattr. When you
|
251
278
|
have a JSON column in postgres, you can query values from within that column in various ways.
|
252
279
|
|
@@ -255,12 +282,17 @@ these are the common scenarios that we've used.
|
|
255
282
|
|
256
283
|
```
|
257
284
|
User.where("(settings->>'opted_in')::boolean") # boolean query
|
258
|
-
User.where("settings->>'string_value' = ?", "some string") # string query
|
285
|
+
User.where("settings->>'string_value' = ?", "some string") # string query
|
259
286
|
```
|
260
287
|
|
261
288
|
## License
|
262
|
-
Licensed under the [MIT License](http://creativecommons.org/licenses/MIT/)
|
263
289
|
|
264
|
-
|
290
|
+
This project is released under the MIT license:
|
291
|
+
|
292
|
+
* https://opensource.org/licenses/MIT
|
293
|
+
|
294
|
+
Copyright 2023 [jejacks0n](https://github.com/jejacks0n)
|
265
295
|
|
266
296
|
## Make Code Not War
|
297
|
+
|
298
|
+
[](http://forthebadge.com)
|
data/lib/schemattr/attribute.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Schemattr
|
2
4
|
class Attribute
|
3
5
|
attr_accessor :model, :attr_name, :hash
|
@@ -22,7 +24,6 @@ module Schemattr
|
|
22
24
|
end
|
23
25
|
|
24
26
|
private
|
25
|
-
|
26
27
|
def method_missing(m, *args)
|
27
28
|
if @allow_arbitrary_attributes
|
28
29
|
self[$1] = args[0] if args.length == 1 && /^(\w+)=$/ =~ m
|
data/lib/schemattr/dsl.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Schemattr
|
2
4
|
class DSL
|
3
5
|
attr_accessor :attribute_class, :delegated, :defaults
|
@@ -13,8 +15,12 @@ module Schemattr
|
|
13
15
|
end
|
14
16
|
|
15
17
|
protected
|
16
|
-
|
17
18
|
def field(name, type, options = {})
|
19
|
+
if options[:from].present?
|
20
|
+
options[:value_from] = options.delete(:from)
|
21
|
+
log_warning(name, type, options[:value_from])
|
22
|
+
end
|
23
|
+
|
18
24
|
if respond_to?(type, true)
|
19
25
|
send(type, name, options)
|
20
26
|
else
|
@@ -55,10 +61,9 @@ module Schemattr
|
|
55
61
|
alias_method :time, :datetime
|
56
62
|
|
57
63
|
private
|
58
|
-
|
59
64
|
def define(name, boolean, options, blocks = {})
|
60
65
|
setter = blocks[:setter] || lambda { sync_value(self[name] = val, options[:sync]) }
|
61
|
-
getter = blocks[:getter] || lambda { migrate_value(self[name], options[:
|
66
|
+
getter = blocks[:getter] || lambda { migrate_value(self[name], options[:value_from]) }
|
62
67
|
default_for(name, options[:default])
|
63
68
|
method_for("#{name}=", options[:sync], &setter)
|
64
69
|
method_for(name, options[:sync], &getter)
|
@@ -69,6 +74,14 @@ module Schemattr
|
|
69
74
|
@defaults[name.to_s] = default
|
70
75
|
end
|
71
76
|
|
77
|
+
def log_warning(name, type, old_name)
|
78
|
+
message = <<-HEREDOC
|
79
|
+
Schemattr Warning: #{name}, #{type}, from: #{old_name}
|
80
|
+
You are using the keyword `from:` instead of the new `value_from:`. This is deprecated and will be removed in a future version."
|
81
|
+
HEREDOC
|
82
|
+
puts message
|
83
|
+
end
|
84
|
+
|
72
85
|
def method_for(name, delegated = false, &block)
|
73
86
|
@delegated.push(name.to_s) if delegated
|
74
87
|
unless attribute_class.instance_methods.include?(name.to_sym)
|
data/lib/schemattr/version.rb
CHANGED
@@ -1,3 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
module Schemattr
|
2
|
-
|
4
|
+
# Returns the currently loaded version as a +Gem::Version+.
|
5
|
+
def self.version
|
6
|
+
Gem::Version.new(VERSION::STRING)
|
7
|
+
end
|
8
|
+
|
9
|
+
module VERSION
|
10
|
+
MAJOR = 0
|
11
|
+
MINOR = 2
|
12
|
+
TINY = 0
|
13
|
+
PRE = nil
|
14
|
+
|
15
|
+
STRING = [MAJOR, MINOR, TINY, PRE].compact.join(".")
|
16
|
+
end
|
3
17
|
end
|
data/lib/schemattr.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require "schemattr/version"
|
2
4
|
require "schemattr/dsl"
|
3
5
|
require "schemattr/attribute"
|
4
|
-
require "schemattr/active_record_extension
|
6
|
+
require "schemattr/active_record_extension"
|
5
7
|
|
6
8
|
ActiveRecord::Base.send(:include, Schemattr::ActiveRecordExtension) if defined?(ActiveRecord)
|
metadata
CHANGED
@@ -1,52 +1,71 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: schemattr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
|
-
-
|
8
|
-
autorequire:
|
7
|
+
- Jeremy Jackson
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
12
|
-
dependencies:
|
11
|
+
date: 2023-07-23 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 5.0.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 5.0.0
|
13
27
|
description: Write schema-less attributes in ActiveRecord using a helpful and flexible
|
14
28
|
DSL.
|
15
|
-
email:
|
16
|
-
- jejacks0n@gmail.com
|
29
|
+
email: jejacks0n@gmail.com
|
17
30
|
executables: []
|
18
31
|
extensions: []
|
19
32
|
extra_rdoc_files: []
|
20
33
|
files:
|
21
|
-
-
|
34
|
+
- CHANGELOG.md
|
35
|
+
- MIT-LICENSE
|
22
36
|
- README.md
|
23
37
|
- lib/schemattr.rb
|
24
38
|
- lib/schemattr/active_record_extension.rb
|
25
39
|
- lib/schemattr/attribute.rb
|
26
40
|
- lib/schemattr/dsl.rb
|
27
41
|
- lib/schemattr/version.rb
|
28
|
-
homepage:
|
42
|
+
homepage: https://github.com/jejacks0n/schemattr
|
29
43
|
licenses:
|
30
44
|
- MIT
|
31
|
-
metadata:
|
32
|
-
|
45
|
+
metadata:
|
46
|
+
homepage_uri: https://github.com/jejacks0n/schemattr
|
47
|
+
source_code_uri: https://github.com/jejacks0n/schemattr
|
48
|
+
bug_tracker_uri: https://github.com/jejacks0n/schemattr/issues
|
49
|
+
changelog_uri: https://github.com/jejacks0n/schemattr/CHANGELOG.md
|
50
|
+
documentation_uri: https://github.com/jejacks0n/schemattr/README.md
|
51
|
+
rubygems_mfa_required: 'true'
|
52
|
+
post_install_message:
|
33
53
|
rdoc_options: []
|
34
54
|
require_paths:
|
35
55
|
- lib
|
36
56
|
required_ruby_version: !ruby/object:Gem::Requirement
|
37
57
|
requirements:
|
38
|
-
- - "
|
58
|
+
- - ">="
|
39
59
|
- !ruby/object:Gem::Version
|
40
|
-
version:
|
60
|
+
version: 2.7.0
|
41
61
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
42
62
|
requirements:
|
43
63
|
- - ">="
|
44
64
|
- !ruby/object:Gem::Version
|
45
65
|
version: '0'
|
46
66
|
requirements: []
|
47
|
-
|
48
|
-
|
49
|
-
signing_key:
|
67
|
+
rubygems_version: 3.4.14
|
68
|
+
signing_key:
|
50
69
|
specification_version: 4
|
51
|
-
summary: 'Schemattr: Simple schema-less column definitions for ActiveRecord'
|
70
|
+
summary: 'Schemattr: Simple schema-less column definitions for ActiveRecord.'
|
52
71
|
test_files: []
|