doeskeyvalue 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/.document +5 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +16 -0
- data/LICENSE.txt +20 -0
- data/README.rdoc +98 -0
- data/Rakefile +40 -0
- data/VERSION +1 -0
- data/doeskeyvalue.gemspec +56 -0
- data/lib/doeskeyvalue/indexes.rb +84 -0
- data/lib/doeskeyvalue/keys.rb +40 -0
- data/lib/doeskeyvalue/util.rb +27 -0
- data/lib/doeskeyvalue.rb +83 -0
- data/lib/generators/doeskeyvalue/doeskeyvalue_generator.rb +25 -0
- data/lib/generators/doeskeyvalue/templates/create_key_value_index.rb +30 -0
- metadata +94 -0
data/.document
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2011 Awexome Labs, LLC
|
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.rdoc
ADDED
@@ -0,0 +1,98 @@
|
|
1
|
+
= DoesKeyValue
|
2
|
+
|
3
|
+
Bring the fun of NoSQL into your SQL-backed Active Record objects in a compartmentalized way. Turns
|
4
|
+
any text field on your objects into a schema-less key value store.
|
5
|
+
|
6
|
+
|
7
|
+
== Installation
|
8
|
+
|
9
|
+
Do the usual, of course:
|
10
|
+
|
11
|
+
gem install doeskeyvalue
|
12
|
+
|
13
|
+
And add a gem dependency to your Gemfile:
|
14
|
+
|
15
|
+
gem "doeskeyvalue", ">=0.0.1"
|
16
|
+
|
17
|
+
|
18
|
+
== Example (TODO: Update this with new API)
|
19
|
+
|
20
|
+
To add document_fields to your ActiveRecord object, first add a text column to your class' backing table that will hold your document content. By default, do_document_fields will expect this column to be named "document", but you can override that easily.
|
21
|
+
|
22
|
+
Once you've migrated that change into your model, here's a sample of hold to add document-style blob fields to your object:
|
23
|
+
|
24
|
+
class ObjectWithBlob < ActiveRecord::Base
|
25
|
+
|
26
|
+
# Declare that we are using document blobs in a particular column of this object:
|
27
|
+
do_document_fields
|
28
|
+
|
29
|
+
# Declare the columns we are adding to our document:
|
30
|
+
document_field :name, String
|
31
|
+
document_field :phone, String
|
32
|
+
document_field :age, Integer
|
33
|
+
|
34
|
+
# Declare the document fields you'd like to track with indexes:
|
35
|
+
document_index :name
|
36
|
+
|
37
|
+
|
38
|
+
# If we wanted to change the name of the column in which we store the document, we'd do this:
|
39
|
+
# do_document_fields :something_other_than_document. Like so:
|
40
|
+
|
41
|
+
do_document_fields :settings
|
42
|
+
|
43
|
+
# Changing the name of the column gives you a specifically-formatted field-definition method:
|
44
|
+
|
45
|
+
settings_field :age
|
46
|
+
settings_field :sex
|
47
|
+
settings_field :location
|
48
|
+
|
49
|
+
# This also provides a customized way to index:
|
50
|
+
|
51
|
+
settings_index :location
|
52
|
+
|
53
|
+
end
|
54
|
+
|
55
|
+
|
56
|
+
Now, you can add and remove new fields to this object within the document as simply as adding or removing declarations of "document_field".
|
57
|
+
|
58
|
+
|
59
|
+
Once you've added any indices to your model's fields, you'll want to be sure to build your supporting index tables. We use separate tables with database-level indexing to support indexed lookups of data in your document_field attributes. Build these tables by running this rake task:
|
60
|
+
|
61
|
+
rake db:migrate:document_indexes
|
62
|
+
|
63
|
+
|
64
|
+
Now, you can use strictly-provided finders to find your objects by their document field attributes. Considering the model we described above, you can lookup by any *indexed* field like so:
|
65
|
+
|
66
|
+
obj = ObjectWithBlob.find_by_name("Charlie")
|
67
|
+
=> [#<ObjectWithBlob id: 412, document: {:name=>"Charlie", :phone=>"212-555-1234", :age=>34}, created_at: "2010-03-22 20:49:03", updated_at: "2010-03-22 20:49:03">]
|
68
|
+
|
69
|
+
Finders are only added to document fields that are indexed. Finders also are all "find_all" lookups on equality.
|
70
|
+
|
71
|
+
If you'd like to use special sub-searching within any of your document-formatted fields, you can use the hash-based search conditions. Given the "settings" document declared in the example above, we can now also do this:
|
72
|
+
|
73
|
+
obj = ObjectWithBlob.find_with_settings(:location=>"San Francisco, CA")
|
74
|
+
=> [#<ObjectWithBlob id: 412, settings: {:age=>32, :sex=>"Female", :location=>"San Francisco, CA"}, created_at: "2010-03-22 20:49:03", updated_at: "2010-03-22 20:49:03">]
|
75
|
+
|
76
|
+
It's simple.
|
77
|
+
|
78
|
+
|
79
|
+
== Prior Versions
|
80
|
+
|
81
|
+
DoesKeyValue is a much newer and cleaner version of the old "do_document_fields" plugin for prior versions
|
82
|
+
of Rails. Check that out at http://github.com/mccolin/do_document_fields
|
83
|
+
|
84
|
+
|
85
|
+
== Contributions
|
86
|
+
|
87
|
+
* Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet
|
88
|
+
* Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it
|
89
|
+
* Fork the project
|
90
|
+
* Start a feature/bugfix branch
|
91
|
+
* Commit and push until you are happy with your contribution
|
92
|
+
* Make sure to add tests for it. This is important so I don't break it in a future version unintentionally.
|
93
|
+
* Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it.
|
94
|
+
|
95
|
+
== Copyright
|
96
|
+
|
97
|
+
Copyright (c) 2011 Awexome Labs, LLC. http://awexomelabs.com/
|
98
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,40 @@
|
|
1
|
+
# AWEXOME LABS
|
2
|
+
# Rakefile
|
3
|
+
|
4
|
+
require 'rubygems'
|
5
|
+
require 'bundler'
|
6
|
+
begin
|
7
|
+
Bundler.setup(:default, :development)
|
8
|
+
rescue Bundler::BundlerError => e
|
9
|
+
$stderr.puts e.message
|
10
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
11
|
+
exit e.status_code
|
12
|
+
end
|
13
|
+
require 'rake'
|
14
|
+
|
15
|
+
require 'jeweler'
|
16
|
+
Jeweler::Tasks.new do |gem|
|
17
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
18
|
+
gem.name = "doeskeyvalue"
|
19
|
+
gem.homepage = "http://github.com/awexome/doeskeyvalue"
|
20
|
+
gem.license = "MIT"
|
21
|
+
gem.summary = %Q{Add schema-less NoSQL-like key values to any ActiveRecord class}
|
22
|
+
gem.description = %Q{Bring the fun of NoSQL into your SQL-backed Active Record objects in a compartmentalized way. Turns any text field on your objects into a schema-less key value store.}
|
23
|
+
gem.email = "gems@awexomelabs.com"
|
24
|
+
gem.authors = ["Awexome Labs"]
|
25
|
+
|
26
|
+
# Internal Gem Dependencies:
|
27
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
28
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
29
|
+
end
|
30
|
+
Jeweler::RubygemsDotOrgTasks.new
|
31
|
+
|
32
|
+
require 'rake/rdoctask'
|
33
|
+
Rake::RDocTask.new do |rdoc|
|
34
|
+
version = File.exist?('VERSION') ? File.read('VERSION') : ""
|
35
|
+
|
36
|
+
rdoc.rdoc_dir = 'rdoc'
|
37
|
+
rdoc.title = "doeskeyvalue #{version}"
|
38
|
+
rdoc.rdoc_files.include('README*')
|
39
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
40
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
@@ -0,0 +1,56 @@
|
|
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 = %q{doeskeyvalue}
|
8
|
+
s.version = "0.1.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Awexome Labs"]
|
12
|
+
s.date = %q{2011-02-15}
|
13
|
+
s.description = %q{Bring the fun of NoSQL into your SQL-backed Active Record objects in a compartmentalized way. Turns any text field on your objects into a schema-less key value store.}
|
14
|
+
s.email = %q{gems@awexomelabs.com}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE.txt",
|
17
|
+
"README.rdoc"
|
18
|
+
]
|
19
|
+
s.files = [
|
20
|
+
".document",
|
21
|
+
"Gemfile",
|
22
|
+
"Gemfile.lock",
|
23
|
+
"LICENSE.txt",
|
24
|
+
"README.rdoc",
|
25
|
+
"Rakefile",
|
26
|
+
"VERSION",
|
27
|
+
"doeskeyvalue.gemspec",
|
28
|
+
"lib/doeskeyvalue.rb",
|
29
|
+
"lib/doeskeyvalue/indexes.rb",
|
30
|
+
"lib/doeskeyvalue/keys.rb",
|
31
|
+
"lib/doeskeyvalue/util.rb",
|
32
|
+
"lib/generators/doeskeyvalue/doeskeyvalue_generator.rb",
|
33
|
+
"lib/generators/doeskeyvalue/templates/create_key_value_index.rb"
|
34
|
+
]
|
35
|
+
s.homepage = %q{http://github.com/awexome/doeskeyvalue}
|
36
|
+
s.licenses = ["MIT"]
|
37
|
+
s.require_paths = ["lib"]
|
38
|
+
s.rubygems_version = %q{1.5.2}
|
39
|
+
s.summary = %q{Add schema-less NoSQL-like key values to any ActiveRecord class}
|
40
|
+
|
41
|
+
if s.respond_to? :specification_version then
|
42
|
+
s.specification_version = 3
|
43
|
+
|
44
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
45
|
+
s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
|
46
|
+
s.add_development_dependency(%q<jeweler>, ["~> 1.5.2"])
|
47
|
+
else
|
48
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
49
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
50
|
+
end
|
51
|
+
else
|
52
|
+
s.add_dependency(%q<bundler>, ["~> 1.0.0"])
|
53
|
+
s.add_dependency(%q<jeweler>, ["~> 1.5.2"])
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# AWEXOME LABS
|
2
|
+
# DoesKeyValue
|
3
|
+
#
|
4
|
+
# Indexes -- ActiveRecord::Base methods for settings and retrieval of values based
|
5
|
+
# on key indexes
|
6
|
+
|
7
|
+
module DoesKeyValue
|
8
|
+
module Indexes
|
9
|
+
|
10
|
+
def declare_index(key_value_column, key_name, opts={})
|
11
|
+
puts "DOES_KEY_VALUE: Index declared: #{key_value_column}, #{key_name}, #{opts.inspect}"
|
12
|
+
raise DoesKeyValue::NoColumnNameSpecified unless key_value_column
|
13
|
+
raise DoesKeyValue::NoKeyNameSpecified unless key_name
|
14
|
+
raise DoesKeyValue::KeyAndIndexOptionsMustBeHash unless opts.is_a?(Hash)
|
15
|
+
|
16
|
+
search_key = "#{key_value_column}.#{key_name}"
|
17
|
+
raise DoesKeyValue::NoKeyForThatIndex if !self.respond_to?(key_name) || !self.respond_to?("#{key_name}=")
|
18
|
+
|
19
|
+
class_name = self.name.underscore
|
20
|
+
class_table_name = self.table_name
|
21
|
+
index_table_name = "key_value_index"
|
22
|
+
|
23
|
+
# INDEX TABLE: key_value_index
|
24
|
+
# id:int
|
25
|
+
# key_name:string
|
26
|
+
# value:string
|
27
|
+
# obj_type:string
|
28
|
+
# obj_id:int
|
29
|
+
|
30
|
+
# Define finders that leverage the custom index table:
|
31
|
+
instance_eval <<-EOS
|
32
|
+
def find_all_by_#{key_value_column}_#{key_name}(value)
|
33
|
+
find(
|
34
|
+
:all,
|
35
|
+
:select=>"*",
|
36
|
+
:from=>"#{index_table_name}",
|
37
|
+
:conditions=>["`#{index_table_name}`.obj_type = ? AND `#{index_table_name}`.key_name = ? AND `#{index_table_name}`.value = ?", self.to_s, "#{search_key}", value],
|
38
|
+
:joins => "LEFT JOIN `#{class_table_name}` ON `#{class_table_name}`.id = `#{index_table_name}`.obj_id"
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_all_by_#{key_name}(value)
|
43
|
+
find_all_by_#{key_value_column}_#{key_name}(value)
|
44
|
+
end
|
45
|
+
|
46
|
+
def find_all_with_#{key_value_column}(opts={})
|
47
|
+
conds = Array.new
|
48
|
+
opts.each do |k, v|
|
49
|
+
conds.add_condition(["`#{index_table_name}`.obj_type = ? AND `#{index_table_name}`.key_name = ? AND `#{index_table_name}`.value = ?", self.to_s, "#{search_key}", v])
|
50
|
+
end
|
51
|
+
find(
|
52
|
+
:all,
|
53
|
+
:select=>"*",
|
54
|
+
:from=>"#{index_table_name}",
|
55
|
+
:conditions=>conds,
|
56
|
+
:joins=>"LEFT JOIN `#{class_table_name}` ON `#{class_table_name}`.id = `#{index_table_name}`.obj_id"
|
57
|
+
)
|
58
|
+
end
|
59
|
+
EOS
|
60
|
+
|
61
|
+
# Provide a callback after save which updates the index
|
62
|
+
define_method("update_index_#{key_value_column}_#{key_name}_after_save") do
|
63
|
+
class_name = self.class.name.underscore
|
64
|
+
class_table_name = self.class.table_name
|
65
|
+
index_table_name = "key_value_indexes"
|
66
|
+
# TODO: Restrict value to 255 characters, the table-enforced limit
|
67
|
+
idx_id = ActiveRecord::Base.connection.insert("INSERT INTO `#{index_table_name}` (`obj_type`,`obj_id`,`key_name`,`value`) VALUES (\""+self.class.to_s+"\","+self.id.to_s+", \""+search_key.to_s+"\", \"#{self.send(key_name).to_s}\")")
|
68
|
+
end
|
69
|
+
after_save "update_index_#{key_value_column}_#{key_name}_after_save"
|
70
|
+
|
71
|
+
# Provide a callback after destroy to likewise update the index
|
72
|
+
define_method("update_index_#{key_value_column}_#{key_name}_after_destroy") do
|
73
|
+
class_name = self.class.name.underscore
|
74
|
+
class_table_name = self.class.table_name
|
75
|
+
index_table_name = "key_value_indexes"
|
76
|
+
num_del = ActiveRecord::Base.connection.delete("DELETE FROM `#{index_table_name}` WHERE `obj_type` = \"#{self.class}\" AND `obj_id` = #{self.id}")
|
77
|
+
end
|
78
|
+
after_destroy "update_index_#{key_value_column}_#{key_name}_after_destroy"
|
79
|
+
|
80
|
+
end
|
81
|
+
|
82
|
+
|
83
|
+
end # Index
|
84
|
+
end # DoesKeyValue
|
@@ -0,0 +1,40 @@
|
|
1
|
+
# AWEXOME LABS
|
2
|
+
# DoesKeyValue
|
3
|
+
#
|
4
|
+
# Keys -- ActiveRecord::Base methods for setting schemaless keys
|
5
|
+
|
6
|
+
module DoesKeyValue
|
7
|
+
module Keys
|
8
|
+
|
9
|
+
def declare_key(key_value_column, key_name, opts={})
|
10
|
+
puts "DOES_KEY_VALUE: Key declared: #{key_value_column}, #{key_name}, #{opts.inspect}"
|
11
|
+
raise DoesKeyValue::NoColumnNameSpecified unless key_value_column
|
12
|
+
raise DoesKeyValue::NoKeyNameSpecified unless key_name
|
13
|
+
raise DoesKeyValue::KeyAndIndexOptionsMustBeHash unless opts.is_a?(Hash)
|
14
|
+
|
15
|
+
# Define accessors for the key column in the AR class:
|
16
|
+
class_eval <<-EOS
|
17
|
+
def #{key_name}
|
18
|
+
puts "DOES_KEY_VALUE: Accessor for `#{key_name}` invoked"
|
19
|
+
return (self.send(:#{key_value_column}) || Hash.new)[:#{key_name}]
|
20
|
+
end
|
21
|
+
puts "DOES_KEY_VALUE: Key accessor `#{key_name}` declared"
|
22
|
+
|
23
|
+
def #{key_name}=(value)
|
24
|
+
puts "DOES_KEY_VALUE: Setter for `#{key_name}` invoked"
|
25
|
+
key_set = self.send(:#{key_value_column}) || Hash.new
|
26
|
+
key_set[:#{key_name}] = value
|
27
|
+
self.send("#{key_value_column}=", key_set)
|
28
|
+
end
|
29
|
+
puts "DOES_KEY_VALUE: Key manipulator `#{key_name}=` declared"
|
30
|
+
EOS
|
31
|
+
|
32
|
+
# Check for opts[:index=>true] and if present, call declare_index
|
33
|
+
if opts[:index] == true
|
34
|
+
declare_index(key_value_column, key_name) # TODO: Provide mechanism for passing index options
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end # Keys
|
40
|
+
end # DoesKeyValue
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# AWEXOME LABS
|
2
|
+
# DoesKeyValue
|
3
|
+
|
4
|
+
|
5
|
+
module DoesKeyValue
|
6
|
+
module Util
|
7
|
+
|
8
|
+
module CondArray
|
9
|
+
def add_condition(cond, conj="AND")
|
10
|
+
if cond.is_a?(Array)
|
11
|
+
if self.empty?
|
12
|
+
(self << cond).flatten!
|
13
|
+
else
|
14
|
+
self[0] += " #{conj} #{cond.shift}"
|
15
|
+
(self << cond).flatten!
|
16
|
+
end
|
17
|
+
elsif cond.is_a?(String)
|
18
|
+
self[0] += " #{conj} #{cond}"
|
19
|
+
else
|
20
|
+
raise "Condition must be an Array or String"
|
21
|
+
end
|
22
|
+
self
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end # DoesKeyValue
|
data/lib/doeskeyvalue.rb
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
# AWEXOME LABS
|
2
|
+
# DoesKeyValue
|
3
|
+
|
4
|
+
require 'doeskeyvalue'
|
5
|
+
require 'rails'
|
6
|
+
require 'active_record'
|
7
|
+
|
8
|
+
require 'doeskeyvalue/keys'
|
9
|
+
require 'doeskeyvalue/indexes'
|
10
|
+
require 'doeskeyvalue/util'
|
11
|
+
|
12
|
+
|
13
|
+
module DoesKeyValue
|
14
|
+
|
15
|
+
# Create a Rails Engine
|
16
|
+
class Engine < Rails::Engine
|
17
|
+
end
|
18
|
+
|
19
|
+
# Return the current working version from VERSION file:
|
20
|
+
def self.version
|
21
|
+
@@version ||= File.open(File.join(File.dirname(__FILE__), "..", "VERSION"), "r").read
|
22
|
+
end
|
23
|
+
|
24
|
+
# Exception Types for DoesKeyValue
|
25
|
+
class NoColumnNameSpecified < Exception
|
26
|
+
def initialize(msg="A class column name must be provided for storing key values in blob"); super(msg); end
|
27
|
+
end
|
28
|
+
class NoKeyNameSpecified < Exception
|
29
|
+
def initialize(msg="A key name must be provided to build a DoesKeyValue key"); super(msg); end
|
30
|
+
end
|
31
|
+
class NoKeyForThatIndex < Exception
|
32
|
+
def initialize(msg="A key must exist before an index can be applied to it"); super(msg); end
|
33
|
+
end
|
34
|
+
class KeyAndIndexOptionsMustBeHash < Exception
|
35
|
+
def initialize(msg="Options passed to declarations of keys and indexes must of class Hash"); super(msg); end
|
36
|
+
end
|
37
|
+
class KeyValueIndexTableDoesNotExist < Exception
|
38
|
+
def initialize(msg="DoesKeyValue requires an index table be generated to use key indexes. Use generator to generate migration"); super(msg); end
|
39
|
+
end
|
40
|
+
|
41
|
+
end # DoesKeyValue
|
42
|
+
|
43
|
+
|
44
|
+
module ActiveRecord
|
45
|
+
class Base
|
46
|
+
|
47
|
+
# Call this method within your class to establish key-value behavior and prep
|
48
|
+
# the internal structure that will hold the blob
|
49
|
+
def self.doeskeyvalue(column, opts={})
|
50
|
+
puts "DOES_KEY_VALUE: Turned on for AR Column:#{column}"
|
51
|
+
self.instance_eval do
|
52
|
+
extend DoesKeyValue::Keys
|
53
|
+
extend DoesKeyValue::Indexes
|
54
|
+
|
55
|
+
# Identify the AR text column holding our data and serialize it:
|
56
|
+
@@key_value_column = column.to_sym
|
57
|
+
cattr_accessor :key_value_column
|
58
|
+
serialize @@key_value_column, Hash
|
59
|
+
end
|
60
|
+
|
61
|
+
Array.class_eval do
|
62
|
+
include DoesKeyValue::Util::CondArray
|
63
|
+
end
|
64
|
+
|
65
|
+
instance_eval <<-EOS
|
66
|
+
def #{@@key_value_column}_key(key_name, opts={})
|
67
|
+
puts "DOES_KEY_VALUE: Inside defined method #{@@key_value_column}_key"
|
68
|
+
key_name = key_name.to_sym
|
69
|
+
declare_key(@@key_value_column, key_name, opts)
|
70
|
+
end
|
71
|
+
|
72
|
+
def #{@@key_value_column}_index(key_name, opts={})
|
73
|
+
puts "DOES_KEY_VALUE: Inside defined method #{@@key_value_column}_index"
|
74
|
+
key_name = key_name.to_sym
|
75
|
+
declare_index(@@key_value_column, key_name, opts)
|
76
|
+
end
|
77
|
+
EOS
|
78
|
+
puts "DOES_KEY_VALUE: key and index methods declared"
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
end # ActiveRecord::Base
|
83
|
+
end # ActiveRecord
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# AWEXOME LABS
|
2
|
+
# DoesKeyValue
|
3
|
+
#
|
4
|
+
# IndexTableGenerator -- generator for the Index database table
|
5
|
+
|
6
|
+
require 'rails/generators'
|
7
|
+
require 'rails/generators/migration'
|
8
|
+
|
9
|
+
class DoeskeyvalueGenerator < Rails::Generators::Base
|
10
|
+
include Rails::Generators::Migration
|
11
|
+
source_root File.expand_path('../templates', __FILE__)
|
12
|
+
|
13
|
+
def self.next_migration_number(path)
|
14
|
+
if ActiveRecord::Base.timestamped_migrations
|
15
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
16
|
+
else
|
17
|
+
"%.3d" % (current_migration_number(path) + 1)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def create_migration_file
|
22
|
+
migration_template 'create_key_value_index.rb', 'db/migrate/create_key_value_index.rb'
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
# AWEXOME LABS
|
2
|
+
# DoesKeyValue
|
3
|
+
#
|
4
|
+
# CreateKeyValueIndex -- generated migration template for key/value index table
|
5
|
+
|
6
|
+
class CreateKeyValueIndex < ActiveRecord::Migration
|
7
|
+
def self.up
|
8
|
+
create_table :key_value_index do |t|
|
9
|
+
# The key is a composite of the grouping and key name (e.g., "settings.user_id")
|
10
|
+
t.string :key_name
|
11
|
+
|
12
|
+
# The stored value is saved as varchar(255), which limits indexability slightly:
|
13
|
+
t.string :value
|
14
|
+
|
15
|
+
# Store details about the target object:
|
16
|
+
t.string :obj_type
|
17
|
+
t.integer :obj_id
|
18
|
+
|
19
|
+
# Track all touches:
|
20
|
+
t.timestamps
|
21
|
+
end
|
22
|
+
|
23
|
+
# Index is important here:
|
24
|
+
add_index :key_value_index, [:obj_type, :key_name, :value]
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.down
|
28
|
+
drop_table :key_value_index
|
29
|
+
end
|
30
|
+
end
|
metadata
ADDED
@@ -0,0 +1,94 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: doeskeyvalue
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.0
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Awexome Labs
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-02-15 00:00:00 -05:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: bundler
|
18
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
19
|
+
none: false
|
20
|
+
requirements:
|
21
|
+
- - ~>
|
22
|
+
- !ruby/object:Gem::Version
|
23
|
+
version: 1.0.0
|
24
|
+
type: :development
|
25
|
+
prerelease: false
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: jeweler
|
29
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
30
|
+
none: false
|
31
|
+
requirements:
|
32
|
+
- - ~>
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.5.2
|
35
|
+
type: :development
|
36
|
+
prerelease: false
|
37
|
+
version_requirements: *id002
|
38
|
+
description: Bring the fun of NoSQL into your SQL-backed Active Record objects in a compartmentalized way. Turns any text field on your objects into a schema-less key value store.
|
39
|
+
email: gems@awexomelabs.com
|
40
|
+
executables: []
|
41
|
+
|
42
|
+
extensions: []
|
43
|
+
|
44
|
+
extra_rdoc_files:
|
45
|
+
- LICENSE.txt
|
46
|
+
- README.rdoc
|
47
|
+
files:
|
48
|
+
- .document
|
49
|
+
- Gemfile
|
50
|
+
- Gemfile.lock
|
51
|
+
- LICENSE.txt
|
52
|
+
- README.rdoc
|
53
|
+
- Rakefile
|
54
|
+
- VERSION
|
55
|
+
- doeskeyvalue.gemspec
|
56
|
+
- lib/doeskeyvalue.rb
|
57
|
+
- lib/doeskeyvalue/indexes.rb
|
58
|
+
- lib/doeskeyvalue/keys.rb
|
59
|
+
- lib/doeskeyvalue/util.rb
|
60
|
+
- lib/generators/doeskeyvalue/doeskeyvalue_generator.rb
|
61
|
+
- lib/generators/doeskeyvalue/templates/create_key_value_index.rb
|
62
|
+
has_rdoc: true
|
63
|
+
homepage: http://github.com/awexome/doeskeyvalue
|
64
|
+
licenses:
|
65
|
+
- MIT
|
66
|
+
post_install_message:
|
67
|
+
rdoc_options: []
|
68
|
+
|
69
|
+
require_paths:
|
70
|
+
- lib
|
71
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
72
|
+
none: false
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
hash: -2545379118669446342
|
77
|
+
segments:
|
78
|
+
- 0
|
79
|
+
version: "0"
|
80
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
version: "0"
|
86
|
+
requirements: []
|
87
|
+
|
88
|
+
rubyforge_project:
|
89
|
+
rubygems_version: 1.5.2
|
90
|
+
signing_key:
|
91
|
+
specification_version: 3
|
92
|
+
summary: Add schema-less NoSQL-like key values to any ActiveRecord class
|
93
|
+
test_files: []
|
94
|
+
|