has-meta 0.8.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 +7 -0
- data/.gitignore +13 -0
- data/.rspec +3 -0
- data/.rvmrc +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +105 -0
- data/LICENSE.txt +21 -0
- data/README.md +169 -0
- data/Rakefile +8 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/db/migrate/1_has_meta_migration.rb +26 -0
- data/has-meta.gemspec +28 -0
- data/lib/has-meta.rb +53 -0
- data/lib/has_meta/data_mover.rb +109 -0
- data/lib/has_meta/dynamic_methods.rb +80 -0
- data/lib/has_meta/engine.rb +4 -0
- data/lib/has_meta/instance_methods.rb +34 -0
- data/lib/has_meta/meta_data.rb +92 -0
- data/lib/has_meta/meta_query.rb +85 -0
- data/lib/has_meta/query_methods.rb +18 -0
- data/lib/has_meta/version.rb +3 -0
- data/lib/tasks/data_mover.rake +10 -0
- metadata +123 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 928b405da392f2e0c10011167dfaff2d42b9a9fce7dba0cefd3266ebca9d05d9
|
|
4
|
+
data.tar.gz: 6501c857bf540e800b5de03027f47987e96c2beecab80ef385e7a3c91f036b45
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 2da0e9dd873bff4d454e58cd58650b22692c9cacf504421be84ebcfc00b7551f1c4b165d910d8c8e56d44bb75b62e011be11d817bae4f70897df18ed800c1205
|
|
7
|
+
data.tar.gz: d57dd3db2819d4b6027378d48ab21c5a086dcd89b7c6395c8ac984813bd2f72d4759ffbb3d8f12a3017765ed18ee4c3ccdd669ef49283d9227932d8404f4cc82
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.rvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
rvm use ruby-2.5.0@has-meta
|
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
has-meta (0.8.0)
|
|
5
|
+
activerecord (>= 4.2.8)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
actionpack (4.2.10)
|
|
11
|
+
actionview (= 4.2.10)
|
|
12
|
+
activesupport (= 4.2.10)
|
|
13
|
+
rack (~> 1.6)
|
|
14
|
+
rack-test (~> 0.6.2)
|
|
15
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
|
16
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
|
17
|
+
actionview (4.2.10)
|
|
18
|
+
activesupport (= 4.2.10)
|
|
19
|
+
builder (~> 3.1)
|
|
20
|
+
erubis (~> 2.7.0)
|
|
21
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
|
22
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.3)
|
|
23
|
+
activemodel (4.2.10)
|
|
24
|
+
activesupport (= 4.2.10)
|
|
25
|
+
builder (~> 3.1)
|
|
26
|
+
activerecord (4.2.10)
|
|
27
|
+
activemodel (= 4.2.10)
|
|
28
|
+
activesupport (= 4.2.10)
|
|
29
|
+
arel (~> 6.0)
|
|
30
|
+
activesupport (4.2.10)
|
|
31
|
+
i18n (~> 0.7)
|
|
32
|
+
minitest (~> 5.1)
|
|
33
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
|
34
|
+
tzinfo (~> 1.1)
|
|
35
|
+
arel (6.0.4)
|
|
36
|
+
builder (3.2.3)
|
|
37
|
+
coderay (1.1.2)
|
|
38
|
+
concurrent-ruby (1.0.5)
|
|
39
|
+
crass (1.0.3)
|
|
40
|
+
diff-lcs (1.3)
|
|
41
|
+
erubis (2.7.0)
|
|
42
|
+
i18n (0.9.1)
|
|
43
|
+
concurrent-ruby (~> 1.0)
|
|
44
|
+
loofah (2.1.1)
|
|
45
|
+
crass (~> 1.0.2)
|
|
46
|
+
nokogiri (>= 1.5.9)
|
|
47
|
+
method_source (0.9.0)
|
|
48
|
+
mini_portile2 (2.3.0)
|
|
49
|
+
minitest (5.11.1)
|
|
50
|
+
nokogiri (1.8.1)
|
|
51
|
+
mini_portile2 (~> 2.3.0)
|
|
52
|
+
pry (0.11.3)
|
|
53
|
+
coderay (~> 1.1.0)
|
|
54
|
+
method_source (~> 0.9.0)
|
|
55
|
+
rack (1.6.8)
|
|
56
|
+
rack-test (0.6.3)
|
|
57
|
+
rack (>= 1.0)
|
|
58
|
+
rails-deprecated_sanitizer (1.0.3)
|
|
59
|
+
activesupport (>= 4.2.0.alpha)
|
|
60
|
+
rails-dom-testing (1.0.9)
|
|
61
|
+
activesupport (>= 4.2.0, < 5.0)
|
|
62
|
+
nokogiri (~> 1.6)
|
|
63
|
+
rails-deprecated_sanitizer (>= 1.0.1)
|
|
64
|
+
rails-html-sanitizer (1.0.3)
|
|
65
|
+
loofah (~> 2.0)
|
|
66
|
+
railties (4.2.10)
|
|
67
|
+
actionpack (= 4.2.10)
|
|
68
|
+
activesupport (= 4.2.10)
|
|
69
|
+
rake (>= 0.8.7)
|
|
70
|
+
thor (>= 0.18.1, < 2.0)
|
|
71
|
+
rake (10.5.0)
|
|
72
|
+
rspec-core (3.7.1)
|
|
73
|
+
rspec-support (~> 3.7.0)
|
|
74
|
+
rspec-expectations (3.7.0)
|
|
75
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
76
|
+
rspec-support (~> 3.7.0)
|
|
77
|
+
rspec-mocks (3.7.0)
|
|
78
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
79
|
+
rspec-support (~> 3.7.0)
|
|
80
|
+
rspec-rails (3.7.2)
|
|
81
|
+
actionpack (>= 3.0)
|
|
82
|
+
activesupport (>= 3.0)
|
|
83
|
+
railties (>= 3.0)
|
|
84
|
+
rspec-core (~> 3.7.0)
|
|
85
|
+
rspec-expectations (~> 3.7.0)
|
|
86
|
+
rspec-mocks (~> 3.7.0)
|
|
87
|
+
rspec-support (~> 3.7.0)
|
|
88
|
+
rspec-support (3.7.0)
|
|
89
|
+
sqlite3 (1.3.13)
|
|
90
|
+
thor (0.20.0)
|
|
91
|
+
thread_safe (0.3.6)
|
|
92
|
+
tzinfo (1.2.4)
|
|
93
|
+
thread_safe (~> 0.1)
|
|
94
|
+
|
|
95
|
+
PLATFORMS
|
|
96
|
+
ruby
|
|
97
|
+
|
|
98
|
+
DEPENDENCIES
|
|
99
|
+
has-meta!
|
|
100
|
+
pry
|
|
101
|
+
rspec-rails
|
|
102
|
+
sqlite3
|
|
103
|
+
|
|
104
|
+
BUNDLED WITH
|
|
105
|
+
1.16.1
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2017 ProTrainings, LLC
|
|
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,169 @@
|
|
|
1
|
+
# Has-Meta
|
|
2
|
+
A key/value store solution for Rails apps with bloated tables
|
|
3
|
+
|
|
4
|
+
[](https://travis-ci.org/ProTrainings/has-meta)
|
|
5
|
+
|
|
6
|
+
## Installation
|
|
7
|
+
|
|
8
|
+
Add this line to your application's Gemfile:
|
|
9
|
+
|
|
10
|
+
```ruby
|
|
11
|
+
gem 'has-meta'
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
And then execute:
|
|
15
|
+
|
|
16
|
+
$ bundle
|
|
17
|
+
|
|
18
|
+
Or install it yourself as:
|
|
19
|
+
|
|
20
|
+
$ gem install has-meta
|
|
21
|
+
|
|
22
|
+
Then, install migrations:
|
|
23
|
+
|
|
24
|
+
```ruby
|
|
25
|
+
rake has_meta_engine:install:migrations
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Finally, review the migrations and migrate:
|
|
29
|
+
|
|
30
|
+
```ruby
|
|
31
|
+
rails db:migrate
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Usage
|
|
35
|
+
|
|
36
|
+
### Declaring Meta Attributes
|
|
37
|
+
|
|
38
|
+
Suppose we have a model `Part` and only a minority of our parts records have the attribute `catalog_number` populated. We want to move `catalog_number` to our key/value store.
|
|
39
|
+
|
|
40
|
+
To create a new meta attribute on an Active Record model, add this line to your model:
|
|
41
|
+
|
|
42
|
+
```ruby
|
|
43
|
+
has_meta :catalog_number
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Or specify multiple meta attributes on the model:
|
|
47
|
+
|
|
48
|
+
```ruby
|
|
49
|
+
has_meta :catalog_number, :other_attribute
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
You may also choose to migrate existing data from a table:
|
|
53
|
+
|
|
54
|
+
$ rake "has_meta_engine:data_mover[parts, catalog_number, integer, catalog_number]"
|
|
55
|
+
|
|
56
|
+
Once that data is moved generate a migration to remove the column and run the migration:
|
|
57
|
+
|
|
58
|
+
$ rake generate migration RevmoveCatalogNumberFromParts catalog_number:integer
|
|
59
|
+
$ rake db:migrate
|
|
60
|
+
|
|
61
|
+
And finally, declare the meta attribute in the model
|
|
62
|
+
|
|
63
|
+
```ruby
|
|
64
|
+
has_meta :catalog_number
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Getting and Setting Meta Attributes
|
|
68
|
+
|
|
69
|
+
Now, we can use normal getters and setters to access the attribute:
|
|
70
|
+
|
|
71
|
+
```ruby
|
|
72
|
+
new_part = Part.create name: 'Fancy new part'
|
|
73
|
+
new_part.catalog_number = 12345
|
|
74
|
+
new_part.save
|
|
75
|
+
|
|
76
|
+
new_part.catalog_number
|
|
77
|
+
# => 12345
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
You can update the attribute any way you would with other attributes managed by Active Record:
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
new_part.update catalog_number: 67890
|
|
84
|
+
new_part.catalog_number # => 67890
|
|
85
|
+
|
|
86
|
+
new_part.attributes = {catalog_number: 12345}
|
|
87
|
+
new_part.catalog_number # => 12345
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
**NB**: _Declaring a meta attribute on a model creates a polymorphic relationship between the model and the MetaData model. Therefore, the parent model must be saved before assigning meta attributes._
|
|
91
|
+
|
|
92
|
+
Meta attributes may also represent an Active Record model. Perhaps some of our parts may conform to a uniform standard represented by class `Standard`. Just declare the meta attribute `:standard` and `has-meta` will treat the meta attribute as a one-to-one relation if the attribute corresponds to an Active Record model in your app.
|
|
93
|
+
|
|
94
|
+
```ruby
|
|
95
|
+
has_meta :catalog_number, :standard
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Now you can get or set the attribute using either object or the object id as you would with any other attribute:
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
new_standard = Standard.create name: 'Some great standard'
|
|
102
|
+
new_part.standard = new_standard
|
|
103
|
+
new_part.standard # => #<Standard id: 1, name: "Some great standard">
|
|
104
|
+
new_part.standard_id # => 1
|
|
105
|
+
|
|
106
|
+
newer_standard = Standard.create name 'An even better standard'
|
|
107
|
+
new_part.standard.id = newer_standard.id
|
|
108
|
+
new_part.standard # => #<Standard id: 2, name: "An even better standard">
|
|
109
|
+
new_part.standard_id # => 2
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Finding by meta attributes
|
|
113
|
+
|
|
114
|
+
`find_by_attribute_name` methods are provided for meta attributes. For attributes representing an Active Record model, use `find_by_attribute_id`:
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
Part.find_by_catalog_number 12345
|
|
118
|
+
# => #<Part id: 1, name: "Fancy new part">
|
|
119
|
+
|
|
120
|
+
Part.find_by_standard_id 2
|
|
121
|
+
# => #<Part id: 1, name: "Fancy new part">
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
You may also use `with_meta` method to return a scope of parts with correspoding meta attribute values:
|
|
125
|
+
|
|
126
|
+
```ruby
|
|
127
|
+
another_part = Part.create name: 'Another fancy new part'
|
|
128
|
+
another_part.update standard: new_standard
|
|
129
|
+
|
|
130
|
+
Part.with_meta standard: new_standard
|
|
131
|
+
# => #<ActiveRecord::Relation [#<Part id: 1, name: "Fancy new part">,
|
|
132
|
+
#<Part id: 2, name: "Another fancy new part">]>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
`with_meta` accepts the `any: true` option to match any condition provided:
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
yet_another_part = Part.create name: 'Yet another fancy new part'
|
|
139
|
+
yet_another_part.update catalog_number: 12345
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
Part.with_meta({standard: new_standard, catalog_number: 12345}, any: true)
|
|
143
|
+
# => #<ActiveRecord::Relation [#<Part id: 1, name: "Fancy new part">,
|
|
144
|
+
#<Part id: 2, name: "Another fancy new part">,
|
|
145
|
+
#<Part id: 3, name: "Yet another fancy new part">]>
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
Calling `excluding_meta` will return all records not meeting the criteria:
|
|
149
|
+
|
|
150
|
+
```ruby
|
|
151
|
+
Part.excluding_meta catalog_number: 12345
|
|
152
|
+
# => #<ActiveRecord::Relation [#<Part id: 2, name: "Another fancy new part">]>
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## TODO/Known Issues
|
|
156
|
+
`has-meta` was developed for Active Record 4.2+ and MySQL 5.5. PRs for supporting earlier versions of Active Record and/or PostgreSQL are welcome!
|
|
157
|
+
|
|
158
|
+
## Contributing
|
|
159
|
+
|
|
160
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/protrainings/has-meta.
|
|
161
|
+
|
|
162
|
+
## License
|
|
163
|
+
|
|
164
|
+
Has-Meta is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
|
165
|
+
|
|
166
|
+
## About ProTrainings
|
|
167
|
+
Has-Meta was written by [Dan Drust](https://www.github.com/dandrust). It is maintained and funded by Protrainings, LLC. Has-Meta, names, and logos are copyright ProTrainings, LLC.
|
|
168
|
+
|
|
169
|
+
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "has/meta"
|
|
5
|
+
|
|
6
|
+
# You can add fixtures and/or initialization code here to make experimenting
|
|
7
|
+
# with your gem easier. You can also use a different console, if you like.
|
|
8
|
+
|
|
9
|
+
# (If you use this, don't forget to add pry to your Gemfile!)
|
|
10
|
+
# require "pry"
|
|
11
|
+
# Pry.start
|
|
12
|
+
|
|
13
|
+
require "irb"
|
|
14
|
+
IRB.start(__FILE__)
|
data/bin/setup
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
class HasMetaMigration < ActiveRecord::Migration[4.2]
|
|
2
|
+
|
|
3
|
+
def self.up
|
|
4
|
+
create_table :meta_data do |t|
|
|
5
|
+
|
|
6
|
+
t.references :meta_model, polymorphic: true
|
|
7
|
+
|
|
8
|
+
t.string :key
|
|
9
|
+
t.string :text_value
|
|
10
|
+
t.integer :integer_value
|
|
11
|
+
t.decimal :decimal_value,
|
|
12
|
+
:precision => 6,
|
|
13
|
+
:scale => 2
|
|
14
|
+
t.date :date_value
|
|
15
|
+
|
|
16
|
+
t.timestamps
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
add_index :meta_data, [:meta_model_type, :meta_model_id, :key]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.down
|
|
23
|
+
drop_table :meta_data
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
end
|
data/has-meta.gemspec
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# Use ruby 2.5.0 with default gemset
|
|
2
|
+
lib = File.expand_path("../lib", __FILE__)
|
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
4
|
+
require "has_meta/version"
|
|
5
|
+
|
|
6
|
+
Gem::Specification.new do |spec|
|
|
7
|
+
spec.name = "has-meta"
|
|
8
|
+
spec.version = HasMeta::VERSION
|
|
9
|
+
spec.authors = ["Dan Drust"]
|
|
10
|
+
spec.email = ["dan.drust@protrainings.com"]
|
|
11
|
+
|
|
12
|
+
spec.summary = "A key/value store solution for Rails apps with bloated tables"
|
|
13
|
+
spec.homepage = 'https://www.github.com/protrainings/has-meta'
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.files = `git ls-files -z`.split("\x0").reject do |f|
|
|
16
|
+
f.match(%r{^(test|spec|features)/})
|
|
17
|
+
end
|
|
18
|
+
spec.bindir = "exe"
|
|
19
|
+
spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
|
|
20
|
+
spec.require_paths = ["lib"]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
spec.add_dependency 'activerecord', ['>= 4.2.8']
|
|
24
|
+
|
|
25
|
+
spec.add_development_dependency 'pry'
|
|
26
|
+
spec.add_development_dependency 'sqlite3'
|
|
27
|
+
spec.add_development_dependency 'rspec-rails'
|
|
28
|
+
end
|
data/lib/has-meta.rb
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# require 'active_record'
|
|
2
|
+
# require 'active_record/version'
|
|
3
|
+
# require 'active_support/core_ext/module'
|
|
4
|
+
|
|
5
|
+
require 'pry'
|
|
6
|
+
|
|
7
|
+
begin
|
|
8
|
+
require 'rails/engine'
|
|
9
|
+
require 'has_meta/engine'
|
|
10
|
+
rescue
|
|
11
|
+
LoadError
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
module HasMeta
|
|
15
|
+
|
|
16
|
+
extend ActiveSupport::Autoload
|
|
17
|
+
|
|
18
|
+
autoload :MetaData
|
|
19
|
+
autoload :DataMover
|
|
20
|
+
autoload :MetaQuery
|
|
21
|
+
autoload :QueryMethods
|
|
22
|
+
autoload :DynamicMethods
|
|
23
|
+
autoload :InstanceMethods
|
|
24
|
+
autoload :VERSION
|
|
25
|
+
|
|
26
|
+
def meta_attributes
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def has_meta(*attributes)
|
|
31
|
+
options = attributes.pop if attributes.last.is_a? Hash
|
|
32
|
+
attributes = attributes.to_a.flatten.compact.map(&:to_sym)
|
|
33
|
+
|
|
34
|
+
if self.meta_attributes.present?
|
|
35
|
+
self.meta_attributes += attributes
|
|
36
|
+
else
|
|
37
|
+
class_attribute :meta_attributes, instance_predicate: false, instance_writer: false
|
|
38
|
+
self.meta_attributes = attributes
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
class_eval do
|
|
42
|
+
has_many :meta_data, as: :meta_model, dependent: :destroy, class_name: '::HasMeta::MetaData'
|
|
43
|
+
include HasMeta::InstanceMethods
|
|
44
|
+
include HasMeta::DynamicMethods
|
|
45
|
+
include HasMeta::QueryMethods
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
ActiveSupport.on_load :active_record do
|
|
52
|
+
extend HasMeta
|
|
53
|
+
end
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
module HasMeta
|
|
2
|
+
class DataMover
|
|
3
|
+
def initialize table, attribute, type, key
|
|
4
|
+
@table = table
|
|
5
|
+
@attribute = attribute
|
|
6
|
+
@key = key
|
|
7
|
+
@type = type
|
|
8
|
+
resolve_type!
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def execute
|
|
12
|
+
|
|
13
|
+
insert = Arel::Nodes::InsertStatement.new
|
|
14
|
+
insert.relation = destination_table
|
|
15
|
+
insert.columns = destination_columns
|
|
16
|
+
insert.values = Arel::Nodes::SqlLiteral.new source_values
|
|
17
|
+
# There seems to be a bug with earlier versions of Arel where multiple values aren't supported
|
|
18
|
+
# insert.select = source_select.where(source_conditions)
|
|
19
|
+
|
|
20
|
+
ActiveRecord::Base.connection.execute insert.to_sql if migrateable?
|
|
21
|
+
|
|
22
|
+
self
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
private
|
|
26
|
+
|
|
27
|
+
attr_accessor :abort
|
|
28
|
+
attr_reader :table, :attribute, :key, :type
|
|
29
|
+
|
|
30
|
+
def resolve_type!
|
|
31
|
+
@type = 'text' if @type == 'string'
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def source_values
|
|
35
|
+
migrateable_source_values
|
|
36
|
+
.reduce(" VALUES ") {|acc, row| acc += format_values row}[0...-1]
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def migrateable_source_values
|
|
40
|
+
if type.to_s == 'text'
|
|
41
|
+
source_model.where.not(:"#{attribute}" => [nil, ''])
|
|
42
|
+
else
|
|
43
|
+
source_model.where(source_table[:"#{attribute}"].not_eq(nil))
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def migrateable?
|
|
48
|
+
migrateable_source_values.present?
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def format_values row
|
|
52
|
+
"('#{table.classify.constantize.name}', #{row.id}, '#{key}', #{escape(row.send(:"#{attribute}"))}, #{timestamp}),"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def escape value
|
|
56
|
+
if ['text', 'date'].include? type.to_s
|
|
57
|
+
"'#{value}'"
|
|
58
|
+
else
|
|
59
|
+
value
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def timestamp
|
|
64
|
+
case ::ActiveRecord::Base.connection.adapter_name
|
|
65
|
+
when 'Mysql2'
|
|
66
|
+
'NOW()'
|
|
67
|
+
when 'SQLite'
|
|
68
|
+
"'#{Time.now}'"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def source_model
|
|
73
|
+
table.classify.constantize
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def source_table
|
|
77
|
+
@source_table ||= Arel::Table.new(table)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def source_select
|
|
81
|
+
source_table.project(
|
|
82
|
+
Arel.sql("\'#{table.classify.constantize.name}\'"), # :meta_model_type
|
|
83
|
+
source_table[:id], # :meta_model_id
|
|
84
|
+
Arel.sql("\'#{key}\'"), # :key
|
|
85
|
+
source_table[:"#{attribute}"], # :integer_value (ex.)
|
|
86
|
+
Arel::Nodes::NamedFunction.new("NOW", []) # :created_at
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def source_conditions
|
|
91
|
+
source_table[:"#{attribute}"].not_in([nil, ''])
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def destination_table
|
|
95
|
+
@destination_table ||= HasMeta::MetaData.arel_table
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def destination_columns
|
|
99
|
+
[
|
|
100
|
+
destination_table[:meta_model_type],
|
|
101
|
+
destination_table[:meta_model_id],
|
|
102
|
+
destination_table[:key],
|
|
103
|
+
destination_table[:"#{type}_value"],
|
|
104
|
+
destination_table[:created_at],
|
|
105
|
+
]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
end
|
|
109
|
+
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
module HasMeta
|
|
2
|
+
module DynamicMethods
|
|
3
|
+
def self.included base
|
|
4
|
+
base.extend ClassMethods
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
#TODO: refactor this
|
|
8
|
+
def respond_to? method, include_private=false
|
|
9
|
+
attribute = self.meta_attributes.select { |x| method.match /^#{x}(_id)?=?$/ }.pop
|
|
10
|
+
if attribute
|
|
11
|
+
self.class.find_object_from(attribute) ? true : !method.match(/^#{attribute}=?$/).nil?
|
|
12
|
+
else
|
|
13
|
+
super
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def method_missing method, *args, &block
|
|
18
|
+
attribute = self.meta_attributes.select { |x| method.match /^#{x}(_id)?=?$/ }.pop
|
|
19
|
+
if attribute
|
|
20
|
+
object = self.class.find_object_from attribute
|
|
21
|
+
if method =~ /=$/ # setter
|
|
22
|
+
object ? meta_set!(:"#{attribute}_id", args.first.try(:id) || args.first) : meta_set!(attribute, args.first)
|
|
23
|
+
else # getter
|
|
24
|
+
if object
|
|
25
|
+
method =~ /_id$/ ? meta_get(:"#{attribute}_id") : object.find_by_id(meta_get(:"#{attribute}_id"))
|
|
26
|
+
else
|
|
27
|
+
meta_get(attribute)
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
else
|
|
31
|
+
super
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
module ClassMethods
|
|
36
|
+
|
|
37
|
+
def respond_to? method, include_private=false
|
|
38
|
+
attribute = self.meta_attributes.select { |x| method.match(/(?<=^find_by_)#{x}(?=$|(?=_id$))/) }.pop
|
|
39
|
+
if attribute
|
|
40
|
+
find_object_from(attribute) ? !method.match(/_id$/).nil? : !method.match(/#{attribute}$/).nil?
|
|
41
|
+
else
|
|
42
|
+
super
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def method_missing method, *args, &block
|
|
47
|
+
# TODO: refactor this to not be as cluttery and dense
|
|
48
|
+
attribute = self.meta_attributes.select { |x| method.match /(?<=^find_by_)#{x}(?=$|(?=_id$))/ }.pop
|
|
49
|
+
if attribute
|
|
50
|
+
object = find_object_from(attribute)
|
|
51
|
+
if object and method =~ /_id$/
|
|
52
|
+
conditions = {key: "#{attribute}_id", meta_model_type: self}.
|
|
53
|
+
merge! MetaData.generate_value_hash(args.first)
|
|
54
|
+
MetaData.where(conditions).map do |x|
|
|
55
|
+
self.find_by_id(x.meta_model_id)
|
|
56
|
+
end
|
|
57
|
+
elsif !object
|
|
58
|
+
conditions = {key: "#{attribute}", meta_model_type: self}.
|
|
59
|
+
merge! MetaData.generate_value_hash(args.first)
|
|
60
|
+
MetaData.where(conditions).map do |x|
|
|
61
|
+
self.find_by_id(x.meta_model_id)
|
|
62
|
+
end
|
|
63
|
+
else
|
|
64
|
+
super
|
|
65
|
+
end
|
|
66
|
+
else
|
|
67
|
+
super
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def find_object_from attribute
|
|
72
|
+
begin
|
|
73
|
+
attribute.to_s.classify.constantize
|
|
74
|
+
rescue
|
|
75
|
+
nil
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
module HasMeta
|
|
2
|
+
module InstanceMethods
|
|
3
|
+
|
|
4
|
+
def meta_get key
|
|
5
|
+
self.meta_data.where(key: key.to_s).try(:first).try(:value)
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def meta_set key, value
|
|
9
|
+
return meta_destroy key if value.nil? or value == ''
|
|
10
|
+
|
|
11
|
+
meta = self.meta_data.where(key: key.to_s).first_or_create
|
|
12
|
+
meta.value = value
|
|
13
|
+
meta
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def meta_set! key, value
|
|
17
|
+
meta_set(key, value).save
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def meta_destroy key
|
|
21
|
+
self.meta_data.where(key: key).destroy_all
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# TODO: Test these
|
|
25
|
+
def list_meta_keys
|
|
26
|
+
self.meta_data.pluck(:keys).uniq
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def remove_meta(key)
|
|
30
|
+
self.send(key.to_sym, nil)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
module HasMeta
|
|
2
|
+
class MetaData < ::ActiveRecord::Base
|
|
3
|
+
|
|
4
|
+
belongs_to :meta_model, polymorphic: true
|
|
5
|
+
|
|
6
|
+
attr_accessor :value
|
|
7
|
+
attr_reader :data_type
|
|
8
|
+
|
|
9
|
+
def value
|
|
10
|
+
@value ||= value_attributes.compact.values.pop
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def value= value
|
|
14
|
+
@data_type, @value = resolve_data_type! value
|
|
15
|
+
set_attribute
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def self.generate_value_hash *values
|
|
19
|
+
# values = 'some text'
|
|
20
|
+
# values = ['some text', 'some other text']
|
|
21
|
+
# values = ['some text', 'some other text', 9]
|
|
22
|
+
if values.size == 1
|
|
23
|
+
# {text_value: 'some text'}
|
|
24
|
+
value_hash_for values.pop
|
|
25
|
+
else
|
|
26
|
+
# ['some text', 'some other text', 9]
|
|
27
|
+
# [{text_value: 'some text'}, {text_value: 'some other text'}, {integer_value: 9}]
|
|
28
|
+
# {:text_value=>[{:text_value=>"some text"}, {:text_value=>"some other text"}], :integer_value=>[{:integer_value=>9}]}
|
|
29
|
+
# values.map { |value| value_hash_for value }.group_by { |x| x.keys.first }.map {|k, v| {k => v.map {|x| x.values.first}}}
|
|
30
|
+
values
|
|
31
|
+
.map { |value| value_hash_for value }
|
|
32
|
+
.group_by { |x| x.keys.first }
|
|
33
|
+
.reduce({}) do |acc, hash|
|
|
34
|
+
key, value = *hash
|
|
35
|
+
acc.merge({key => value.size == 1 ? value.first.values.first : value.map {|x| x.values.first}})
|
|
36
|
+
end
|
|
37
|
+
# .map {|k, v| {k => v.map {|x| x.values.first}}}
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def self.value_hash_for value
|
|
44
|
+
data_type, value = resolve_data_type! value
|
|
45
|
+
{"#{data_type}_value": value}
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def self.resolve_data_type! value
|
|
49
|
+
case value
|
|
50
|
+
when ->(x) {x.kind_of? Integer}
|
|
51
|
+
# TODO: dynamically check for a range error (is this a ruby thing or mysql thing?)
|
|
52
|
+
if value < 2000000000
|
|
53
|
+
return :integer, value
|
|
54
|
+
else
|
|
55
|
+
return :text, value.to_s
|
|
56
|
+
end
|
|
57
|
+
when ->(x) {x.kind_of? Float}
|
|
58
|
+
return :decimal, value
|
|
59
|
+
when ->(x) {x.kind_of? Date}
|
|
60
|
+
return :date, value
|
|
61
|
+
when ->(x) {x.respond_to? :id}
|
|
62
|
+
return :integer, value.id
|
|
63
|
+
else
|
|
64
|
+
return :integer, value.to_i if value =~ /^-?\d+$/
|
|
65
|
+
return :decimal, value.to_f if value =~ /^-?\d*\.\d+$/
|
|
66
|
+
return :text, value
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def resolve_data_type! value
|
|
71
|
+
self.class.resolve_data_type! value
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def value_attributes
|
|
75
|
+
self.attributes.select do |k, _|
|
|
76
|
+
k =~ /_value/
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def reset_values
|
|
81
|
+
value_attributes.keys.each do |attribute|
|
|
82
|
+
self[attribute] = nil
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def set_attribute
|
|
87
|
+
reset_values
|
|
88
|
+
self[:"#{@data_type}_value"] = @value
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
end
|
|
92
|
+
end
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
module HasMeta
|
|
2
|
+
class MetaQuery
|
|
3
|
+
def initialize meta_model, meta_data, conditions, options={}
|
|
4
|
+
@meta_model = meta_model
|
|
5
|
+
@meta_data = meta_data
|
|
6
|
+
@conditions = conditions
|
|
7
|
+
@options = options
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def build
|
|
11
|
+
@meta_model
|
|
12
|
+
.joins(for_each_meta_key)
|
|
13
|
+
.where(conditions_for_keys_and_values)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
attr_reader :meta_model, :meta_data, :conditions, :options
|
|
19
|
+
|
|
20
|
+
def meta_model_arel_table
|
|
21
|
+
@meta_model_arel_table ||= @meta_model.arel_table
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def meta_data_arel_table
|
|
25
|
+
@meta_data_arel_table ||= @meta_data.arel_table
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def meta_data_aliases
|
|
29
|
+
@meta_data_aliases = @conditions.keys
|
|
30
|
+
.map.with_index do |key, i|
|
|
31
|
+
{key => meta_data_arel_table.alias("#{key}_join")}
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def for_each_meta_key
|
|
36
|
+
meta_data_aliases.reduce(meta_model_arel_table) { |acc, meta_data_alias_hash|
|
|
37
|
+
key, meta_data_alias = *meta_data_alias_hash.first
|
|
38
|
+
|
|
39
|
+
acc
|
|
40
|
+
.join(meta_data_alias, join_type)
|
|
41
|
+
.on(on_conditions meta_data_alias, key)
|
|
42
|
+
}.join_sources
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def join_type
|
|
46
|
+
if options[:exclude] or options[:any]
|
|
47
|
+
Arel::Nodes::OuterJoin
|
|
48
|
+
else
|
|
49
|
+
Arel::Nodes::InnerJoin
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def on_conditions table_alias, key
|
|
54
|
+
type_condition = table_alias[:meta_model_type].eq(meta_model)
|
|
55
|
+
id_condition = table_alias[:meta_model_id].eq(meta_model_arel_table[:id])
|
|
56
|
+
key_condition = table_alias[:key].eq(resolve_key key)
|
|
57
|
+
|
|
58
|
+
type_condition.and(id_condition).and(key_condition)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def conditions_for_keys_and_values
|
|
62
|
+
@conditions.values.map.with_index do |values, i|
|
|
63
|
+
|
|
64
|
+
conditions_for_values(meta_data_aliases[i].values.pop, values)
|
|
65
|
+
.reduce { |acc, x| acc.or(x) }
|
|
66
|
+
|
|
67
|
+
end
|
|
68
|
+
.reduce { |acc, x| options[:any] ? acc.or(x) : acc.and(x) }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def resolve_key key
|
|
72
|
+
meta_model.find_object_from(key) ? "#{key}_id" : key
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def conditions_for_values meta_data_alias, values
|
|
76
|
+
MetaData.generate_value_hash(*values).map do |column, value|
|
|
77
|
+
if options[:exclude]
|
|
78
|
+
meta_data_alias[column].not_in(value)
|
|
79
|
+
else
|
|
80
|
+
meta_data_alias[column].in(value)
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module HasMeta
|
|
2
|
+
module QueryMethods
|
|
3
|
+
def self.included base
|
|
4
|
+
base.extend ClassMethods
|
|
5
|
+
end
|
|
6
|
+
|
|
7
|
+
module ClassMethods
|
|
8
|
+
|
|
9
|
+
def with_meta args=nil, options={}
|
|
10
|
+
HasMeta::MetaQuery.new(self, MetaData, args, options).build
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def excluding_meta args=nil, options={}
|
|
14
|
+
HasMeta::MetaQuery.new(self, MetaData, args, options.merge(exclude: true)).build
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: has-meta
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.8.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Dan Drust
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: exe
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2018-02-02 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: 4.2.8
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - ">="
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 4.2.8
|
|
27
|
+
- !ruby/object:Gem::Dependency
|
|
28
|
+
name: pry
|
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
|
30
|
+
requirements:
|
|
31
|
+
- - ">="
|
|
32
|
+
- !ruby/object:Gem::Version
|
|
33
|
+
version: '0'
|
|
34
|
+
type: :development
|
|
35
|
+
prerelease: false
|
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
37
|
+
requirements:
|
|
38
|
+
- - ">="
|
|
39
|
+
- !ruby/object:Gem::Version
|
|
40
|
+
version: '0'
|
|
41
|
+
- !ruby/object:Gem::Dependency
|
|
42
|
+
name: sqlite3
|
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
|
44
|
+
requirements:
|
|
45
|
+
- - ">="
|
|
46
|
+
- !ruby/object:Gem::Version
|
|
47
|
+
version: '0'
|
|
48
|
+
type: :development
|
|
49
|
+
prerelease: false
|
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
51
|
+
requirements:
|
|
52
|
+
- - ">="
|
|
53
|
+
- !ruby/object:Gem::Version
|
|
54
|
+
version: '0'
|
|
55
|
+
- !ruby/object:Gem::Dependency
|
|
56
|
+
name: rspec-rails
|
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '0'
|
|
62
|
+
type: :development
|
|
63
|
+
prerelease: false
|
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
65
|
+
requirements:
|
|
66
|
+
- - ">="
|
|
67
|
+
- !ruby/object:Gem::Version
|
|
68
|
+
version: '0'
|
|
69
|
+
description:
|
|
70
|
+
email:
|
|
71
|
+
- dan.drust@protrainings.com
|
|
72
|
+
executables: []
|
|
73
|
+
extensions: []
|
|
74
|
+
extra_rdoc_files: []
|
|
75
|
+
files:
|
|
76
|
+
- ".gitignore"
|
|
77
|
+
- ".rspec"
|
|
78
|
+
- ".rvmrc"
|
|
79
|
+
- ".travis.yml"
|
|
80
|
+
- Gemfile
|
|
81
|
+
- Gemfile.lock
|
|
82
|
+
- LICENSE.txt
|
|
83
|
+
- README.md
|
|
84
|
+
- Rakefile
|
|
85
|
+
- bin/console
|
|
86
|
+
- bin/setup
|
|
87
|
+
- db/migrate/1_has_meta_migration.rb
|
|
88
|
+
- has-meta.gemspec
|
|
89
|
+
- lib/has-meta.rb
|
|
90
|
+
- lib/has_meta/data_mover.rb
|
|
91
|
+
- lib/has_meta/dynamic_methods.rb
|
|
92
|
+
- lib/has_meta/engine.rb
|
|
93
|
+
- lib/has_meta/instance_methods.rb
|
|
94
|
+
- lib/has_meta/meta_data.rb
|
|
95
|
+
- lib/has_meta/meta_query.rb
|
|
96
|
+
- lib/has_meta/query_methods.rb
|
|
97
|
+
- lib/has_meta/version.rb
|
|
98
|
+
- lib/tasks/data_mover.rake
|
|
99
|
+
homepage: https://www.github.com/protrainings/has-meta
|
|
100
|
+
licenses:
|
|
101
|
+
- MIT
|
|
102
|
+
metadata: {}
|
|
103
|
+
post_install_message:
|
|
104
|
+
rdoc_options: []
|
|
105
|
+
require_paths:
|
|
106
|
+
- lib
|
|
107
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
108
|
+
requirements:
|
|
109
|
+
- - ">="
|
|
110
|
+
- !ruby/object:Gem::Version
|
|
111
|
+
version: '0'
|
|
112
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
113
|
+
requirements:
|
|
114
|
+
- - ">="
|
|
115
|
+
- !ruby/object:Gem::Version
|
|
116
|
+
version: '0'
|
|
117
|
+
requirements: []
|
|
118
|
+
rubyforge_project:
|
|
119
|
+
rubygems_version: 2.7.3
|
|
120
|
+
signing_key:
|
|
121
|
+
specification_version: 4
|
|
122
|
+
summary: A key/value store solution for Rails apps with bloated tables
|
|
123
|
+
test_files: []
|