has_meta_data 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG.rdoc +6 -0
- data/LICENSE +20 -0
- data/README.rdoc +85 -0
- data/Rakefile +14 -0
- data/init.rb +3 -0
- data/lib/has_meta_data.rb +69 -0
- data/lib/has_meta_data/railtie.rb +19 -0
- data/lib/has_meta_data/version.rb +3 -0
- data/spec/db/database.yml +21 -0
- data/spec/db/schema.rb +14 -0
- data/spec/db/test.sqlite3 +0 -0
- data/spec/factories/article.rb +16 -0
- data/spec/has_meta_data_spec.rb +75 -0
- data/spec/spec_helper.rb +25 -0
- metadata +113 -0
data/CHANGELOG.rdoc
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2008 [name of plugin creator]
|
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,85 @@
|
|
1
|
+
== Has Meta Data
|
2
|
+
|
3
|
+
This plugin allows you to extend one of your models with additional supplementary data stored in another table through a has_one relationship. The benefit of this plugin over a simple has_one is that the meta model class is dynamically defined (though extendable via the has_meta_data declaration) and fields on the meta model are automatically delegated to from the main model, allowing you to work solely with your primary model and not worry about the storage of the data in a separate meta table. This plugin also automatically handles creation of the meta model if you set a field on your model that exists in the meta model table.
|
4
|
+
|
5
|
+
Common use cases for this are database efficiency if only a small number of your records have a certain set of data or in conjunction with STI to accomplish something akin to Class Table Inheritance (CTI).
|
6
|
+
|
7
|
+
In short, it's a transparent way of splitting up data into two tables while still treating them as one model.
|
8
|
+
|
9
|
+
== Installation
|
10
|
+
|
11
|
+
This gem works with Rails 3 only.
|
12
|
+
|
13
|
+
In your Gemfile:
|
14
|
+
|
15
|
+
gem "has_meta_data"
|
16
|
+
|
17
|
+
== Basic Example
|
18
|
+
|
19
|
+
## Database Schema
|
20
|
+
create_table :articles, :force => true do |t|
|
21
|
+
t.string :title
|
22
|
+
t.text :body
|
23
|
+
end
|
24
|
+
|
25
|
+
create_table :article_meta, :force => true do |t|
|
26
|
+
t.references :article
|
27
|
+
t.string :reference_link
|
28
|
+
t.text :reference_note
|
29
|
+
end
|
30
|
+
|
31
|
+
## Model
|
32
|
+
class Article < ActiveRecord::Base
|
33
|
+
has_meta_data
|
34
|
+
end
|
35
|
+
|
36
|
+
## Exposed Class Methods & Scopes:
|
37
|
+
Article.meta_data_class
|
38
|
+
=> Article::Meta
|
39
|
+
Article.with_meta_data.all
|
40
|
+
=> (Articles that have associated meta data)
|
41
|
+
Article.without_meta_data.all
|
42
|
+
=> (Articles with no associated meta data)
|
43
|
+
|
44
|
+
## Usage Examples:
|
45
|
+
|
46
|
+
article = Article.create(:title => 'Title', :body => 'Body')
|
47
|
+
article.has_meta_data? # => false
|
48
|
+
|
49
|
+
article.reference_link = 'http://benhughes.name/'
|
50
|
+
article.has_meta_data? # => true
|
51
|
+
article.meta
|
52
|
+
# => #<Article::Meta reference_link: "http://benhughes.name/", reference_note: nil>
|
53
|
+
|
54
|
+
article = Article.create(:title => 'Title', :body => 'Body', :reference_link => 'http://benhughes.name/')
|
55
|
+
article.has_meta_data? # => true
|
56
|
+
article.reference_link # => "http://benhughes.name/"
|
57
|
+
|
58
|
+
== Changing the Defaults
|
59
|
+
|
60
|
+
You can control the name of the meta class, foreign key, and table name not unlike ActiveRecord associations:
|
61
|
+
|
62
|
+
class Article < ActiveRecord::Base
|
63
|
+
has_meta_data :class_name => 'AdditionalData',
|
64
|
+
:foreign_key => 'news_article_id',
|
65
|
+
:table_name => 'news_article_additional_data'
|
66
|
+
end
|
67
|
+
|
68
|
+
|
69
|
+
== Extending the Meta Data Class
|
70
|
+
|
71
|
+
Because you don't directly define the meta data class, you can specify a block of code to be run in its
|
72
|
+
context by passing a block to has_draft:
|
73
|
+
|
74
|
+
class Article < ActiveRecord::Base
|
75
|
+
belongs_to :user
|
76
|
+
|
77
|
+
has_meta_data do
|
78
|
+
belongs_to :some_author
|
79
|
+
|
80
|
+
def do_something
|
81
|
+
# Something
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
data/Rakefile
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "rake"
|
3
|
+
require "rake/rdoctask"
|
4
|
+
|
5
|
+
desc "Generate documentation for the plugin."
|
6
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
7
|
+
rdoc.rdoc_dir = "rdoc"
|
8
|
+
rdoc.title = "has_meta_data"
|
9
|
+
rdoc.options << "--line-numbers" << "--inline-source"
|
10
|
+
rdoc.rdoc_files.include('README')
|
11
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
12
|
+
end
|
13
|
+
|
14
|
+
Dir["#{File.dirname(__FILE__)}/lib/tasks/*.rake"].sort.each { |ext| load ext }
|
data/init.rb
ADDED
@@ -0,0 +1,69 @@
|
|
1
|
+
module HasMetaData
|
2
|
+
def self.included(base)
|
3
|
+
base.extend ClassMethods
|
4
|
+
end
|
5
|
+
|
6
|
+
module ClassMethods
|
7
|
+
def has_meta_data(options = {}, &block)
|
8
|
+
return if self.included_modules.include?(HasMetaData::InstanceMethods)
|
9
|
+
include HasMetaData::InstanceMethods
|
10
|
+
|
11
|
+
cattr_accessor :meta_data_class_name, :meta_data_foreign_key, :meta_data_table_name, :meta_data_columns
|
12
|
+
|
13
|
+
self.meta_data_class_name = options[:class_name] || 'Meta'
|
14
|
+
self.meta_data_foreign_key = options[:foreign_key] || self.to_s.foreign_key
|
15
|
+
self.meta_data_table_name = options[:table_name] || "#{table_name_prefix}#{self.name.demodulize.underscore}_meta#{table_name_suffix}"
|
16
|
+
|
17
|
+
|
18
|
+
# Create Relationship to Meta Data
|
19
|
+
class_eval do
|
20
|
+
has_one :meta,
|
21
|
+
:class_name => "#{self.to_s}::#{meta_data_class_name}",
|
22
|
+
:foreign_key => meta_data_foreign_key,
|
23
|
+
:dependent => :destroy
|
24
|
+
|
25
|
+
named_scope :with_meta, :include => [:meta], :conditions => "#{meta_data_table_name}.id IS NOT NULL"
|
26
|
+
named_scope :without_meta, :include => [:meta], :conditions => "#{meta_data_table_name}.id IS NULL"
|
27
|
+
end
|
28
|
+
|
29
|
+
# Dynamically Create Model::Meta Class
|
30
|
+
const_set(meta_data_class_name, Class.new(ActiveRecord::Base))
|
31
|
+
|
32
|
+
meta_data_class.cattr_accessor :original_class
|
33
|
+
meta_data_class.original_class = self
|
34
|
+
meta_data_class.set_table_name(meta_data_table_name)
|
35
|
+
|
36
|
+
# Draft Parent Association
|
37
|
+
meta_data_class.belongs_to self.to_s.demodulize.underscore.to_sym, :class_name => "::#{self.to_s}", :foreign_key => meta_data_foreign_key
|
38
|
+
|
39
|
+
# Block extension
|
40
|
+
meta_data_class.class_eval(&block) if block_given?
|
41
|
+
|
42
|
+
# Finally setup attribute delegation to the meta fields
|
43
|
+
self.meta_data_columns = meta_data_class.content_columns.map(&:name)
|
44
|
+
|
45
|
+
meta_data_columns.each do |field|
|
46
|
+
define_method(field.to_sym) do
|
47
|
+
meta.send(field.to_sym) if has_meta_data?
|
48
|
+
end
|
49
|
+
|
50
|
+
define_method("#{field}=".to_sym) do |value|
|
51
|
+
self.build_meta unless has_meta_data?
|
52
|
+
meta.send("#{field}=".to_sym, value)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def meta_data_class
|
58
|
+
const_get(meta_data_class_name)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
module InstanceMethods
|
63
|
+
def has_meta_data?
|
64
|
+
!self.meta.nil?
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
require "has_meta_data/railtie"
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module HasMetaData
|
2
|
+
if defined?(Rails::Railtie)
|
3
|
+
require "rails"
|
4
|
+
|
5
|
+
class Railtie < Rails::Railtie
|
6
|
+
initializer "has_draft.extend_active_record" do
|
7
|
+
ActiveSupport.on_load(:active_record) do
|
8
|
+
HasMetaData::Railtie.insert
|
9
|
+
end
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
class Railtie
|
15
|
+
def self.insert
|
16
|
+
ActiveRecord::Base.send(:include, HasMetaData)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
sqlite:
|
2
|
+
adapter: sqlite
|
3
|
+
database: spec/db/test.sqlite
|
4
|
+
|
5
|
+
sqlite3:
|
6
|
+
adapter: sqlite3
|
7
|
+
database: spec/db/test.sqlite3
|
8
|
+
|
9
|
+
postgresql:
|
10
|
+
adapter: postgresql
|
11
|
+
username: postgres
|
12
|
+
password: postgres
|
13
|
+
database: has_draft_plugin_test
|
14
|
+
min_messages: ERROR
|
15
|
+
|
16
|
+
mysql:
|
17
|
+
adapter: mysql
|
18
|
+
host: localhost
|
19
|
+
username: root
|
20
|
+
password:
|
21
|
+
database: has_draft_plugin_test
|
data/spec/db/schema.rb
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
ActiveRecord::Schema.define(:version => 0) do
|
2
|
+
|
3
|
+
create_table :articles, :force => true do |t|
|
4
|
+
t.string :title
|
5
|
+
t.text :body
|
6
|
+
end
|
7
|
+
|
8
|
+
create_table :article_meta, :force => true do |t|
|
9
|
+
t.references :article
|
10
|
+
t.string :reference_link
|
11
|
+
t.text :reference_note
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
Binary file
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require "factory_girl"
|
2
|
+
|
3
|
+
Factory.define(:article) do |f|
|
4
|
+
f.title { Faker::Lorem.sentence }
|
5
|
+
f.body { Faker::Lorem.paragraphs.join("\n\n") }
|
6
|
+
end
|
7
|
+
|
8
|
+
Factory.define(:article_meta, :class => Article::Meta) do |f|
|
9
|
+
f.association(:article)
|
10
|
+
f.reference_link { "http://" + Faker::Internet.domain_name }
|
11
|
+
f.reference_note { Faker::Lorem.paragraph }
|
12
|
+
end
|
13
|
+
|
14
|
+
Factory.define(:article_with_meta_data, :parent => :article) do |f|
|
15
|
+
f.association(:meta, :factory => :article_meta)
|
16
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe HasMetaData do
|
4
|
+
context "Article model" do
|
5
|
+
it "should expose #meta_data_class_name as Meta" do
|
6
|
+
Article.meta_data_class_name.should == "Meta"
|
7
|
+
end
|
8
|
+
|
9
|
+
it "should expose #meta_data_class as Article::Meta" do
|
10
|
+
Article.meta_data_class.should == Article::Meta
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should default #meta_data_foreign_key to article_id" do
|
14
|
+
Article.meta_data_foreign_key.should == "article_id"
|
15
|
+
end
|
16
|
+
|
17
|
+
it "should find #meta_data_columns" do
|
18
|
+
Article.meta_data_columns.should == ["reference_link", "reference_note"]
|
19
|
+
end
|
20
|
+
|
21
|
+
context "Meta model" do
|
22
|
+
it "should be defined under the Article namespace" do
|
23
|
+
Article.constants.should include('Meta')
|
24
|
+
end
|
25
|
+
|
26
|
+
it "should expose #original_class as Article" do
|
27
|
+
Article::Meta.original_class.should == Article
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
context "with an article without meta data" do
|
33
|
+
before { @article = Factory.create(:article) }
|
34
|
+
|
35
|
+
it "should not have meta data" do
|
36
|
+
@article.should_not have_meta_data
|
37
|
+
end
|
38
|
+
|
39
|
+
it "should return nil for meta data fields" do
|
40
|
+
@article.reference_link.should be_nil
|
41
|
+
@article.reference_note.should be_nil
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
context "with an article with meta data" do
|
46
|
+
before { @article = Factory.create(:article_with_meta_data) }
|
47
|
+
|
48
|
+
it "should have meta data" do
|
49
|
+
@article.should have_meta_data
|
50
|
+
end
|
51
|
+
|
52
|
+
it "should return values for meta data fields" do
|
53
|
+
@article.reference_link.should_not be_nil
|
54
|
+
@article.reference_note.should_not be_nil
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
context "with an article after setting meta data fields through mutators" do
|
59
|
+
before do
|
60
|
+
@article = Factory.create(:article)
|
61
|
+
@article.reference_link = "http://railsgarden.com/"
|
62
|
+
@article.reference_note = "Notes"
|
63
|
+
end
|
64
|
+
|
65
|
+
it "should have meta data" do
|
66
|
+
@article.should have_meta_data
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should return values for meta data fields" do
|
70
|
+
@article.reference_link.should == "http://railsgarden.com/"
|
71
|
+
@article.reference_note.should == "Notes"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
require "rubygems"
|
2
|
+
require "rspec"
|
3
|
+
require "factory_girl"
|
4
|
+
require "faker"
|
5
|
+
require "rails"
|
6
|
+
require "active_record"
|
7
|
+
require "active_support"
|
8
|
+
|
9
|
+
# Establish DB Connection
|
10
|
+
config = YAML::load(IO.read(File.join(File.dirname(__FILE__), 'db', 'database.yml')))
|
11
|
+
ActiveRecord::Base.configurations = {'test' => config[ENV['DB'] || 'sqlite3']}
|
12
|
+
ActiveRecord::Base.establish_connection(ActiveRecord::Base.configurations['test'])
|
13
|
+
|
14
|
+
# Load Test Schema into the Database
|
15
|
+
load(File.dirname(__FILE__) + "/db/schema.rb")
|
16
|
+
|
17
|
+
require File.dirname(__FILE__) + '/../init'
|
18
|
+
|
19
|
+
# Example has_meta_data Model:
|
20
|
+
class Article < ActiveRecord::Base
|
21
|
+
has_meta_data
|
22
|
+
end
|
23
|
+
|
24
|
+
# Load Factories:
|
25
|
+
Dir[File.join(File.dirname(__FILE__), "factories/**/*.rb")].each {|f| require f}
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: has_meta_data
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 23
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 1
|
8
|
+
- 0
|
9
|
+
- 0
|
10
|
+
version: 1.0.0
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Ben Hughes
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-15 00:00:00 +08:00
|
19
|
+
default_executable:
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: activesupport
|
23
|
+
prerelease: false
|
24
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
hash: 7
|
30
|
+
segments:
|
31
|
+
- 3
|
32
|
+
- 0
|
33
|
+
- 0
|
34
|
+
version: 3.0.0
|
35
|
+
type: :runtime
|
36
|
+
version_requirements: *id001
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: activerecord
|
39
|
+
prerelease: false
|
40
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ">="
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
hash: 7
|
46
|
+
segments:
|
47
|
+
- 3
|
48
|
+
- 0
|
49
|
+
- 0
|
50
|
+
version: 3.0.0
|
51
|
+
type: :runtime
|
52
|
+
version_requirements: *id002
|
53
|
+
description: Create one model backed by a primary table and a supplementary 'meta' table.
|
54
|
+
email: ben@railsgarden.com
|
55
|
+
executables: []
|
56
|
+
|
57
|
+
extensions: []
|
58
|
+
|
59
|
+
extra_rdoc_files: []
|
60
|
+
|
61
|
+
files:
|
62
|
+
- lib/has_meta_data/railtie.rb
|
63
|
+
- lib/has_meta_data/version.rb
|
64
|
+
- lib/has_meta_data.rb
|
65
|
+
- spec/db/database.yml
|
66
|
+
- spec/db/schema.rb
|
67
|
+
- spec/db/test.sqlite3
|
68
|
+
- spec/factories/article.rb
|
69
|
+
- spec/has_meta_data_spec.rb
|
70
|
+
- spec/spec_helper.rb
|
71
|
+
- CHANGELOG.rdoc
|
72
|
+
- LICENSE
|
73
|
+
- Rakefile
|
74
|
+
- README.rdoc
|
75
|
+
- init.rb
|
76
|
+
has_rdoc: true
|
77
|
+
homepage: http://github.com/rubiety/has_meta_data
|
78
|
+
licenses: []
|
79
|
+
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
|
83
|
+
require_paths:
|
84
|
+
- lib
|
85
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
hash: 3
|
91
|
+
segments:
|
92
|
+
- 0
|
93
|
+
version: "0"
|
94
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
95
|
+
none: false
|
96
|
+
requirements:
|
97
|
+
- - ">="
|
98
|
+
- !ruby/object:Gem::Version
|
99
|
+
hash: 19
|
100
|
+
segments:
|
101
|
+
- 1
|
102
|
+
- 3
|
103
|
+
- 4
|
104
|
+
version: 1.3.4
|
105
|
+
requirements: []
|
106
|
+
|
107
|
+
rubyforge_project: has_meta_data
|
108
|
+
rubygems_version: 1.3.7
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: Extended attributes in an attached meta table.
|
112
|
+
test_files: []
|
113
|
+
|