has_metadata_column 1.0.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.
- data/LICENSE.txt +20 -0
- data/README.md +106 -0
- data/has_metadata_column.gemspec +61 -0
- data/lib/has_metadata_column.rb +275 -0
- metadata +129 -0
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2012 Tim Morgan
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,106 @@
|
|
1
|
+
Has Metadata Column -- Keep your tables narrow
|
2
|
+
===================
|
3
|
+
|
4
|
+
| | |
|
5
|
+
|:------------|:--------------------------------|
|
6
|
+
| **Author** | Tim Morgan |
|
7
|
+
| **Version** | 1.0 (Mar 1, 2012) |
|
8
|
+
| **License** | Released under the MIT License. |
|
9
|
+
|
10
|
+
About
|
11
|
+
-----
|
12
|
+
|
13
|
+
So you're wondering why it is you need to make, test, schedule, and deploy a
|
14
|
+
whole nother migration just to add one more freeform "Favorite Music"-type
|
15
|
+
column to your users model? Wish there were an easier way?
|
16
|
+
|
17
|
+
There is! Combine all of those "about me," "favorite music," etc. type fields
|
18
|
+
into _one_ JSON-serialized `TEXT` column, and now every model can have
|
19
|
+
schemaless, migration-free data.
|
20
|
+
|
21
|
+
If you're interested in moving your metadata out to another table (or database)
|
22
|
+
entirely, consider using
|
23
|
+
[HasMetadata](https://github.com/riscfuture/has_metadata).
|
24
|
+
|
25
|
+
This gem does use some "metaprogramming magic" to make the metadata fields
|
26
|
+
appear like first-class fields, for purposes of validation and easy access. If
|
27
|
+
this is unsettling to you, I recommend using my gem
|
28
|
+
[JsonSerialize](https://github.com/riscfuture/json_serialize) instead, as it
|
29
|
+
does not get its little fingers all up in ActiveRecord's business.
|
30
|
+
|
31
|
+
(Why yes, I _do_ have a gem for a every use case!)
|
32
|
+
|
33
|
+
h2. Installation
|
34
|
+
|
35
|
+
**Important Note:** This gem is only compatible with Ruby 1.9 and Rails 3.0.
|
36
|
+
|
37
|
+
Merely add the gem to your Rails project's `Gemfile`:
|
38
|
+
|
39
|
+
```` ruby
|
40
|
+
gem 'has_metadata_column'
|
41
|
+
````
|
42
|
+
|
43
|
+
Usage
|
44
|
+
-----
|
45
|
+
|
46
|
+
The first thing to think about is what columns to keep. You will need to keep
|
47
|
+
any indexed columns, or any columns you perform lookups or other SQL queries
|
48
|
+
with. You should also keep any frequently accessed columns, especially if they
|
49
|
+
are small (integers or booleans). Good candidates for the metadata column are
|
50
|
+
the `TEXT`- and `VARCHAR`-type columns that you only need to render a page or
|
51
|
+
two in your app.
|
52
|
+
|
53
|
+
You'll need to add a `TEXT` column to your model to store the metadata. You can
|
54
|
+
call it what you want; `metadata` is assumed by default.
|
55
|
+
|
56
|
+
```` ruby
|
57
|
+
t.text :metadata
|
58
|
+
````
|
59
|
+
|
60
|
+
Next, include the `HasMetadataColumn` module in your model, and call the
|
61
|
+
`has_metadata_column` method to define the schema of your metadata. You can get
|
62
|
+
more information in the {HasMetadataColumn::ClassMethods#has_metadata_column}
|
63
|
+
documentation, but for starters, here's a basic example:
|
64
|
+
|
65
|
+
```` ruby
|
66
|
+
class User < ActiveRecord::Base
|
67
|
+
include HasMetadataColumn
|
68
|
+
has_metadata(
|
69
|
+
:my_metadata_column,
|
70
|
+
about_me: { type: String, length: { maximum: 512 } },
|
71
|
+
birthdate: { type: Date, presence: true },
|
72
|
+
zipcode: { type: Number, numericality: { greater_than: 9999, less_than: 10_000 } }
|
73
|
+
)
|
74
|
+
end
|
75
|
+
````
|
76
|
+
|
77
|
+
As you can see, you pass field names mapped to a hash. The hash describes the
|
78
|
+
validation that will be performed, and is in the same format as a call to
|
79
|
+
`validates`. In addition to the `EachValidator` keys shown above, you can also
|
80
|
+
pass a `type` key, to constrain the Ruby type that can be assigned to the field.
|
81
|
+
You can only assign types that can be JSON-serialized: strings, numbers, arrays,
|
82
|
+
hashes, dates/times, booleans, and `nil`.
|
83
|
+
|
84
|
+
Each of these fields (in this case, `about_me`, `birthdate`, and `zipcode`) can
|
85
|
+
be accessed and set as first_level methods on an instance of your model:
|
86
|
+
|
87
|
+
```` ruby
|
88
|
+
user.about_me #=> "I was born in 1982 in Aberdeen. My father was a carpenter from..."
|
89
|
+
````
|
90
|
+
|
91
|
+
... and thus, used as part of `form_for` fields:
|
92
|
+
|
93
|
+
```` ruby
|
94
|
+
form_for user do |f|
|
95
|
+
f.text_area :about_me, rows: 5, cols: 80
|
96
|
+
end
|
97
|
+
````
|
98
|
+
|
99
|
+
... and validations.
|
100
|
+
|
101
|
+
The only thing you _can't_ do is use these fields in a query, obviously. You
|
102
|
+
can't do something like `User.where(zipcode: 90210)`, because that column
|
103
|
+
doesn't exist on the `users` table.
|
104
|
+
|
105
|
+
... Unless you use PostgreSQL 9.2, and define your metadata column as type
|
106
|
+
`json`. Support for _that_ is coming...
|
@@ -0,0 +1,61 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = "has_metadata_column"
|
8
|
+
s.version = "1.0.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Tim Morgan"]
|
12
|
+
s.date = "2012-03-02"
|
13
|
+
s.description = "Reduce your table width and migration overhead by moving non-indexed columns to a separate metadata column."
|
14
|
+
s.email = "git@timothymorgan.info"
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.md"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
"README.md",
|
21
|
+
"has_metadata_column.gemspec",
|
22
|
+
"lib/has_metadata_column.rb"
|
23
|
+
]
|
24
|
+
s.homepage = "http://github.com/riscfuture/has_metadata_column"
|
25
|
+
s.licenses = ["MIT"]
|
26
|
+
s.require_paths = ["lib"]
|
27
|
+
s.required_ruby_version = Gem::Requirement.new(">= 1.9")
|
28
|
+
s.rubygems_version = "1.8.17"
|
29
|
+
s.summary = "Schemaless metadata using JSON columns"
|
30
|
+
|
31
|
+
if s.respond_to? :specification_version then
|
32
|
+
s.specification_version = 3
|
33
|
+
|
34
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
35
|
+
s.add_runtime_dependency(%q<rails>, [">= 3.0"])
|
36
|
+
s.add_runtime_dependency(%q<boolean>, [">= 0"])
|
37
|
+
s.add_development_dependency(%q<rspec>, [">= 0"])
|
38
|
+
s.add_development_dependency(%q<sqlite3>, [">= 0"])
|
39
|
+
s.add_development_dependency(%q<yard>, [">= 0"])
|
40
|
+
s.add_development_dependency(%q<redcarpet>, [">= 0"])
|
41
|
+
s.add_development_dependency(%q<jeweler>, [">= 0"])
|
42
|
+
else
|
43
|
+
s.add_dependency(%q<rails>, [">= 3.0"])
|
44
|
+
s.add_dependency(%q<boolean>, [">= 0"])
|
45
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
46
|
+
s.add_dependency(%q<sqlite3>, [">= 0"])
|
47
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
48
|
+
s.add_dependency(%q<redcarpet>, [">= 0"])
|
49
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
50
|
+
end
|
51
|
+
else
|
52
|
+
s.add_dependency(%q<rails>, [">= 3.0"])
|
53
|
+
s.add_dependency(%q<boolean>, [">= 0"])
|
54
|
+
s.add_dependency(%q<rspec>, [">= 0"])
|
55
|
+
s.add_dependency(%q<sqlite3>, [">= 0"])
|
56
|
+
s.add_dependency(%q<yard>, [">= 0"])
|
57
|
+
s.add_dependency(%q<redcarpet>, [">= 0"])
|
58
|
+
s.add_dependency(%q<jeweler>, [">= 0"])
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
@@ -0,0 +1,275 @@
|
|
1
|
+
require 'boolean'
|
2
|
+
|
3
|
+
# @private
|
4
|
+
class Object
|
5
|
+
|
6
|
+
# Creates a deep copy of this object.
|
7
|
+
#
|
8
|
+
# @raise [TypeError] If the object cannot be deep-copied. All objects that can
|
9
|
+
# be marshalled can be deep-copied.
|
10
|
+
|
11
|
+
def deep_clone
|
12
|
+
Marshal.load Marshal.dump(self)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
# Provides the {ClassMethods#has_metadata_column} method to subclasses of
|
17
|
+
# `ActiveRecord::Base`.
|
18
|
+
|
19
|
+
module HasMetadataColumn
|
20
|
+
extend ActiveSupport::Concern
|
21
|
+
|
22
|
+
# Valid values for the `:type` option.
|
23
|
+
TYPES = [ String, Fixnum, Integer, Float, Hash, Array, TrueClass, FalseClass, Boolean, NilClass, Date, Time ]
|
24
|
+
|
25
|
+
# @private
|
26
|
+
def self.metadata_typecast(value, type=nil)
|
27
|
+
type ||= String
|
28
|
+
raise ArgumentError, "Can't convert objects of type #{type.to_s}" unless TYPES.include?(type)
|
29
|
+
|
30
|
+
if value.kind_of?(String) then
|
31
|
+
if type == Integer or type == Fixnum then
|
32
|
+
begin
|
33
|
+
return Integer(value.sub(/^0+/, '')) # so that it doesn't think it's in octal
|
34
|
+
rescue ArgumentError
|
35
|
+
return value
|
36
|
+
end
|
37
|
+
elsif type == Float then
|
38
|
+
begin
|
39
|
+
return Float(value)
|
40
|
+
rescue ArgumentError
|
41
|
+
return value
|
42
|
+
end
|
43
|
+
elsif type == Boolean then
|
44
|
+
return value.parse_bool
|
45
|
+
elsif type == Date then
|
46
|
+
return nil if value.nil?
|
47
|
+
begin
|
48
|
+
return Date.parse(value)
|
49
|
+
rescue ArgumentError
|
50
|
+
return value
|
51
|
+
end
|
52
|
+
elsif type == Time then
|
53
|
+
return nil if value.nil?
|
54
|
+
begin
|
55
|
+
return Time.parse(value)
|
56
|
+
rescue ArgumentError
|
57
|
+
return value
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
return value
|
62
|
+
end
|
63
|
+
|
64
|
+
# Class methods that are added to your model.
|
65
|
+
|
66
|
+
module ClassMethods
|
67
|
+
|
68
|
+
# Defines a set of fields whose values exist in the JSON metadata column.
|
69
|
+
# Each key in the `fields` hash is the name of a metadata field, and the
|
70
|
+
# value is a set of options to pass to the `validates` method. If you do not
|
71
|
+
# want to perform any validation on a field, simply pass `true` as its key
|
72
|
+
# value.
|
73
|
+
#
|
74
|
+
# In addition to the normal `validates` keys, you can also include a `:type`
|
75
|
+
# key to restrict values to certain classes, or a `:default` key to specify
|
76
|
+
# a value to return for the getter should none be set (normal default is
|
77
|
+
# `nil`). See {TYPES} for a list of valid values.
|
78
|
+
#
|
79
|
+
# @overload has_metadata_column(column, fields)
|
80
|
+
# @param [Symbol] column (:metadata) The column containing the metadata
|
81
|
+
# information.
|
82
|
+
# @param [Hash<Symbol, Hash>] fields A mapping of field names to their
|
83
|
+
# validation options (and/or the `:type` key).
|
84
|
+
# @raise [ArgumentError] If invalid arguments are given, or an invalid
|
85
|
+
# class for the `:type` key.
|
86
|
+
# @raise [StandardError] If invalid field names are given (see source).
|
87
|
+
#
|
88
|
+
# @example Three metadata fields, one basic, one validated, and one type-checked.
|
89
|
+
# has_metadata_column(optional: true, required: { presence: true }, number: { type: Fixnum })
|
90
|
+
|
91
|
+
def has_metadata_column(*args)
|
92
|
+
fields = args.extract_options!
|
93
|
+
column = args.shift
|
94
|
+
|
95
|
+
raise ArgumentError, "has_metadata_column takes a column name and a hash of fields" unless args.empty?
|
96
|
+
raise "Can't define Rails-magic timestamped columns as metadata" if Rails.version >= '3.2.0' && (fields.keys & [:created_at, :created_on, :updated_at, :updated_on]).any?
|
97
|
+
classes = fields.values.select { |o| o[:type] && !TYPES.include?(o[:type]) }
|
98
|
+
raise ArgumentError, "#{classes.to_sentence} cannot be serialized to JSON" if classes.any?
|
99
|
+
|
100
|
+
if !respond_to?(:metadata_column_fields) then
|
101
|
+
class_attribute :metadata_column_fields
|
102
|
+
self.metadata_column_fields = fields.deep_clone
|
103
|
+
class_attribute :metadata_column
|
104
|
+
self.metadata_column = column || :metadata
|
105
|
+
|
106
|
+
after_save :_reset_metadata
|
107
|
+
|
108
|
+
alias_method_chain :changed_attributes, :metadata_column
|
109
|
+
alias_method_chain :attribute_will_change!, :metadata_column
|
110
|
+
alias_method_chain :attribute_method?, :metadata
|
111
|
+
alias_method_chain :attribute, :metadata
|
112
|
+
alias_method_chain :attribute_before_type_cast, :metadata
|
113
|
+
alias_method_chain :_attribute, :metadata
|
114
|
+
alias_method_chain :attribute=, :metadata
|
115
|
+
alias_method_chain :query_attribute, :metadata
|
116
|
+
alias_method_chain :reload, :metadata
|
117
|
+
else
|
118
|
+
raise "Cannot redefine existing metadata column #{self.metadata_column}" if column && column != self.metadata_column
|
119
|
+
if metadata_column_fields.slice(*fields.keys) != fields
|
120
|
+
raise "Cannot redefine existing metadata fields: #{(fields.keys & self.metadata_column_fields.keys).to_sentence}" unless (fields.keys & self.metadata_column_fields.keys).empty?
|
121
|
+
self.metadata_column_fields = self.metadata_column_fields.merge(fields)
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
fields.each do |name, options|
|
126
|
+
if options.kind_of?(Hash) then
|
127
|
+
type = options.delete(:type)
|
128
|
+
type_validate = !options.delete(:skip_type_validation)
|
129
|
+
options.delete :default
|
130
|
+
|
131
|
+
validate do |obj|
|
132
|
+
value = obj.send(name)
|
133
|
+
errors.add(name, :incorrect_type) if !HasMetadataColumn.metadata_typecast(value, type).kind_of?(type) &&
|
134
|
+
(!options[:allow_nil] || (options[:allow_nil] && !value.nil?)) &&
|
135
|
+
(!options[:allow_blank] || (options[:allow_blank] && !value.blank?))
|
136
|
+
end if type && type_validate
|
137
|
+
validates(name, options) unless options.empty? or (options.keys - [:allow_nil, :allow_blank]).empty?
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
# @private
|
144
|
+
def as_json(options={})
|
145
|
+
options ||= Hash.new # the JSON encoder can sometimes give us nil options?
|
146
|
+
options[:except] = Array.wrap(options[:except]) + [ self.class.metadata_column ]
|
147
|
+
options[:methods] = Array.wrap(options[:methods]) + self.class.metadata_column_fields.keys - options[:except].map(&:to_sym)
|
148
|
+
super options
|
149
|
+
end
|
150
|
+
|
151
|
+
# @private
|
152
|
+
def to_xml(options={})
|
153
|
+
options[:except] = Array.wrap(options[:except]) + [ self.class.metadata_column ]
|
154
|
+
options[:methods] = Array.wrap(options[:methods]) + self.class.metadata_column_fields.keys - options[:except].map(&:to_sym)
|
155
|
+
super options
|
156
|
+
end
|
157
|
+
|
158
|
+
# @private
|
159
|
+
def assign_multiparameter_attributes(pairs)
|
160
|
+
fake_attributes = pairs.select { |(field, _)| self.class.metadata_column_fields.include? field[0, field.index('(')].to_sym }
|
161
|
+
|
162
|
+
fake_attributes.group_by { |(field, _)| field[0, field.index('(')] }.each do |field_name, parts|
|
163
|
+
options = self.class.metadata_column_fields[field_name.to_sym]
|
164
|
+
if options[:type] then
|
165
|
+
args = parts.each_with_object([]) do |(part_name, value), ary|
|
166
|
+
part_ann = part_name[part_name.index('(') + 1, part_name.length]
|
167
|
+
index = part_ann.to_i - 1
|
168
|
+
raise "Out-of-bounds multiparameter argument index" unless index >= 0
|
169
|
+
ary[index] = if value.blank? then
|
170
|
+
nil
|
171
|
+
elsif part_ann.ends_with?('i)') then
|
172
|
+
value.to_i
|
173
|
+
elsif part_ann.ends_with?('f)') then
|
174
|
+
value.to_f
|
175
|
+
else
|
176
|
+
value
|
177
|
+
end
|
178
|
+
end
|
179
|
+
send :"#{field_name}=", options[:type].new(*args) unless args.empty?
|
180
|
+
else
|
181
|
+
raise "#{field_name} has no type and cannot be used for multiparameter assignment"
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
185
|
+
super(pairs - fake_attributes)
|
186
|
+
end
|
187
|
+
|
188
|
+
# @private
|
189
|
+
def inspect
|
190
|
+
"#<#{self.class.to_s} #{attributes.except(self.class.metadata_column.to_s).merge(_metadata_hash.try(:stringify_keys) || {}).map { |k, v| "#{k}: #{v.inspect}" }.join(', ')}>"
|
191
|
+
end
|
192
|
+
|
193
|
+
# @private
|
194
|
+
def reload_with_metadata
|
195
|
+
res = reload_without_metadata
|
196
|
+
@_metadata_hash = nil
|
197
|
+
@_changed_metadata = nil
|
198
|
+
res
|
199
|
+
end
|
200
|
+
|
201
|
+
private
|
202
|
+
|
203
|
+
def changed_attributes_with_metadata_column
|
204
|
+
changed_attributes_without_metadata_column.merge(_changed_metadata)
|
205
|
+
end
|
206
|
+
|
207
|
+
def attribute_will_change_with_metadata_column!(attr)
|
208
|
+
unless attribute_names.include?(attr)
|
209
|
+
send :"#{self.class.metadata_column}_will_change!"
|
210
|
+
end
|
211
|
+
attribute_will_change_without_metadata_column! attr
|
212
|
+
end
|
213
|
+
|
214
|
+
def _metadata_hash
|
215
|
+
@_metadata_hash ||= begin
|
216
|
+
send(self.class.metadata_column) ? JSON.parse(send(self.class.metadata_column)) : {}
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def _changed_metadata
|
221
|
+
@_changed_metadata ||= {}
|
222
|
+
end
|
223
|
+
|
224
|
+
## ATTRIBUTE MATCHER METHODS
|
225
|
+
|
226
|
+
def attribute_with_metadata(attr)
|
227
|
+
return attribute_without_metadata(attr) unless self.class.metadata_column_fields.include?(attr.to_sym)
|
228
|
+
|
229
|
+
options = self.class.metadata_column_fields[attr.to_sym] || {}
|
230
|
+
default = options.include?(:default) ? options[:default] : nil
|
231
|
+
_metadata_hash.include?(attr) ? HasMetadataColumn.metadata_typecast(_metadata_hash[attr], options[:type]) : default
|
232
|
+
end
|
233
|
+
|
234
|
+
def attribute_before_type_cast_with_metadata(attr)
|
235
|
+
return attribute_before_type_cast_without_metadata(attr) unless self.class.metadata_column_fields.include?(attr.to_sym)
|
236
|
+
options = self.class.metadata_column_fields[attr.to_sym] || {}
|
237
|
+
default = options.include?(:default) ? options[:default] : nil
|
238
|
+
_metadata_hash.include?(attr) ? _metadata_hash[attr] : default
|
239
|
+
end
|
240
|
+
|
241
|
+
def _attribute_with_metadata(attr)
|
242
|
+
return _attribute_without_metadata(attr) unless self.class.metadata_column_fields.include?(attr.to_sym)
|
243
|
+
attribute_with_metadata attr
|
244
|
+
end
|
245
|
+
|
246
|
+
def attribute_with_metadata=(attr, value)
|
247
|
+
return send(:attribute_without_metadata=, attr, value) unless self.class.metadata_column_fields.include?(attr.to_sym)
|
248
|
+
|
249
|
+
options = self.class.metadata_column_fields[attr.to_sym] || {}
|
250
|
+
attribute_will_change! attr
|
251
|
+
@_metadata_hash ||= {}
|
252
|
+
old = @_metadata_hash[attr.to_s]
|
253
|
+
send :"#{self.class.metadata_column}=", @_metadata_hash.merge(attr.to_s => value).to_json
|
254
|
+
@_metadata_hash = nil
|
255
|
+
@_changed_metadata[attr] = old
|
256
|
+
value
|
257
|
+
end
|
258
|
+
|
259
|
+
def query_attribute_with_metadata(attr)
|
260
|
+
return query_attribute_without_metadata(attr) unless self.class.metadata_column_fields.include?(attr.to_sym)
|
261
|
+
return false unless (value = send(attr))
|
262
|
+
options = self.class.metadata_column_fields[attr.to_sym] || {}
|
263
|
+
type = options[:type] || String
|
264
|
+
return !value.to_i.zero? if type.ancestors.include?(Numeric)
|
265
|
+
return !value.blank?
|
266
|
+
end
|
267
|
+
|
268
|
+
def attribute_method_with_metadata?(attr)
|
269
|
+
self.class.metadata_column_fields.include?(attr.to_sym) || attribute_method_without_metadata?(attr)
|
270
|
+
end
|
271
|
+
|
272
|
+
def _reset_metadata
|
273
|
+
@_changed_metadata = {}
|
274
|
+
end
|
275
|
+
end
|
metadata
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: has_metadata_column
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Tim Morgan
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-03-02 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: &70347550669760 !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '3.0'
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: *70347550669760
|
25
|
+
- !ruby/object:Gem::Dependency
|
26
|
+
name: boolean
|
27
|
+
requirement: &70347550669280 !ruby/object:Gem::Requirement
|
28
|
+
none: false
|
29
|
+
requirements:
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '0'
|
33
|
+
type: :runtime
|
34
|
+
prerelease: false
|
35
|
+
version_requirements: *70347550669280
|
36
|
+
- !ruby/object:Gem::Dependency
|
37
|
+
name: rspec
|
38
|
+
requirement: &70347550668800 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ! '>='
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '0'
|
44
|
+
type: :development
|
45
|
+
prerelease: false
|
46
|
+
version_requirements: *70347550668800
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: sqlite3
|
49
|
+
requirement: &70347550668320 !ruby/object:Gem::Requirement
|
50
|
+
none: false
|
51
|
+
requirements:
|
52
|
+
- - ! '>='
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
type: :development
|
56
|
+
prerelease: false
|
57
|
+
version_requirements: *70347550668320
|
58
|
+
- !ruby/object:Gem::Dependency
|
59
|
+
name: yard
|
60
|
+
requirement: &70347550667840 !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ! '>='
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: '0'
|
66
|
+
type: :development
|
67
|
+
prerelease: false
|
68
|
+
version_requirements: *70347550667840
|
69
|
+
- !ruby/object:Gem::Dependency
|
70
|
+
name: redcarpet
|
71
|
+
requirement: &70347550667360 !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ! '>='
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
type: :development
|
78
|
+
prerelease: false
|
79
|
+
version_requirements: *70347550667360
|
80
|
+
- !ruby/object:Gem::Dependency
|
81
|
+
name: jeweler
|
82
|
+
requirement: &70347551403380 !ruby/object:Gem::Requirement
|
83
|
+
none: false
|
84
|
+
requirements:
|
85
|
+
- - ! '>='
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '0'
|
88
|
+
type: :development
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: *70347551403380
|
91
|
+
description: Reduce your table width and migration overhead by moving non-indexed
|
92
|
+
columns to a separate metadata column.
|
93
|
+
email: git@timothymorgan.info
|
94
|
+
executables: []
|
95
|
+
extensions: []
|
96
|
+
extra_rdoc_files:
|
97
|
+
- LICENSE.txt
|
98
|
+
- README.md
|
99
|
+
files:
|
100
|
+
- README.md
|
101
|
+
- has_metadata_column.gemspec
|
102
|
+
- lib/has_metadata_column.rb
|
103
|
+
- LICENSE.txt
|
104
|
+
homepage: http://github.com/riscfuture/has_metadata_column
|
105
|
+
licenses:
|
106
|
+
- MIT
|
107
|
+
post_install_message:
|
108
|
+
rdoc_options: []
|
109
|
+
require_paths:
|
110
|
+
- lib
|
111
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
112
|
+
none: false
|
113
|
+
requirements:
|
114
|
+
- - ! '>='
|
115
|
+
- !ruby/object:Gem::Version
|
116
|
+
version: '1.9'
|
117
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ! '>='
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
requirements: []
|
124
|
+
rubyforge_project:
|
125
|
+
rubygems_version: 1.8.17
|
126
|
+
signing_key:
|
127
|
+
specification_version: 3
|
128
|
+
summary: Schemaless metadata using JSON columns
|
129
|
+
test_files: []
|