schemattr 0.0.1 → 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 +4 -4
- data/MIT.LICENSE +2 -2
- data/README.md +24 -65
- data/lib/schemattr/active_record_extension.rb +5 -5
- data/lib/schemattr/attribute.rb +27 -27
- data/lib/schemattr/dsl.rb +50 -52
- data/lib/schemattr/version.rb +1 -1
- metadata +9 -8
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 4d2d2b02fcfc5238afd51f0741a619f334315e785b91c2add642cc1ef0955962
|
4
|
+
data.tar.gz: b586cd63d36d773027f6786b81c785eba67b4439a1f9999140e7bff4ad44eb4d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 6bd7afaa4e360c1efe3b171fe2815f2687c23aaf6f1243bdb162ad720788ffd531b2569bfc63e16cd35698a0ad52fdc80196518703651c02d4e7251fe985a59c
|
7
|
+
data.tar.gz: a59b2bd4c1f0bc29ba27d0859512fd863208f4b7591d4a951e67b60e2f80c4a6053fbd61b0598ef264798249ae84c3df4d51591ff9c18f24cc88ec23daee7bb3
|
data/MIT.LICENSE
CHANGED
@@ -1,6 +1,6 @@
|
|
1
|
-
Copyright
|
1
|
+
Copyright 2019 Jeremy Jackson
|
2
2
|
|
3
|
-
https://github.com/
|
3
|
+
https://github.com/jejacks0n/schemattr
|
4
4
|
|
5
5
|
Permission is hereby granted, free of charge, to any person obtaining
|
6
6
|
a copy of this software and associated documentation files (the
|
data/README.md
CHANGED
@@ -2,17 +2,15 @@ Schemattr
|
|
2
2
|
=========
|
3
3
|
|
4
4
|
[](http://badge.fury.io/rb/schemattr)
|
5
|
-
[](
|
9
|
-
[](https://gemnasium.com/modeset/schemattr)
|
5
|
+
[](https://travis-ci.org/jejacks0n/schemattr)
|
6
|
+
[](https://codeclimate.com/github/jejacks0n/schemattr)
|
7
|
+
[](https://codeclimate.com/github/jejacks0n/schemattr)
|
8
|
+
[](https://opensource.org/licenses/MIT)
|
10
9
|
|
11
10
|
Schemattr is an ActiveRecord extension that provides a helpful schema-less attribute DSL. It can be used to define a
|
12
11
|
simple schema for a single attribute that can change over time without having to migrate existing data.
|
13
12
|
|
14
13
|
### Background
|
15
|
-
|
16
14
|
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
|
17
15
|
named `opted_in`, and it means that the user is opted in to receive email updates. Sweet, we go add a migration for this
|
18
16
|
setting and migrate. Ship it, we're done with that feature.
|
@@ -34,39 +32,12 @@ methods, can keep a real column synced with one if its fields, and more.
|
|
34
32
|
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
|
35
33
|
schema and setting a default right there in the code. No migrations, no hassles, easy deployment.
|
36
34
|
|
37
|
-
|
38
|
-
## Table of Contents
|
39
|
-
|
40
|
-
1. [Installation](#installation)
|
41
|
-
2. [Usage](#usage)
|
42
|
-
- [Field types](#field-types)
|
43
|
-
- [Delegating](#delegating)
|
44
|
-
- [Strict mode](#strict-mode-vs-arbitrary-fields)
|
45
|
-
- [Overriding](#overriding-functionality)
|
46
|
-
- [Renaming fields](#renaming-fields)
|
47
|
-
- [Syncing attributes](#syncing-attributes)
|
48
|
-
|
49
|
-
|
50
35
|
## Installation
|
51
|
-
|
52
|
-
Add it to your Gemfile:
|
53
36
|
```ruby
|
54
|
-
gem
|
55
|
-
```
|
56
|
-
|
57
|
-
And then execute:
|
58
|
-
```shell
|
59
|
-
$ bundle
|
60
|
-
```
|
61
|
-
|
62
|
-
Or install it yourself as:
|
63
|
-
```shell
|
64
|
-
$ gem install schemattr
|
37
|
+
gem "schemattr"
|
65
38
|
```
|
66
39
|
|
67
|
-
|
68
40
|
## Usage
|
69
|
-
|
70
41
|
In the examples we assume there's already a User model and table.
|
71
42
|
|
72
43
|
First, let's create a migration to add your schema-less attribute. In postgres you can use a JSON column. We use the
|
@@ -113,7 +84,6 @@ when they're persisted they'll include whatever we've changed them to be. If we
|
|
113
84
|
they'll just be the defaults if we ever ask again.
|
114
85
|
|
115
86
|
### Field types
|
116
|
-
|
117
87
|
The various field types are outlined below. When you define a string field for instance, the value will be coerced into
|
118
88
|
a string at the time that it's set.
|
119
89
|
|
@@ -135,14 +105,13 @@ field is set -- this is intended for when you need something that doesn't care w
|
|
135
105
|
harder to use in forms however.
|
136
106
|
|
137
107
|
### Delegating
|
138
|
-
|
139
108
|
If you don't like the idea of having to access these attributes at `user.settings` you can specify that you'd like them
|
140
109
|
delegated. This adds delegation of the methods that exist on settings to the User instances.
|
141
110
|
|
142
111
|
```ruby
|
143
|
-
|
144
|
-
|
145
|
-
|
112
|
+
attribute_schema :settings, delegated: true do
|
113
|
+
field :opted_in, :boolean, default: true
|
114
|
+
end
|
146
115
|
```
|
147
116
|
|
148
117
|
```ruby
|
@@ -153,7 +122,6 @@ user.opted_in? # => false
|
|
153
122
|
```
|
154
123
|
|
155
124
|
### Strict mode vs. arbitrary fields
|
156
|
-
|
157
125
|
By default, Schemattr doesn't allow arbitrary fields to be added, but it supports it. When strict mode is disabled, it
|
158
126
|
allows any arbitrary field to be set or asked for.
|
159
127
|
|
@@ -161,9 +129,9 @@ allows any arbitrary field to be set or asked for.
|
|
161
129
|
access them through the attribute that you've defined -- in our case, it's `settings`.
|
162
130
|
|
163
131
|
```ruby
|
164
|
-
|
165
|
-
|
166
|
-
|
132
|
+
attribute_schema :settings, delegated: true, strict: false do
|
133
|
+
field :opted_in, :boolean, default: true
|
134
|
+
end
|
167
135
|
```
|
168
136
|
|
169
137
|
```ruby
|
@@ -175,7 +143,6 @@ user.foo # => NoMethodError
|
|
175
143
|
```
|
176
144
|
|
177
145
|
### Overriding
|
178
|
-
|
179
146
|
Schemattr provides the ability to specify your own attribute class. By doing so you can provide your own getters and
|
180
147
|
setters and do more complex logic. In this example we're providing the inverse of `opted_in` with an `opted_out` psuedo
|
181
148
|
field.
|
@@ -188,15 +155,15 @@ class UserSettings < Schemattr::Attribute
|
|
188
155
|
alias_method :opted_out, :opted_out?
|
189
156
|
|
190
157
|
def opted_out=(val)
|
191
|
-
opted_in = !val
|
158
|
+
self.opted_in = !val
|
192
159
|
end
|
193
160
|
end
|
194
161
|
```
|
195
162
|
|
196
163
|
```ruby
|
197
|
-
|
198
|
-
|
199
|
-
|
164
|
+
attribute_schema :settings, class: UserSettings do
|
165
|
+
field :opted_in, :boolean, default: true
|
166
|
+
end
|
200
167
|
```
|
201
168
|
|
202
169
|
```ruby
|
@@ -211,7 +178,6 @@ Our custom `opted_out` psuedo field won't be persisted, because it's not a defin
|
|
211
178
|
existing field that is persisted (`opted_in`).
|
212
179
|
|
213
180
|
#### Getters and setters
|
214
|
-
|
215
181
|
When overriding the attribute class with your own, you can provide your own custom getters and setters as well. These
|
216
182
|
will not be overridden by whatever Schemattr thinks they should do. Take this example, where when someone turns on or
|
217
183
|
off a setting we want to subscribe/unsubscribe them to an email list via a third party.
|
@@ -233,17 +199,16 @@ end
|
|
233
199
|
*Note*: This is not a real world scenario but serves our purposes of describing an example.
|
234
200
|
|
235
201
|
### Renaming fields
|
236
|
-
|
237
202
|
Schemattr makes it easy to rename fields as well. Let's say you've got a field named `opted_in`, as the examples have
|
238
203
|
shown thus far. But you've added new email lists, and you think `opted_in` is too vague. Like, opted in for what?
|
239
204
|
|
240
205
|
We can create a new field that is correctly named, and specify what attribute we want to pull the value from.
|
241
206
|
|
242
207
|
```ruby
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
208
|
+
attribute_schema :settings do
|
209
|
+
# field :opted_in, :boolean, default: true
|
210
|
+
field :email_list_beginner, :boolean, from: :opted_in, default: true
|
211
|
+
end
|
247
212
|
```
|
248
213
|
|
249
214
|
Specifying the `from: :opted_in` option will tell Schemattr to look for the value that may have already been defined in
|
@@ -260,9 +225,9 @@ Let's say we want to be able to be able to easily query users who have opted in.
|
|
260
225
|
leave it, as the case may be) on the users table.
|
261
226
|
|
262
227
|
```ruby
|
263
|
-
|
264
|
-
|
265
|
-
|
228
|
+
attribute_schema :settings do
|
229
|
+
field :email_list_beginner, :boolean, default: true, sync: :opted_in
|
230
|
+
end
|
266
231
|
```
|
267
232
|
|
268
233
|
```ruby
|
@@ -281,9 +246,7 @@ things in sync easier. The second issue can arise is when this attribute is set
|
|
281
246
|
using things like `user.update_column(:opted_in, false)`, and `User.update_all(opted_in: false)` will allow things to
|
282
247
|
get out of sync.
|
283
248
|
|
284
|
-
|
285
249
|
## Querying a JSON column
|
286
|
-
|
287
250
|
This has come up a little bit, and so it's worth documenting -- though it has very little to do with Schemattr. When you
|
288
251
|
have a JSON column in postgres, you can query values from within that column in various ways.
|
289
252
|
|
@@ -295,13 +258,9 @@ User.where("(settings->>'opted_in')::boolean") # boolean query
|
|
295
258
|
User.where("settings->>'string_value' = ?", "some string") # string query
|
296
259
|
```
|
297
260
|
|
298
|
-
|
299
261
|
## License
|
262
|
+
Licensed under the [MIT License](http://creativecommons.org/licenses/MIT/)
|
300
263
|
|
301
|
-
|
302
|
-
|
303
|
-
Copyright 2015 [Mode Set](https://github.com/modeset)
|
304
|
-
|
264
|
+
Copyright 2019 [jejacks0n](https://github.com/jejacks0n)
|
305
265
|
|
306
266
|
## Make Code Not War
|
307
|
-

|
@@ -23,7 +23,7 @@ module Schemattr
|
|
23
23
|
end
|
24
24
|
|
25
25
|
define_method "#{name}" do
|
26
|
-
|
26
|
+
schemaless_attributes[name] ||= attribute_schema.attribute_class.new(self, name, options[:strict] == false)
|
27
27
|
end
|
28
28
|
end
|
29
29
|
end
|
@@ -33,14 +33,14 @@ module Schemattr
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def reload(*_args)
|
36
|
-
|
36
|
+
schemaless_attributes.keys.each { |name| schemaless_attributes[name] = nil }
|
37
37
|
super
|
38
38
|
end
|
39
39
|
|
40
40
|
private
|
41
41
|
|
42
|
-
|
43
|
-
|
44
|
-
|
42
|
+
def schemaless_attributes
|
43
|
+
@_schemaless_attributes ||= {}
|
44
|
+
end
|
45
45
|
end
|
46
46
|
end
|
data/lib/schemattr/attribute.rb
CHANGED
@@ -23,38 +23,38 @@ module Schemattr
|
|
23
23
|
|
24
24
|
private
|
25
25
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
def method_missing(m, *args)
|
27
|
+
if @allow_arbitrary_attributes
|
28
|
+
self[$1] = args[0] if args.length == 1 && /^(\w+)=$/ =~ m
|
29
|
+
self[m.to_s.gsub(/\?$/, "")]
|
30
|
+
else
|
31
|
+
raise NoMethodError, "undefined method '#{m}' for #{self.class}"
|
32
|
+
end
|
32
33
|
end
|
33
|
-
end
|
34
34
|
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
35
|
+
def migrate_value(val, from)
|
36
|
+
return val unless from
|
37
|
+
if (old_val = self[from]).nil?
|
38
|
+
val
|
39
|
+
else
|
40
|
+
@hash.delete(from.to_s)
|
41
|
+
old_val
|
42
|
+
end
|
42
43
|
end
|
43
|
-
end
|
44
44
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
45
|
+
def sync_value(val, to)
|
46
|
+
model[to] = val if to
|
47
|
+
val
|
48
|
+
end
|
49
49
|
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
50
|
+
def []=(key, val)
|
51
|
+
hash[key.to_s] = val
|
52
|
+
model[attr_name] = hash
|
53
|
+
val
|
54
|
+
end
|
55
55
|
|
56
|
-
|
57
|
-
|
58
|
-
|
56
|
+
def [](key)
|
57
|
+
hash[key.to_s]
|
58
|
+
end
|
59
59
|
end
|
60
60
|
end
|
data/lib/schemattr/dsl.rb
CHANGED
@@ -14,73 +14,71 @@ module Schemattr
|
|
14
14
|
|
15
15
|
protected
|
16
16
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
17
|
+
def field(name, type, options = {})
|
18
|
+
if respond_to?(type, true)
|
19
|
+
send(type, name, options)
|
20
|
+
else
|
21
|
+
define(name, false, options)
|
22
|
+
end
|
22
23
|
end
|
23
|
-
end
|
24
24
|
|
25
|
-
|
26
|
-
|
27
|
-
|
25
|
+
def string(name, options = {})
|
26
|
+
define name, false, options, setter: lambda { |val| sync_value(self[name] = val.to_s, options[:sync]) }
|
27
|
+
end
|
28
28
|
|
29
|
-
|
30
|
-
|
31
|
-
|
29
|
+
def integer(name, options = {})
|
30
|
+
define name, false, options, setter: lambda { |val| sync_value(self[name] = val.to_i, options[:sync]) }
|
31
|
+
end
|
32
32
|
|
33
|
-
|
34
|
-
|
35
|
-
|
33
|
+
def float(name, options = {})
|
34
|
+
define name, false, options, setter: lambda { |val| sync_value(self[name] = val.to_f, options[:sync]) }
|
35
|
+
end
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
37
|
+
def datetime(name, options = {})
|
38
|
+
define name, false, options
|
39
|
+
end
|
40
40
|
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
def date(name, options = {})
|
42
|
+
define name, false, options
|
43
|
+
end
|
44
44
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
45
|
+
def boolean(name, options = {})
|
46
|
+
define name, true, options, setter: lambda { |val|
|
47
|
+
bool = ActiveRecord::Type::Boolean.new.deserialize(val)
|
48
|
+
sync_value(self[name] = bool, options[:sync])
|
49
|
+
}
|
50
|
+
end
|
51
51
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
52
|
+
alias_method :text, :string
|
53
|
+
alias_method :bigint, :integer
|
54
|
+
alias_method :decimal, :float
|
55
|
+
alias_method :time, :datetime
|
56
56
|
|
57
57
|
private
|
58
58
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
59
|
+
def define(name, boolean, options, blocks = {})
|
60
|
+
setter = blocks[:setter] || lambda { sync_value(self[name] = val, options[:sync]) }
|
61
|
+
getter = blocks[:getter] || lambda { migrate_value(self[name], options[:from]) }
|
62
|
+
default_for(name, options[:default])
|
63
|
+
method_for("#{name}=", options[:sync], &setter)
|
64
|
+
method_for(name, options[:sync], &getter)
|
65
|
+
alias_for("#{name}?", name, options[:sync]) if boolean
|
66
|
+
end
|
67
67
|
|
68
|
-
|
69
|
-
|
70
|
-
|
68
|
+
def default_for(name, default)
|
69
|
+
@defaults[name.to_s] = default
|
70
|
+
end
|
71
71
|
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
72
|
+
def method_for(name, delegated = false, &block)
|
73
|
+
@delegated.push(name.to_s) if delegated
|
74
|
+
unless attribute_class.instance_methods.include?(name.to_sym)
|
75
|
+
attribute_class.send(:define_method, name, &block)
|
76
|
+
end
|
76
77
|
end
|
77
|
-
end
|
78
78
|
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
attribute_class.send(:alias_method, new, old)
|
79
|
+
def alias_for(new, old, delegated)
|
80
|
+
@delegated.push(new.to_s) if delegated
|
81
|
+
attribute_class.send(:alias_method, new, old) unless attribute_class.instance_methods.include?(new.to_sym)
|
83
82
|
end
|
84
|
-
end
|
85
83
|
end
|
86
84
|
end
|
data/lib/schemattr/version.rb
CHANGED
metadata
CHANGED
@@ -1,18 +1,19 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: schemattr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0
|
4
|
+
version: 0.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- jejacks0n
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2019-04-14 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
13
|
+
description: Write schema-less attributes in ActiveRecord using a helpful and flexible
|
14
|
+
DSL.
|
14
15
|
email:
|
15
|
-
-
|
16
|
+
- jejacks0n@gmail.com
|
16
17
|
executables: []
|
17
18
|
extensions: []
|
18
19
|
extra_rdoc_files: []
|
@@ -24,7 +25,7 @@ files:
|
|
24
25
|
- lib/schemattr/attribute.rb
|
25
26
|
- lib/schemattr/dsl.rb
|
26
27
|
- lib/schemattr/version.rb
|
27
|
-
homepage:
|
28
|
+
homepage: http://github.com/jejacks0n/bitbot
|
28
29
|
licenses:
|
29
30
|
- MIT
|
30
31
|
metadata: {}
|
@@ -34,9 +35,9 @@ require_paths:
|
|
34
35
|
- lib
|
35
36
|
required_ruby_version: !ruby/object:Gem::Requirement
|
36
37
|
requirements:
|
37
|
-
- - "
|
38
|
+
- - "~>"
|
38
39
|
- !ruby/object:Gem::Version
|
39
|
-
version: '
|
40
|
+
version: '2.4'
|
40
41
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
42
|
requirements:
|
42
43
|
- - ">="
|
@@ -47,5 +48,5 @@ rubyforge_project:
|
|
47
48
|
rubygems_version: 2.7.6
|
48
49
|
signing_key:
|
49
50
|
specification_version: 4
|
50
|
-
summary: ''
|
51
|
+
summary: 'Schemattr: Simple schema-less column definitions for ActiveRecord'
|
51
52
|
test_files: []
|