schemattr 0.0.1
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 +7 -0
- data/MIT.LICENSE +22 -0
- data/README.md +307 -0
- data/lib/schemattr.rb +6 -0
- data/lib/schemattr/active_record_extension.rb +46 -0
- data/lib/schemattr/attribute.rb +60 -0
- data/lib/schemattr/dsl.rb +86 -0
- data/lib/schemattr/version.rb +3 -0
- metadata +51 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 3162b47b2a1acf700ca81a8ee47222391cf0711ad4b5194b9d5c46cf19584162
|
4
|
+
data.tar.gz: 12b294d20e189e9d16bb23644a26199829bccd3c42ba1b7662aea33b79ae4662
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 1134ffee2611f6f203f3891fe21088121a80f7ceb6a02197dd8cf4aa97b114d575a82c6652217faafb840ecbf6dbf904d61169984ee3cf7fb499dcb035f1a2fe
|
7
|
+
data.tar.gz: dee2aa5f9b8120885f8a0422de822e5ced1bf60103668149963d8ce8866c535db0915123427968af0b3b85653fb18626540190bad2ca16120da5bd79c7385c26
|
data/MIT.LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright 2015 Jeremy Jackson / ModeSet
|
2
|
+
|
3
|
+
https://github.com/modeset/schemattr
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,307 @@
|
|
1
|
+
Schemattr
|
2
|
+
=========
|
3
|
+
|
4
|
+
[](http://badge.fury.io/rb/schemattr)
|
5
|
+
[](https://travis-ci.org/modeset/schemattr)
|
6
|
+
[](https://codeclimate.com/github/modeset/schemattr)
|
7
|
+
[](https://codeclimate.com/github/modeset/schemattr)
|
8
|
+
[](http://opensource.org/licenses/MIT)
|
9
|
+
[](https://gemnasium.com/modeset/schemattr)
|
10
|
+
|
11
|
+
Schemattr is an ActiveRecord extension that provides a helpful schema-less attribute DSL. It can be used to define a
|
12
|
+
simple schema for a single attribute that can change over time without having to migrate existing data.
|
13
|
+
|
14
|
+
### Background
|
15
|
+
|
16
|
+
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
|
+
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
|
+
setting and migrate. Ship it, we're done with that feature.
|
19
|
+
|
20
|
+
Ok, so now it's a year later and your project has grown a lot. You have over 4MM users, and in that year there's been a
|
21
|
+
lot of business requirements that necessitated new settings for users. Each setting has been added ad hoc, as needed --
|
22
|
+
there's now three email lists, and users can opt in and out of each one independently.
|
23
|
+
|
24
|
+
This is where Schemattr comes in. Adding a new setting, or changing the name of an existing setting is non-trivial at
|
25
|
+
this point of your projects life-cycle, and requires a multi-step migration. You'll need to add the column (don't set a
|
26
|
+
default for that column, because that locks the table!), then you'll need to update each record in batches, once
|
27
|
+
complete you'll set a default, and finally you'll want to add a null constraint. This can become a hassle, and
|
28
|
+
introduces complexity to your deployments.
|
29
|
+
|
30
|
+
Schemattr allows you to move all of those settings into a single JSON (or similarly serialized) column. It can behave as
|
31
|
+
though the column is defined on the record itself through delegation, allows providing overrides for getter/setter
|
32
|
+
methods, can keep a real column synced with one if its fields, and more.
|
33
|
+
|
34
|
+
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
|
+
schema and setting a default right there in the code. No migrations, no hassles, easy deployment.
|
36
|
+
|
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
|
+
## Installation
|
51
|
+
|
52
|
+
Add it to your Gemfile:
|
53
|
+
```ruby
|
54
|
+
gem 'schemattr'
|
55
|
+
```
|
56
|
+
|
57
|
+
And then execute:
|
58
|
+
```shell
|
59
|
+
$ bundle
|
60
|
+
```
|
61
|
+
|
62
|
+
Or install it yourself as:
|
63
|
+
```shell
|
64
|
+
$ gem install schemattr
|
65
|
+
```
|
66
|
+
|
67
|
+
|
68
|
+
## Usage
|
69
|
+
|
70
|
+
In the examples we assume there's already a User model and table.
|
71
|
+
|
72
|
+
First, let's create a migration to add your schema-less attribute. In postgres you can use a JSON column. We use the
|
73
|
+
postgres JSON type as our example because the JSON type allows queries and indexing, and hstore does annoying things to
|
74
|
+
booleans. We don't need to set our default value to an empty object because Schemattr handles that for us.
|
75
|
+
|
76
|
+
*Note*: If you're using a different database provider, like sqlite3 for instance, you can use a text column and tell
|
77
|
+
ActiveRecord to serialize that column (e.g. `serialize :settings` in your model). Though, you won't be able to easily
|
78
|
+
query in these cases so consider your options.
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
class AddSettingsToUsers < ActiveRecord::Migration
|
82
|
+
def change
|
83
|
+
add_column :users, :settings, :json
|
84
|
+
end
|
85
|
+
end
|
86
|
+
```
|
87
|
+
|
88
|
+
Schemattr hooks into ActiveRecord and provides the `attribute_schema` method on any model that inherits from
|
89
|
+
ActiveRecord. This method provides a simple DSL that allows you to define the schema for the attribute. You can define
|
90
|
+
various fields, specify their types, defaults if needed, and additional options.
|
91
|
+
|
92
|
+
```ruby
|
93
|
+
class User < ActiveRecord::Base
|
94
|
+
attribute_schema :settings do
|
95
|
+
boolean :opted_in, default: true
|
96
|
+
boolean :email_list_advanced, default: false
|
97
|
+
boolean :email_list_expert, default: false
|
98
|
+
end
|
99
|
+
end
|
100
|
+
```
|
101
|
+
|
102
|
+
Notice that we've done nothing else, but we already have a working version of what we want. It's shippable.
|
103
|
+
|
104
|
+
```
|
105
|
+
user = User.new
|
106
|
+
user.settings.opted_in? # => true
|
107
|
+
user.settings.email_group_advanced? # => false
|
108
|
+
user.settings.email_group_expert? # => false
|
109
|
+
```
|
110
|
+
|
111
|
+
If we save the user at this point, these settings will be persisted. We can also make changes to them at this point, and
|
112
|
+
when they're persisted they'll include whatever we've changed them to be. If we don't save the user, that's ok too --
|
113
|
+
they'll just be the defaults if we ever ask again.
|
114
|
+
|
115
|
+
### Field types
|
116
|
+
|
117
|
+
The various field types are outlined below. When you define a string field for instance, the value will be coerced into
|
118
|
+
a string at the time that it's set.
|
119
|
+
|
120
|
+
type | description
|
121
|
+
---------|--------------
|
122
|
+
boolean | boolean value
|
123
|
+
string | string value
|
124
|
+
text | same as string type
|
125
|
+
integer | number value
|
126
|
+
bigint | same as integer
|
127
|
+
float | floating point number value
|
128
|
+
decimal | same as float
|
129
|
+
datetime | datetime object
|
130
|
+
time | time object (stored the same as datetime)
|
131
|
+
date | date object
|
132
|
+
|
133
|
+
You can additionally define your own types using `field :foo, :custom_type` and there will no coercion at the time the
|
134
|
+
field is set -- this is intended for when you need something that doesn't care what type it is. This generally makes it
|
135
|
+
harder to use in forms however.
|
136
|
+
|
137
|
+
### Delegating
|
138
|
+
|
139
|
+
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
|
+
delegated. This adds delegation of the methods that exist on settings to the User instances.
|
141
|
+
|
142
|
+
```ruby
|
143
|
+
attribute_schema :settings, delegated: true do
|
144
|
+
field :opted_in, :boolean, default: true
|
145
|
+
end
|
146
|
+
```
|
147
|
+
|
148
|
+
```ruby
|
149
|
+
user = User.new
|
150
|
+
user.opted_in = false
|
151
|
+
user.settings.opted_in? # => false
|
152
|
+
user.opted_in? # => false
|
153
|
+
```
|
154
|
+
|
155
|
+
### Strict mode vs. arbitrary fields
|
156
|
+
|
157
|
+
By default, Schemattr doesn't allow arbitrary fields to be added, but it supports it. When strict mode is disabled, it
|
158
|
+
allows any arbitrary field to be set or asked for.
|
159
|
+
|
160
|
+
*Note*: When delegated and strict mode is disabled, you cannot set arbitrary fields on the model directly and must
|
161
|
+
access them through the attribute that you've defined -- in our case, it's `settings`.
|
162
|
+
|
163
|
+
```ruby
|
164
|
+
attribute_schema :settings, delegated: true, strict: false do
|
165
|
+
field :opted_in, :boolean, default: true
|
166
|
+
end
|
167
|
+
```
|
168
|
+
|
169
|
+
```ruby
|
170
|
+
user = User.new
|
171
|
+
user.settings.foo # => nil
|
172
|
+
user.settings.foo = "bar"
|
173
|
+
user.settings.foo # => "bar"
|
174
|
+
user.foo # => NoMethodError
|
175
|
+
```
|
176
|
+
|
177
|
+
### Overriding
|
178
|
+
|
179
|
+
Schemattr provides the ability to specify your own attribute class. By doing so you can provide your own getters and
|
180
|
+
setters and do more complex logic. In this example we're providing the inverse of `opted_in` with an `opted_out` psuedo
|
181
|
+
field.
|
182
|
+
|
183
|
+
```ruby
|
184
|
+
class UserSettings < Schemattr::Attribute
|
185
|
+
def opted_out
|
186
|
+
!self[:opted_in]
|
187
|
+
end
|
188
|
+
alias_method :opted_out, :opted_out?
|
189
|
+
|
190
|
+
def opted_out=(val)
|
191
|
+
opted_in = !val
|
192
|
+
end
|
193
|
+
end
|
194
|
+
```
|
195
|
+
|
196
|
+
```ruby
|
197
|
+
attribute_schema :settings, class: UserSettings do
|
198
|
+
field :opted_in, :boolean, default: true
|
199
|
+
end
|
200
|
+
```
|
201
|
+
|
202
|
+
```ruby
|
203
|
+
user = User.new
|
204
|
+
user.settings.opted_out? # => false
|
205
|
+
user.settings.opted_in? # => true
|
206
|
+
user.settings.opted_out = true
|
207
|
+
user.settings.opted_in? # => false
|
208
|
+
```
|
209
|
+
|
210
|
+
Our custom `opted_out` psuedo field won't be persisted, because it's not a defined field and is just an accessor for an
|
211
|
+
existing field that is persisted (`opted_in`).
|
212
|
+
|
213
|
+
#### Getters and setters
|
214
|
+
|
215
|
+
When overriding the attribute class with your own, you can provide your own custom getters and setters as well. These
|
216
|
+
will not be overridden by whatever Schemattr thinks they should do. Take this example, where when someone turns on or
|
217
|
+
off a setting we want to subscribe/unsubscribe them to an email list via a third party.
|
218
|
+
|
219
|
+
```ruby
|
220
|
+
class UserSettings < Schemattr::Attribute
|
221
|
+
def opted_in=(val)
|
222
|
+
if val
|
223
|
+
SubscribeEmail.perform_async(model.email)
|
224
|
+
else
|
225
|
+
UnsubscribeEmail.perform_async(model.email)
|
226
|
+
end
|
227
|
+
# there is no super, so you must set it manually.
|
228
|
+
self[:opted_in] = val
|
229
|
+
end
|
230
|
+
end
|
231
|
+
```
|
232
|
+
|
233
|
+
*Note*: This is not a real world scenario but serves our purposes of describing an example.
|
234
|
+
|
235
|
+
### Renaming fields
|
236
|
+
|
237
|
+
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
|
+
shown thus far. But you've added new email lists, and you think `opted_in` is too vague. Like, opted in for what?
|
239
|
+
|
240
|
+
We can create a new field that is correctly named, and specify what attribute we want to pull the value from.
|
241
|
+
|
242
|
+
```ruby
|
243
|
+
attribute_schema :settings do
|
244
|
+
# field :opted_in, :boolean, default: true
|
245
|
+
field :email_list_beginner, :boolean, from: :opted_in, default: true
|
246
|
+
end
|
247
|
+
```
|
248
|
+
|
249
|
+
Specifying the `from: :opted_in` option will tell Schemattr to look for the value that may have already been defined in
|
250
|
+
`opted_in` before the rename. This allows for slow migrations, but you can also write a migration to ensure this happens
|
251
|
+
quickly.
|
252
|
+
|
253
|
+
### Syncing attributes
|
254
|
+
|
255
|
+
There's a down side to keeping some things internal to this settings attribute. You can query JSON types in postgres,
|
256
|
+
but it may not be optimal given your indexing strategy. Schemattr provides a mechanism to keep an attribute in sync, but
|
257
|
+
it's important to understand it and handle it with care.
|
258
|
+
|
259
|
+
Let's say we want to be able to be able to easily query users who have opted in. We can add the `opted_in` column to (or
|
260
|
+
leave it, as the case may be) on the users table.
|
261
|
+
|
262
|
+
```ruby
|
263
|
+
attribute_schema :settings do
|
264
|
+
field :email_list_beginner, :boolean, default: true, sync: :opted_in
|
265
|
+
end
|
266
|
+
```
|
267
|
+
|
268
|
+
```ruby
|
269
|
+
user = User.new
|
270
|
+
user.settings.email_list_beginner = false
|
271
|
+
user.read_attribute(:opted_in) # => false
|
272
|
+
user.save!
|
273
|
+
User.where(opted_in: false) # => user
|
274
|
+
```
|
275
|
+
|
276
|
+
By adding the sync option to the field, Schemattr will try to keep that attribute in sync. There are some caveats that
|
277
|
+
can lead to confusion however.
|
278
|
+
|
279
|
+
First, when you do this, it forces delegation of `user.opted_in` to `user.settings.opted_in` -- this is to make keeping
|
280
|
+
things in sync easier. The second issue can arise is when this attribute is set directly in the database -- which means
|
281
|
+
using things like `user.update_column(:opted_in, false)`, and `User.update_all(opted_in: false)` will allow things to
|
282
|
+
get out of sync.
|
283
|
+
|
284
|
+
|
285
|
+
## Querying a JSON column
|
286
|
+
|
287
|
+
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
|
+
have a JSON column in postgres, you can query values from within that column in various ways.
|
289
|
+
|
290
|
+
[The documentation](http://www.postgresql.org/docs/9.4/static/functions-json.html) can be a little hard to grok, so
|
291
|
+
these are the common scenarios that we've used.
|
292
|
+
|
293
|
+
```
|
294
|
+
User.where("(settings->>'opted_in')::boolean") # boolean query
|
295
|
+
User.where("settings->>'string_value' = ?", "some string") # string query
|
296
|
+
```
|
297
|
+
|
298
|
+
|
299
|
+
## License
|
300
|
+
|
301
|
+
Licensed under the [MIT License](http://creativecommons.org/licenses/MIT)
|
302
|
+
|
303
|
+
Copyright 2015 [Mode Set](https://github.com/modeset)
|
304
|
+
|
305
|
+
|
306
|
+
## Make Code Not War
|
307
|
+

|
data/lib/schemattr.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
module Schemattr
|
2
|
+
module ActiveRecordExtension
|
3
|
+
module ClassMethods
|
4
|
+
def attribute_schema(name, options = {}, &block)
|
5
|
+
raise ArgumentError, "No schema provided, block expected for schemaless_attribute." unless block_given?
|
6
|
+
|
7
|
+
name = name.to_sym
|
8
|
+
attribute_schema = DSL.new(options[:class], &block)
|
9
|
+
if options[:delegated]
|
10
|
+
delegate(*attribute_schema.attribute_class.instance_methods(false), to: name)
|
11
|
+
else
|
12
|
+
delegate(*attribute_schema.delegated, to: name)
|
13
|
+
end
|
14
|
+
|
15
|
+
define_method "#{name}=" do |val|
|
16
|
+
raise ArgumentError, "Setting #{name} requires a hash" unless val.is_a?(Hash)
|
17
|
+
delegator = send(name)
|
18
|
+
val.each do |k, v|
|
19
|
+
endpoint = options[:delegated] && self.respond_to?("#{k}=") ? self : delegator
|
20
|
+
endpoint.send("#{k}=", v)
|
21
|
+
end
|
22
|
+
val
|
23
|
+
end
|
24
|
+
|
25
|
+
define_method "#{name}" do
|
26
|
+
_schemaless_attributes[name] ||= attribute_schema.attribute_class.new(self, name, options[:strict] == false)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.included(base = nil, &_block)
|
32
|
+
base.extend(ClassMethods)
|
33
|
+
end
|
34
|
+
|
35
|
+
def reload(*_args)
|
36
|
+
_schemaless_attributes.keys.each { |name| _schemaless_attributes[name] = nil }
|
37
|
+
super
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def _schemaless_attributes
|
43
|
+
@_schemaless_attributes ||= {}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module Schemattr
|
2
|
+
class Attribute
|
3
|
+
attr_accessor :model, :attr_name, :hash
|
4
|
+
|
5
|
+
def initialize(model, attr_name, allow_arbitrary_attributes = false)
|
6
|
+
@model = model
|
7
|
+
@attr_name = attr_name
|
8
|
+
@allow_arbitrary_attributes = allow_arbitrary_attributes
|
9
|
+
@hash = defaults.merge(model[attr_name] || {})
|
10
|
+
end
|
11
|
+
|
12
|
+
def field_names
|
13
|
+
(self.class.defaults.keys || []).map { |k| k.to_sym }
|
14
|
+
end
|
15
|
+
|
16
|
+
def defaults
|
17
|
+
self.class.defaults
|
18
|
+
end
|
19
|
+
|
20
|
+
def as_json(*args)
|
21
|
+
@hash
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
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
|
33
|
+
end
|
34
|
+
|
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
|
43
|
+
end
|
44
|
+
|
45
|
+
def sync_value(val, to)
|
46
|
+
model[to] = val if to
|
47
|
+
val
|
48
|
+
end
|
49
|
+
|
50
|
+
def []=(key, val)
|
51
|
+
hash[key.to_s] = val
|
52
|
+
model[attr_name] = hash
|
53
|
+
val
|
54
|
+
end
|
55
|
+
|
56
|
+
def [](key)
|
57
|
+
hash[key.to_s]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,86 @@
|
|
1
|
+
module Schemattr
|
2
|
+
class DSL
|
3
|
+
attr_accessor :attribute_class, :delegated, :defaults
|
4
|
+
|
5
|
+
def initialize(klass_override = nil, &block)
|
6
|
+
@attribute_class = Class.new(klass_override || Attribute)
|
7
|
+
@delegated = []
|
8
|
+
@defaults = defaults = {}
|
9
|
+
|
10
|
+
instance_eval(&block)
|
11
|
+
|
12
|
+
@attribute_class.define_singleton_method("defaults") { defaults }
|
13
|
+
end
|
14
|
+
|
15
|
+
protected
|
16
|
+
|
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
|
23
|
+
end
|
24
|
+
|
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
|
+
|
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
|
+
|
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
|
+
|
37
|
+
def datetime(name, options = {})
|
38
|
+
_define name, false, options
|
39
|
+
end
|
40
|
+
|
41
|
+
def date(name, options = {})
|
42
|
+
_define name, false, options
|
43
|
+
end
|
44
|
+
|
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
|
+
|
52
|
+
alias_method :text, :string
|
53
|
+
alias_method :bigint, :integer
|
54
|
+
alias_method :decimal, :float
|
55
|
+
alias_method :time, :datetime
|
56
|
+
|
57
|
+
private
|
58
|
+
|
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(name, options[:default])
|
63
|
+
_method("#{name}=", options[:sync], &setter)
|
64
|
+
_method(name, options[:sync], &getter)
|
65
|
+
_alias("#{name}?", name, options[:sync]) if boolean
|
66
|
+
end
|
67
|
+
|
68
|
+
def _default(name, default)
|
69
|
+
@defaults[name.to_s] = default
|
70
|
+
end
|
71
|
+
|
72
|
+
def _method(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
|
77
|
+
end
|
78
|
+
|
79
|
+
def _alias(new, old, delegated)
|
80
|
+
@delegated.push(new.to_s) if delegated
|
81
|
+
unless attribute_class.instance_methods.include?(new.to_sym)
|
82
|
+
attribute_class.send(:alias_method, new, old)
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
metadata
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: schemattr
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- jejacks0n
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2018-11-16 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: ''
|
14
|
+
email:
|
15
|
+
- info@modeset.com
|
16
|
+
executables: []
|
17
|
+
extensions: []
|
18
|
+
extra_rdoc_files: []
|
19
|
+
files:
|
20
|
+
- MIT.LICENSE
|
21
|
+
- README.md
|
22
|
+
- lib/schemattr.rb
|
23
|
+
- lib/schemattr/active_record_extension.rb
|
24
|
+
- lib/schemattr/attribute.rb
|
25
|
+
- lib/schemattr/dsl.rb
|
26
|
+
- lib/schemattr/version.rb
|
27
|
+
homepage: https://github.com/modeset/schemattr
|
28
|
+
licenses:
|
29
|
+
- MIT
|
30
|
+
metadata: {}
|
31
|
+
post_install_message:
|
32
|
+
rdoc_options: []
|
33
|
+
require_paths:
|
34
|
+
- lib
|
35
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
41
|
+
requirements:
|
42
|
+
- - ">="
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
requirements: []
|
46
|
+
rubyforge_project:
|
47
|
+
rubygems_version: 2.7.6
|
48
|
+
signing_key:
|
49
|
+
specification_version: 4
|
50
|
+
summary: ''
|
51
|
+
test_files: []
|