has_metadata 1.6.1 → 1.6.2
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/README.md +117 -0
- data/has_metadata.gemspec +17 -7
- data/lib/has_metadata/metadata_generator.rb +6 -6
- data/lib/has_metadata/model.rb +9 -7
- data/lib/has_metadata.rb +57 -43
- data/templates/metadata.rb +7 -4
- metadata +69 -45
- data/README.textile +0 -126
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: e2f29c8739a41a97beed0ed3c6b331f8ba3cc2da4ee8bacbd7dc269a0113d23a
|
|
4
|
+
data.tar.gz: 9ceb1f806883a0a42a8734270801b88f715357fb8315f8dbf51566d84a4caaad
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 91d5b70471591f8c37c8ae7d6849e370356b6f0fb42c324b7e5a451c0b3448564060f178be7c1ec853f53d4302f8eb2acd7ad1f0d13270fd69bd4ac0a19dfc57
|
|
7
|
+
data.tar.gz: a53e2493826bd95af4c129a6fa3b4a73acfee451f1b70797d48fa4803242662d54bda4324b7627204a90718fbc2db65edec46efb60f8fa13d7d38a1ef1ee4736
|
data/README.md
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
> ⚠️ **DEPRECATED:** `has_metadata` is no longer maintained. Superseded by
|
|
2
|
+
> [`has_metadata_column`](https://github.com/RISCfuture/has_metadata_column)
|
|
3
|
+
> (also deprecated). No active maintenance. The final release is v1.6.2.
|
|
4
|
+
|
|
5
|
+
has_metadata
|
|
6
|
+
============
|
|
7
|
+
|
|
8
|
+
**Keep your tables narrow**
|
|
9
|
+
|
|
10
|
+
| | |
|
|
11
|
+
|:------------|:--------------------------------|
|
|
12
|
+
| **Author** | Tim Morgan |
|
|
13
|
+
| **Version** | 1.6.1 (Jan 24, 2012) |
|
|
14
|
+
| **License** | Released under the MIT License. |
|
|
15
|
+
|
|
16
|
+
About
|
|
17
|
+
-----
|
|
18
|
+
|
|
19
|
+
Wide tables are a problem for big databases. If your `ActiveRecord` models have
|
|
20
|
+
10, maybe 15 columns, some of which are `VARCHARs` or maybe even `TEXTs`, it's
|
|
21
|
+
going to slow your queries down when you start to scale up.
|
|
22
|
+
|
|
23
|
+
The easy solution to this problem is to limit your projections; in other words,
|
|
24
|
+
to only `SELECT` the columns that you actually need. If you've got a `users`
|
|
25
|
+
table with a giant `about_me` text column, and you're only trying to look up the
|
|
26
|
+
user's login, then just select the `login` column.
|
|
27
|
+
|
|
28
|
+
In the long run, though, a superior solution is to just move those
|
|
29
|
+
`about_me`-type columns to a completely different table. This table has just one
|
|
30
|
+
JSON-serialized field, making it schemaless, so it doesn't waste space. Each row
|
|
31
|
+
in this table is associated with a record in another table (`Metadata` `has_one`
|
|
32
|
+
of your models).
|
|
33
|
+
|
|
34
|
+
This way, when your website gets huge, all of your giant, freeform data is in
|
|
35
|
+
one table that you can shard, or move off to an alternate database, or even a
|
|
36
|
+
NoSQL-type document store, or otherwise manage as you please. Your relational
|
|
37
|
+
tables remain slim and efficient, containing only columns that a) are indexed,
|
|
38
|
+
or b) you need frequent access to.
|
|
39
|
+
|
|
40
|
+
This gem includes a generator that creates the `Metadata` model, and a module
|
|
41
|
+
that you can include in your models to define which fields have been spun off to
|
|
42
|
+
the metadata record.
|
|
43
|
+
|
|
44
|
+
Installation
|
|
45
|
+
------------
|
|
46
|
+
|
|
47
|
+
**Important Note:** This gem is only compatible with Ruby 1.9+ and Rails 3.0+.
|
|
48
|
+
|
|
49
|
+
Firstly, add the gem to your Rails project's `Gemfile`:
|
|
50
|
+
|
|
51
|
+
```` ruby
|
|
52
|
+
gem 'has_metadata'
|
|
53
|
+
````
|
|
54
|
+
|
|
55
|
+
Next, run the generator, which will add the `Metadata` model and its migration
|
|
56
|
+
to your application.
|
|
57
|
+
|
|
58
|
+
```` sh
|
|
59
|
+
rails generate metadata
|
|
60
|
+
````
|
|
61
|
+
|
|
62
|
+
Usage
|
|
63
|
+
-----
|
|
64
|
+
|
|
65
|
+
The first thing to think about is what columns to keep in your model. You will
|
|
66
|
+
need to keep any indexed columns, or any columns you perform lookups or other
|
|
67
|
+
SQL queries with. You should also keep any frequently accessed columns,
|
|
68
|
+
especially if they are small (integers or booleans). Good candidates for the
|
|
69
|
+
metadata table are the `TEXT`- and `VARCHAR`-type columns that you only need to
|
|
70
|
+
render a page or two in your app.
|
|
71
|
+
|
|
72
|
+
You'll need to change your model's schema so that it has a `metadata_id` column
|
|
73
|
+
that will associate the model with its `Metadata` instance:
|
|
74
|
+
|
|
75
|
+
```` ruby
|
|
76
|
+
t.belongs_to :metadata
|
|
77
|
+
````
|
|
78
|
+
|
|
79
|
+
Next, include the `HasMetadata` module in your model, and call the
|
|
80
|
+
`has_metadata` method to define the schema of your metadata. You can get more
|
|
81
|
+
information in the {HasMetadata::ClassMethods#has_metadata} documentation, but for starters, here's a
|
|
82
|
+
basic example:
|
|
83
|
+
|
|
84
|
+
```` ruby
|
|
85
|
+
class User < ActiveRecord::Base
|
|
86
|
+
include HasMetadata
|
|
87
|
+
has_metadata({
|
|
88
|
+
about_me: { type: String, length: { maximum: 512 } },
|
|
89
|
+
birthdate: { type: Date, presence: true },
|
|
90
|
+
zipcode: { type: Number, numericality: { greater_than: 9999, less_than: 10_000} }
|
|
91
|
+
})
|
|
92
|
+
end
|
|
93
|
+
````
|
|
94
|
+
|
|
95
|
+
As you can see, you pass field names mapped to a hash. The hash describes the
|
|
96
|
+
validation that will be performed, and is in the same format as a call to
|
|
97
|
+
`validates`. In addition to the `EachValidator` keys shown above, you can also
|
|
98
|
+
pass a `type` key, to constrain the Ruby type that can be assigned to the field.
|
|
99
|
+
|
|
100
|
+
Each of these fields (in this case, `about_me`, `birthdate`, and `zipcode`) can
|
|
101
|
+
be accessed and set as first_level methods on an instance of your model:
|
|
102
|
+
|
|
103
|
+
```` ruby
|
|
104
|
+
user.about_me #=> "I was born in 1982 in Aberdeen. My father was a carpenter from..."
|
|
105
|
+
````
|
|
106
|
+
|
|
107
|
+
... and thus, used as part of `form_for` fields:
|
|
108
|
+
|
|
109
|
+
```` ruby
|
|
110
|
+
form_for user do |f|
|
|
111
|
+
f.text_area :about_me, rows: 5, cols: 80
|
|
112
|
+
end
|
|
113
|
+
````
|
|
114
|
+
|
|
115
|
+
The only thing you _can't_ do is use these fields in a query, obviously. You
|
|
116
|
+
can't do something like `User.where(zipcode: 90210)`, because that column
|
|
117
|
+
doesn't exist on the `users` table.
|
data/has_metadata.gemspec
CHANGED
|
@@ -2,23 +2,24 @@
|
|
|
2
2
|
# DO NOT EDIT THIS FILE DIRECTLY
|
|
3
3
|
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
|
4
4
|
# -*- encoding: utf-8 -*-
|
|
5
|
+
# stub: has_metadata 1.6.2 ruby lib
|
|
5
6
|
|
|
6
7
|
Gem::Specification.new do |s|
|
|
7
8
|
s.name = "has_metadata"
|
|
8
|
-
s.version = "1.6.
|
|
9
|
+
s.version = "1.6.2"
|
|
9
10
|
|
|
10
11
|
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
|
11
12
|
s.authors = ["Tim Morgan"]
|
|
12
|
-
s.date = "
|
|
13
|
+
s.date = "2013-12-08"
|
|
13
14
|
s.description = "has_metadata lets you move non-indexed and weighty columns off of your big tables by creating a separate metadata table to store all this extra information. Works with Ruby 1.9. and Rails 3.0."
|
|
14
15
|
s.email = "git@timothymorgan.info"
|
|
15
16
|
s.extra_rdoc_files = [
|
|
16
17
|
"LICENSE",
|
|
17
|
-
"README.
|
|
18
|
+
"README.md"
|
|
18
19
|
]
|
|
19
20
|
s.files = [
|
|
20
21
|
"LICENSE",
|
|
21
|
-
"README.
|
|
22
|
+
"README.md",
|
|
22
23
|
"has_metadata.gemspec",
|
|
23
24
|
"lib/has_metadata.rb",
|
|
24
25
|
"lib/has_metadata/metadata_generator.rb",
|
|
@@ -29,11 +30,20 @@ Gem::Specification.new do |s|
|
|
|
29
30
|
s.homepage = "http://github.com/riscfuture/has_metadata"
|
|
30
31
|
s.require_paths = ["lib"]
|
|
31
32
|
s.required_ruby_version = Gem::Requirement.new(">= 1.9")
|
|
32
|
-
s.rubygems_version = "1.
|
|
33
|
-
s.summary = "Reduce your table width by moving non-indexed columns to a separate metadata table"
|
|
33
|
+
s.rubygems_version = "2.1.11"
|
|
34
|
+
s.summary = "[DEPRECATED] Reduce your table width by moving non-indexed columns to a separate metadata table"
|
|
35
|
+
s.post_install_message = <<~MSG
|
|
36
|
+
|
|
37
|
+
⚠️ DEPRECATED: has_metadata is no longer maintained.
|
|
38
|
+
|
|
39
|
+
Superseded by `has_metadata_column` (also deprecated as of this notice). No active maintenance.
|
|
40
|
+
|
|
41
|
+
This is the final release. No further updates are planned.
|
|
42
|
+
|
|
43
|
+
MSG
|
|
34
44
|
|
|
35
45
|
if s.respond_to? :specification_version then
|
|
36
|
-
s.specification_version =
|
|
46
|
+
s.specification_version = 4
|
|
37
47
|
|
|
38
48
|
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
|
39
49
|
s.add_runtime_dependency(%q<rails>, [">= 3.2"])
|
|
@@ -5,19 +5,19 @@ require 'rails/generators/migration'
|
|
|
5
5
|
# @private
|
|
6
6
|
class MetadataGenerator < Rails::Generators::Base
|
|
7
7
|
include Rails::Generators::Migration
|
|
8
|
-
|
|
8
|
+
|
|
9
9
|
source_root "#{File.dirname __FILE__}/../../templates"
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
def self.next_migration_number(dirname)
|
|
12
12
|
if ActiveRecord::Base.timestamped_migrations then
|
|
13
|
-
Time.now.utc.strftime
|
|
13
|
+
Time.now.utc.strftime '%Y%m%d%H%M%S'
|
|
14
14
|
else
|
|
15
15
|
"%.3d" % (current_migration_number(dirname) + 1)
|
|
16
16
|
end
|
|
17
17
|
end
|
|
18
|
-
|
|
18
|
+
|
|
19
19
|
def copy_files
|
|
20
|
-
copy_file
|
|
21
|
-
migration_template
|
|
20
|
+
copy_file 'metadata.rb', 'app/models/metadata.rb'
|
|
21
|
+
migration_template 'create_metadata.rb', 'db/migrate/create_metadata.rb'
|
|
22
22
|
end
|
|
23
23
|
end
|
data/lib/has_metadata/model.rb
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
require 'active_record'
|
|
2
|
+
|
|
1
3
|
module HasMetadata
|
|
2
|
-
|
|
4
|
+
|
|
3
5
|
# Base class of the {Metadata} model. Functionality is moved to this class to
|
|
4
|
-
# make changes to the model easier. See the
|
|
6
|
+
# make changes to the model easier. See the `Metadata` method for more
|
|
5
7
|
# information.
|
|
6
|
-
|
|
8
|
+
|
|
7
9
|
class Model < ActiveRecord::Base
|
|
8
10
|
self.table_name = 'metadata'
|
|
9
11
|
serialize :data, Hash
|
|
@@ -20,10 +22,10 @@ module HasMetadata
|
|
|
20
22
|
name = name.to_sym
|
|
21
23
|
super(name) or fields.include?(name)
|
|
22
24
|
end
|
|
23
|
-
|
|
25
|
+
|
|
24
26
|
# override attribute prefix/suffix methods to get full first-class
|
|
25
27
|
# property functionality
|
|
26
|
-
|
|
28
|
+
|
|
27
29
|
singleton_class.send(:define_method, :attribute) do |name|
|
|
28
30
|
name = name.to_sym
|
|
29
31
|
super(name) unless fields.include?(name)
|
|
@@ -33,7 +35,7 @@ module HasMetadata
|
|
|
33
35
|
data.include?(name) ? data[name] : default
|
|
34
36
|
end
|
|
35
37
|
singleton_class.send :alias_method, :attribute_before_type_cast, :attribute
|
|
36
|
-
|
|
38
|
+
|
|
37
39
|
singleton_class.send(:define_method, :query_attribute) do |name|
|
|
38
40
|
name = name.to_sym
|
|
39
41
|
super(name) unless fields.include?(name)
|
|
@@ -49,7 +51,7 @@ module HasMetadata
|
|
|
49
51
|
not send(name).blank?
|
|
50
52
|
end
|
|
51
53
|
end
|
|
52
|
-
|
|
54
|
+
|
|
53
55
|
singleton_class.send(:define_method, :attribute=) do |name, value|
|
|
54
56
|
name = name.to_sym
|
|
55
57
|
super(name, value) unless fields.include?(name)
|
data/lib/has_metadata.rb
CHANGED
|
@@ -16,15 +16,16 @@ class Object
|
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
|
|
19
|
-
# Provides the {ClassMethods#has_metadata} method to subclasses of
|
|
19
|
+
# Provides the {ClassMethods#has_metadata} method to subclasses of
|
|
20
|
+
# `ActiveRecord::Base`.
|
|
20
21
|
|
|
21
22
|
module HasMetadata
|
|
22
23
|
extend ActiveSupport::Concern
|
|
23
|
-
|
|
24
|
+
|
|
24
25
|
# @private
|
|
25
26
|
def self.metadata_typecast(value, type)
|
|
26
27
|
if value.kind_of?(String) then
|
|
27
|
-
if type == Integer or type ==
|
|
28
|
+
if type == Integer or type == Integer then
|
|
28
29
|
begin
|
|
29
30
|
return Integer(value.sub(/^0+/, '')) # so that it doesn't think it's in octal
|
|
30
31
|
rescue ArgumentError
|
|
@@ -36,33 +37,37 @@ module HasMetadata
|
|
|
36
37
|
rescue ArgumentError
|
|
37
38
|
return value
|
|
38
39
|
end
|
|
39
|
-
elsif type == Boolean then
|
|
40
|
+
elsif type == Boolean then
|
|
41
|
+
return value.parse_bool
|
|
42
|
+
end
|
|
40
43
|
end
|
|
41
44
|
return value
|
|
42
45
|
end
|
|
43
|
-
|
|
46
|
+
|
|
44
47
|
# Class methods that are added to your model.
|
|
45
|
-
|
|
48
|
+
|
|
46
49
|
module ClassMethods
|
|
47
50
|
|
|
48
51
|
# Defines a set of fields whose values exist in the associated {Metadata}
|
|
49
|
-
# record. Each key in the
|
|
50
|
-
# the value is a set of options to pass to the
|
|
51
|
-
# not want to perform any validation on a field, simply pass
|
|
52
|
+
# record. Each key in the `fields` hash is the name of a metadata field, and
|
|
53
|
+
# the value is a set of options to pass to the `validates` method. If you do
|
|
54
|
+
# not want to perform any validation on a field, simply pass `true` as its
|
|
52
55
|
# key value.
|
|
53
56
|
#
|
|
54
|
-
# In addition to the normal
|
|
55
|
-
# key to restrict values to certain classes, or a
|
|
57
|
+
# In addition to the normal `validates` keys, you can also include a `:type`
|
|
58
|
+
# key to restrict values to certain classes, or a `:default` key to specify
|
|
56
59
|
# a value to return for the getter should none be set (normal default is
|
|
57
|
-
#
|
|
60
|
+
# `nil`).
|
|
58
61
|
#
|
|
59
62
|
# @param [Hash<Symbol, Hash>] fields A mapping of field names to their
|
|
60
|
-
# validation options (and/or the
|
|
63
|
+
# validation options (and/or the `:type` key).
|
|
61
64
|
#
|
|
62
65
|
# @example Three metadata fields, one basic, one validated, and one type-checked.
|
|
63
|
-
# has_metadata(optional: true, required: { presence: true }, number: { type:
|
|
66
|
+
# has_metadata(optional: true, required: { presence: true }, number: { type: Integer })
|
|
64
67
|
|
|
65
68
|
def has_metadata(fields)
|
|
69
|
+
raise "Can't define Rails-magic timestamped columns as metadata" if (fields.keys & [:created_at, :created_on, :updated_at, :updated_on]).any?
|
|
70
|
+
|
|
66
71
|
if !respond_to?(:metadata_fields) then
|
|
67
72
|
belongs_to :metadata, dependent: :destroy
|
|
68
73
|
accepts_nested_attributes_for :metadata
|
|
@@ -74,9 +79,8 @@ module HasMetadata
|
|
|
74
79
|
define_method(:save_metadata) { metadata.save! }
|
|
75
80
|
define_method(:metadata_changed?) { metadata.try :changed? }
|
|
76
81
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
else
|
|
82
|
+
prepend WithMetadata
|
|
83
|
+
elsif metadata_fields.slice(*fields.keys) != fields
|
|
80
84
|
raise "Cannot redefine existing metadata fields: #{(fields.keys & self.metadata_fields.keys).to_sentence}" unless (fields.keys & self.metadata_fields.keys).empty?
|
|
81
85
|
self.metadata_fields = self.metadata_fields.merge(fields)
|
|
82
86
|
end
|
|
@@ -84,33 +88,34 @@ module HasMetadata
|
|
|
84
88
|
fields.each do |name, options|
|
|
85
89
|
# delegate all attribute methods to the metadata
|
|
86
90
|
attribute_method_matchers.each { |matcher| delegate matcher.method_name(name), to: :metadata! }
|
|
87
|
-
|
|
91
|
+
|
|
88
92
|
if options.kind_of?(Hash) then
|
|
89
|
-
type
|
|
93
|
+
type = options.delete(:type)
|
|
90
94
|
type_validate = !options.delete(:skip_type_validation)
|
|
91
95
|
options.delete :default
|
|
92
|
-
|
|
96
|
+
|
|
93
97
|
validate do |obj|
|
|
94
98
|
value = obj.send(name)
|
|
95
|
-
errors.add(name, :incorrect_type) unless
|
|
96
|
-
HasMetadata.metadata_typecast(value, type).kind_of?(type) or
|
|
99
|
+
errors.add(name, :incorrect_type) unless HasMetadata.metadata_typecast(value, type).kind_of?(type) or
|
|
97
100
|
((options[:allow_nil] and value.nil?) or (options[:allow_blank] and value.blank?))
|
|
98
101
|
end if type && type_validate
|
|
99
|
-
validates(name, options) unless options.empty? or (options.keys - [
|
|
102
|
+
validates(name, options) unless options.empty? or (options.keys - [:allow_nil, :allow_blank]).empty?
|
|
100
103
|
end
|
|
101
104
|
end
|
|
102
105
|
end
|
|
103
106
|
end
|
|
104
107
|
|
|
108
|
+
# @private
|
|
105
109
|
def as_json(options={})
|
|
106
|
-
options
|
|
107
|
-
options[:except]
|
|
110
|
+
options ||= Hash.new # the JSON encoder can sometimes give us nil options?
|
|
111
|
+
options[:except] = Array.wrap(options[:except]) + [:metadata_id]
|
|
108
112
|
options[:methods] = Array.wrap(options[:methods]) + metadata_fields.keys - options[:except].map(&:to_sym)
|
|
109
113
|
super options
|
|
110
114
|
end
|
|
111
|
-
|
|
115
|
+
|
|
116
|
+
# @private
|
|
112
117
|
def to_xml(options={})
|
|
113
|
-
options[:except]
|
|
118
|
+
options[:except] = Array.wrap(options[:except]) + [:metadata_id]
|
|
114
119
|
options[:methods] = Array.wrap(options[:methods]) + metadata_fields.keys - options[:except].map(&:to_sym)
|
|
115
120
|
super options
|
|
116
121
|
end
|
|
@@ -118,18 +123,20 @@ module HasMetadata
|
|
|
118
123
|
# @private
|
|
119
124
|
def assign_multiparameter_attributes(pairs)
|
|
120
125
|
fake_attributes = pairs.select { |(field, _)| self.class.metadata_fields.include? field[0, field.index('(')].to_sym }
|
|
126
|
+
result = super(pairs.except(fake_attributes.keys))
|
|
121
127
|
|
|
122
128
|
fake_attributes.group_by { |(field, _)| field[0, field.index('(')] }.each do |field_name, parts|
|
|
123
129
|
options = self.class.metadata_fields[field_name.to_sym]
|
|
124
130
|
if options[:type] then
|
|
125
131
|
args = parts.each_with_object([]) do |(part_name, value), ary|
|
|
126
132
|
part_ann = part_name[part_name.index('(') + 1, part_name.length]
|
|
127
|
-
index
|
|
133
|
+
index = part_ann.to_i - 1
|
|
128
134
|
raise "Out-of-bounds multiparameter argument index" unless index >= 0
|
|
129
135
|
ary[index] = if value.blank? then nil
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
136
|
+
elsif part_ann.ends_with?('i)') then value.to_i
|
|
137
|
+
elsif part_ann.ends_with?('f)') then value.to_f
|
|
138
|
+
else value
|
|
139
|
+
end
|
|
133
140
|
end
|
|
134
141
|
args.compact!
|
|
135
142
|
send :"#{field_name}=", options[:type].new(*args) unless args.empty?
|
|
@@ -138,7 +145,7 @@ module HasMetadata
|
|
|
138
145
|
end
|
|
139
146
|
end
|
|
140
147
|
|
|
141
|
-
|
|
148
|
+
return result
|
|
142
149
|
end
|
|
143
150
|
|
|
144
151
|
# @return [Metadata] An existing associated {Metadata} instance, or new,
|
|
@@ -158,20 +165,27 @@ module HasMetadata
|
|
|
158
165
|
|
|
159
166
|
# @private
|
|
160
167
|
def inspect
|
|
161
|
-
"#<#{self.class.to_s} #{attributes.merge(metadata.try(:data).try(:stringify_keys) || {}).map { |k,v| "#{k}: #{v.inspect}" }.join(', ')}>"
|
|
168
|
+
"#<#{self.class.to_s} #{attributes.merge(metadata.try(:data).try(:stringify_keys) || {}).map { |k, v| "#{k}: #{v.inspect}" }.join(', ')}>"
|
|
162
169
|
end
|
|
163
170
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
171
|
+
module WithMetadata
|
|
172
|
+
# @private
|
|
173
|
+
def changed_attributes
|
|
174
|
+
super.merge(metadata.try(:changed_metadata) || {})
|
|
175
|
+
end
|
|
168
176
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
177
|
+
# @private
|
|
178
|
+
def attributes_changed_by_setter
|
|
179
|
+
super.merge(metadata.try(:changed_metadata) || {})
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
# @private
|
|
183
|
+
def attribute_will_change!(attr)
|
|
184
|
+
if attribute_names.include?(attr) then
|
|
185
|
+
super attr
|
|
186
|
+
else
|
|
187
|
+
metadata!.send :attribute_will_change!, attr
|
|
188
|
+
end
|
|
175
189
|
end
|
|
176
190
|
end
|
|
177
191
|
end
|
data/templates/metadata.rb
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
# Stores information about a model that doesn't need to be in that model's
|
|
2
|
-
# table. Each row in the
|
|
2
|
+
# table. Each row in the `metadata` table stores a schemaless, serialized hash
|
|
3
3
|
# of data associated with a model instance. Any model can have an associated row
|
|
4
|
-
# in the
|
|
4
|
+
# in the `metadata` table by using the {HasMetadata} module.
|
|
5
5
|
#
|
|
6
|
-
#
|
|
6
|
+
# Properties
|
|
7
|
+
# ----------
|
|
7
8
|
#
|
|
8
|
-
# |
|
|
9
|
+
# | | |
|
|
10
|
+
# |:-------|:----------------------------------------------------------------------|
|
|
11
|
+
# | `data` | A hash of this metadata's contents (YAML serialized in the database). |
|
|
9
12
|
|
|
10
13
|
class Metadata < HasMetadata::Model
|
|
11
14
|
end
|
metadata
CHANGED
|
@@ -1,93 +1,112 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: has_metadata
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.6.
|
|
5
|
-
prerelease:
|
|
4
|
+
version: 1.6.2
|
|
6
5
|
platform: ruby
|
|
7
6
|
authors:
|
|
8
7
|
- Tim Morgan
|
|
9
|
-
autorequire:
|
|
10
8
|
bindir: bin
|
|
11
9
|
cert_chain: []
|
|
12
|
-
date:
|
|
10
|
+
date: 2013-12-08 00:00:00.000000000 Z
|
|
13
11
|
dependencies:
|
|
14
12
|
- !ruby/object:Gem::Dependency
|
|
15
13
|
name: rails
|
|
16
|
-
requirement:
|
|
17
|
-
none: false
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
18
15
|
requirements:
|
|
19
|
-
- -
|
|
16
|
+
- - ">="
|
|
20
17
|
- !ruby/object:Gem::Version
|
|
21
18
|
version: '3.2'
|
|
22
19
|
type: :runtime
|
|
23
20
|
prerelease: false
|
|
24
|
-
version_requirements:
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '3.2'
|
|
25
26
|
- !ruby/object:Gem::Dependency
|
|
26
27
|
name: boolean
|
|
27
|
-
requirement:
|
|
28
|
-
none: false
|
|
28
|
+
requirement: !ruby/object:Gem::Requirement
|
|
29
29
|
requirements:
|
|
30
|
-
- -
|
|
30
|
+
- - ">="
|
|
31
31
|
- !ruby/object:Gem::Version
|
|
32
32
|
version: '0'
|
|
33
33
|
type: :runtime
|
|
34
34
|
prerelease: false
|
|
35
|
-
version_requirements:
|
|
35
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
36
|
+
requirements:
|
|
37
|
+
- - ">="
|
|
38
|
+
- !ruby/object:Gem::Version
|
|
39
|
+
version: '0'
|
|
36
40
|
- !ruby/object:Gem::Dependency
|
|
37
41
|
name: jeweler
|
|
38
|
-
requirement:
|
|
39
|
-
none: false
|
|
42
|
+
requirement: !ruby/object:Gem::Requirement
|
|
40
43
|
requirements:
|
|
41
|
-
- -
|
|
44
|
+
- - ">="
|
|
42
45
|
- !ruby/object:Gem::Version
|
|
43
46
|
version: '0'
|
|
44
47
|
type: :development
|
|
45
48
|
prerelease: false
|
|
46
|
-
version_requirements:
|
|
49
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
50
|
+
requirements:
|
|
51
|
+
- - ">="
|
|
52
|
+
- !ruby/object:Gem::Version
|
|
53
|
+
version: '0'
|
|
47
54
|
- !ruby/object:Gem::Dependency
|
|
48
55
|
name: yard
|
|
49
|
-
requirement:
|
|
50
|
-
none: false
|
|
56
|
+
requirement: !ruby/object:Gem::Requirement
|
|
51
57
|
requirements:
|
|
52
|
-
- -
|
|
58
|
+
- - ">="
|
|
53
59
|
- !ruby/object:Gem::Version
|
|
54
60
|
version: '0'
|
|
55
61
|
type: :development
|
|
56
62
|
prerelease: false
|
|
57
|
-
version_requirements:
|
|
63
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
64
|
+
requirements:
|
|
65
|
+
- - ">="
|
|
66
|
+
- !ruby/object:Gem::Version
|
|
67
|
+
version: '0'
|
|
58
68
|
- !ruby/object:Gem::Dependency
|
|
59
69
|
name: RedCloth
|
|
60
|
-
requirement:
|
|
61
|
-
none: false
|
|
70
|
+
requirement: !ruby/object:Gem::Requirement
|
|
62
71
|
requirements:
|
|
63
|
-
- -
|
|
72
|
+
- - ">="
|
|
64
73
|
- !ruby/object:Gem::Version
|
|
65
74
|
version: '0'
|
|
66
75
|
type: :development
|
|
67
76
|
prerelease: false
|
|
68
|
-
version_requirements:
|
|
77
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
78
|
+
requirements:
|
|
79
|
+
- - ">="
|
|
80
|
+
- !ruby/object:Gem::Version
|
|
81
|
+
version: '0'
|
|
69
82
|
- !ruby/object:Gem::Dependency
|
|
70
83
|
name: sqlite3
|
|
71
|
-
requirement:
|
|
72
|
-
none: false
|
|
84
|
+
requirement: !ruby/object:Gem::Requirement
|
|
73
85
|
requirements:
|
|
74
|
-
- -
|
|
86
|
+
- - ">="
|
|
75
87
|
- !ruby/object:Gem::Version
|
|
76
88
|
version: '0'
|
|
77
89
|
type: :development
|
|
78
90
|
prerelease: false
|
|
79
|
-
version_requirements:
|
|
91
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
92
|
+
requirements:
|
|
93
|
+
- - ">="
|
|
94
|
+
- !ruby/object:Gem::Version
|
|
95
|
+
version: '0'
|
|
80
96
|
- !ruby/object:Gem::Dependency
|
|
81
97
|
name: rspec
|
|
82
|
-
requirement:
|
|
83
|
-
none: false
|
|
98
|
+
requirement: !ruby/object:Gem::Requirement
|
|
84
99
|
requirements:
|
|
85
|
-
- -
|
|
100
|
+
- - ">="
|
|
86
101
|
- !ruby/object:Gem::Version
|
|
87
102
|
version: '0'
|
|
88
103
|
type: :development
|
|
89
104
|
prerelease: false
|
|
90
|
-
version_requirements:
|
|
105
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
106
|
+
requirements:
|
|
107
|
+
- - ">="
|
|
108
|
+
- !ruby/object:Gem::Version
|
|
109
|
+
version: '0'
|
|
91
110
|
description: has_metadata lets you move non-indexed and weighty columns off of your
|
|
92
111
|
big tables by creating a separate metadata table to store all this extra information.
|
|
93
112
|
Works with Ruby 1.9. and Rails 3.0.
|
|
@@ -96,10 +115,10 @@ executables: []
|
|
|
96
115
|
extensions: []
|
|
97
116
|
extra_rdoc_files:
|
|
98
117
|
- LICENSE
|
|
99
|
-
- README.
|
|
118
|
+
- README.md
|
|
100
119
|
files:
|
|
101
120
|
- LICENSE
|
|
102
|
-
- README.
|
|
121
|
+
- README.md
|
|
103
122
|
- has_metadata.gemspec
|
|
104
123
|
- lib/has_metadata.rb
|
|
105
124
|
- lib/has_metadata/metadata_generator.rb
|
|
@@ -108,27 +127,32 @@ files:
|
|
|
108
127
|
- templates/metadata.rb
|
|
109
128
|
homepage: http://github.com/riscfuture/has_metadata
|
|
110
129
|
licenses: []
|
|
111
|
-
|
|
130
|
+
metadata: {}
|
|
131
|
+
post_install_message: |2+
|
|
132
|
+
|
|
133
|
+
⚠️ DEPRECATED: has_metadata is no longer maintained.
|
|
134
|
+
|
|
135
|
+
Superseded by `has_metadata_column` (also deprecated as of this notice). No active maintenance.
|
|
136
|
+
|
|
137
|
+
This is the final release. No further updates are planned.
|
|
138
|
+
|
|
112
139
|
rdoc_options: []
|
|
113
140
|
require_paths:
|
|
114
141
|
- lib
|
|
115
142
|
required_ruby_version: !ruby/object:Gem::Requirement
|
|
116
|
-
none: false
|
|
117
143
|
requirements:
|
|
118
|
-
- -
|
|
144
|
+
- - ">="
|
|
119
145
|
- !ruby/object:Gem::Version
|
|
120
146
|
version: '1.9'
|
|
121
147
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
122
|
-
none: false
|
|
123
148
|
requirements:
|
|
124
|
-
- -
|
|
149
|
+
- - ">="
|
|
125
150
|
- !ruby/object:Gem::Version
|
|
126
151
|
version: '0'
|
|
127
152
|
requirements: []
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
summary: Reduce your table width by moving non-indexed columns to a separate metadata
|
|
133
|
-
table
|
|
153
|
+
rubygems_version: 4.0.11
|
|
154
|
+
specification_version: 4
|
|
155
|
+
summary: "[DEPRECATED] Reduce your table width by moving non-indexed columns to a
|
|
156
|
+
separate metadata table"
|
|
134
157
|
test_files: []
|
|
158
|
+
...
|
data/README.textile
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
h1. has_metadata -- Keep your tables narrow
|
|
2
|
-
|
|
3
|
-
| *Author* | Tim Morgan |
|
|
4
|
-
| *Version* | 1.6.1 (Jan 24, 2012) |
|
|
5
|
-
| *License* | Released under the MIT License. |
|
|
6
|
-
|
|
7
|
-
h2. Important note for those upgrading from 1.0 to 1.1
|
|
8
|
-
|
|
9
|
-
You must re-run the @rails generate metadata@ task to update your @Metadata@
|
|
10
|
-
model. When finished, your @app/models/metadata.rb@ file should look like:
|
|
11
|
-
|
|
12
|
-
<pre><code>
|
|
13
|
-
# Stores information about a model that doesn't need to be in that model's
|
|
14
|
-
# table. Each row in the @metadata@ table stores a schemaless, serialized hash
|
|
15
|
-
# of data associated with a model instance. Any model can have an associated row
|
|
16
|
-
# in the @metadata@ table by using the {HasMetadata} module.
|
|
17
|
-
#
|
|
18
|
-
# h2. Properties
|
|
19
|
-
#
|
|
20
|
-
# | @data@ | A hash of this metadata's contents (YAML serialized in the database). |
|
|
21
|
-
|
|
22
|
-
class Metadata < HasMetadata::Model
|
|
23
|
-
end
|
|
24
|
-
</code></pre>
|
|
25
|
-
|
|
26
|
-
Note that this is significantly emptier than the previous model.
|
|
27
|
-
|
|
28
|
-
h2. About
|
|
29
|
-
|
|
30
|
-
Wide tables are a problem for big databases. If your @ActiveRecord@ models have
|
|
31
|
-
10, maybe 15 columns, some of which are @VARCHARs@ or maybe even @TEXTs@, it's
|
|
32
|
-
going to slow your queries down when you start to scale up.
|
|
33
|
-
|
|
34
|
-
The easy solution to this problem is to limit your projections; in other words,
|
|
35
|
-
to only @SELECT@ the columns that you actually need. If you've got a @users@
|
|
36
|
-
table with a giant @about_me@ text column, and you're only trying to look up the
|
|
37
|
-
user's login, then just select the @login@ column.
|
|
38
|
-
|
|
39
|
-
In the long run, though, a superior solution is to just move those
|
|
40
|
-
@about_me@-type columns to a completely different table. This table has just one
|
|
41
|
-
JSON-serialized field, making it schemaless, so it doesn't waste space. Each row
|
|
42
|
-
in this table is associated with a record in another table (@Metadata@ @has_one@
|
|
43
|
-
of your models).
|
|
44
|
-
|
|
45
|
-
This way, when your website gets huge, all of your giant, freeform data is in
|
|
46
|
-
one table that you can shard, or move off to an alternate database, or even a
|
|
47
|
-
NoSQL-type document store, or otherwise manage as you please. Your relational
|
|
48
|
-
tables remain slim and efficient, containing only columns that a) are indexed,
|
|
49
|
-
or b) you need frequent access to.
|
|
50
|
-
|
|
51
|
-
This gem includes a generator that creates the @Metadata@ model, and a module
|
|
52
|
-
that you can include in your models to define which fields have been spun off to
|
|
53
|
-
the metadata record.
|
|
54
|
-
|
|
55
|
-
h2. Installation
|
|
56
|
-
|
|
57
|
-
*Important Note:* This gem is only compatible with Ruby 1.9 and Rails 3.0.
|
|
58
|
-
|
|
59
|
-
Firstly, add the gem to your Rails project's @Gemfile@:
|
|
60
|
-
|
|
61
|
-
<pre><code>
|
|
62
|
-
gem 'has_metadata'
|
|
63
|
-
</code></pre>
|
|
64
|
-
|
|
65
|
-
Next, run the generator, which will add the @Metadata@ model and its migration
|
|
66
|
-
to your application.
|
|
67
|
-
|
|
68
|
-
<pre><code>
|
|
69
|
-
rails generate metadata
|
|
70
|
-
</code></pre>
|
|
71
|
-
|
|
72
|
-
h2. Usage
|
|
73
|
-
|
|
74
|
-
The first thing to think about is what columns to keep in your model. You will
|
|
75
|
-
need to keep any indexed columns, or any columns you perform lookups or other
|
|
76
|
-
SQL queries with. You should also keep any frequently accessed columns,
|
|
77
|
-
especially if they are small (integers or booleans). Good candidates for the
|
|
78
|
-
metadata table are the @TEXT@- and @VARCHAR@-type columns that you only need to
|
|
79
|
-
render a page or two in your app.
|
|
80
|
-
|
|
81
|
-
You'll need to change your model's schema so that it has a @metadata_id@ column
|
|
82
|
-
that will associate the model with its @Metadata@ instance:
|
|
83
|
-
|
|
84
|
-
<pre><code>
|
|
85
|
-
t.belongs_to :metadata
|
|
86
|
-
</code></pre>
|
|
87
|
-
|
|
88
|
-
Next, include the @HasMetadata@ module in your model, and call the
|
|
89
|
-
@has_metadata@ method to define the schema of your metadata. You can get more
|
|
90
|
-
information in the {HasMetadata::ClassMethods#has_metadata} documentation, but for starters, here's a
|
|
91
|
-
basic example:
|
|
92
|
-
|
|
93
|
-
<pre><code>
|
|
94
|
-
class User < ActiveRecord::Base
|
|
95
|
-
include HasMetadata
|
|
96
|
-
has_metadata({
|
|
97
|
-
about_me: { type: String, length: { maximum: 512 } },
|
|
98
|
-
birthdate: { type: Date, presence: true },
|
|
99
|
-
zipcode: { type: Number, numericality: { greater_than: 9999, less_than: 10_000_} }
|
|
100
|
-
})
|
|
101
|
-
end
|
|
102
|
-
</code></pre>
|
|
103
|
-
|
|
104
|
-
As you can see, you pass field names mapped to a hash. The hash describes the
|
|
105
|
-
validation that will be performed, and is in the same format as a call to
|
|
106
|
-
@validates@. In addition to the @EachValidator@ keys shown above, you can also
|
|
107
|
-
pass a @type@ key, to constrain the Ruby type that can be assigned to the field.
|
|
108
|
-
|
|
109
|
-
Each of these fields (in this case, @about_me@, @birthdate@, and @zipcode@) can
|
|
110
|
-
be accessed and set as first_level methods on an instance of your model:
|
|
111
|
-
|
|
112
|
-
<pre><code>
|
|
113
|
-
user.about_me #=> "I was born in 1982 in Aberdeen. My father was a carpenter from..."
|
|
114
|
-
</code></pre>
|
|
115
|
-
|
|
116
|
-
... and thus, used as part of @form_for@ fields:
|
|
117
|
-
|
|
118
|
-
<pre><code>
|
|
119
|
-
form_for user do |f|
|
|
120
|
-
f.text_area :about_me, rows: 5, cols: 80
|
|
121
|
-
end
|
|
122
|
-
</code></pre>
|
|
123
|
-
|
|
124
|
-
The only thing you _can't_ do is use these fields in a query, obviously. You
|
|
125
|
-
can't do something like @User.where(zipcode: 90210)@, because that column
|
|
126
|
-
doesn't exist on the @users@ table.
|