metafy 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +4 -0
- data/Gemfile +4 -0
- data/MIT-LICENSE +21 -0
- data/README.rdoc +82 -0
- data/Rakefile +2 -0
- data/app/models/mattr.rb +41 -0
- data/lib/generators/metafy_generator.rb +19 -0
- data/lib/generators/templates/create_mattrs.rb +20 -0
- data/lib/metafy.rb +4 -0
- data/lib/metafy/activerecord_methods.rb +44 -0
- data/lib/metafy/base.rb +102 -0
- data/lib/metafy/engine.rb +9 -0
- data/metafy.gemspec +23 -0
- data/test/test_helper.rb +13 -0
- data/test/unit/mattr_test.rb +7 -0
- metadata +80 -0
data/.gitignore
ADDED
data/Gemfile
ADDED
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2004-2011 Caseproof, 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.
|
21
|
+
|
data/README.rdoc
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
= Metafy
|
2
|
+
|
3
|
+
Metafy is a Gem for Rails 3 that makes it possible to easily add dynamic attributes to any ActiveRecord model. These lightweight, meta attributes work just like normal database column attributes on your ActiveRecord model and are fully searchable (i.e. you can find records by values contained in these dynamic attributes).
|
4
|
+
|
5
|
+
= Installation
|
6
|
+
|
7
|
+
To apply Metafy to any ActiveRecord model, follow these simple steps:
|
8
|
+
|
9
|
+
1. Install
|
10
|
+
- Add to Gemfile: <b>gem 'metafy'</b>
|
11
|
+
- Install required gems: <b>bundle install</b>
|
12
|
+
|
13
|
+
2. Add the 'mattrs' table to your database
|
14
|
+
- Create migration: <b>rails g metafy</b>
|
15
|
+
- Migrate your database: <b>rake db:migrate</b>
|
16
|
+
|
17
|
+
3. Add metafied attributes to any of your ActiveRecord models using the <b>metafy</b> class method:
|
18
|
+
- Add to app/models/model.rb: <b>metafy :attribute_name</b>
|
19
|
+
|
20
|
+
= Example
|
21
|
+
|
22
|
+
Say you had a table named "users":
|
23
|
+
|
24
|
+
mysql> desc users;
|
25
|
+
+----------------+--------------+------+-----+---------+----------------+
|
26
|
+
| Field | Type | Null | Key | Default | Extra |
|
27
|
+
+----------------+--------------+------+-----+---------+----------------+
|
28
|
+
| id | int(11) | NO | PRI | NULL | auto_increment |
|
29
|
+
| username | varchar(255) | YES | MUL | NULL | |
|
30
|
+
| email | varchar(255) | YES | MUL | NULL | |
|
31
|
+
| created_at | datetime | YES | | NULL | |
|
32
|
+
| updated_at | datetime | YES | | NULL | |
|
33
|
+
+----------------+--------------+------+-----+---------+----------------+
|
34
|
+
|
35
|
+
And you wanted some lightweight, dynamic fields associated with the User model but didn't necessarily want them in the schema then you could add them as meta attributes using metafy like so:
|
36
|
+
|
37
|
+
# app/models/user.rb
|
38
|
+
class User < ActiveRecord::Base
|
39
|
+
metafy :first_name, :last_name,:gender, :country, :language
|
40
|
+
end
|
41
|
+
|
42
|
+
Now you can do things like:
|
43
|
+
>> user = User.meta_where( :first_name => 'Bevis' ).first
|
44
|
+
>> user.first_name = 'Larry'
|
45
|
+
>> user.save
|
46
|
+
>> user.first_name
|
47
|
+
=> 'Larry'
|
48
|
+
|
49
|
+
OR create a new record with meta attributes:
|
50
|
+
>> user = User.new :first_name => 'Jerry', :last_name => 'Johnson'
|
51
|
+
>> user.save
|
52
|
+
|
53
|
+
OR select using the meta_where scope:
|
54
|
+
>> num_johnsons = User.meta_where( :last_name => 'Johnson' ).count
|
55
|
+
|
56
|
+
OR select & order using standard rails relations:
|
57
|
+
>> num_johnsons = User.where( :m_country => { :meta_value => 'Canada' } ).order( 'm_last_name.meta_value ASC' )
|
58
|
+
|
59
|
+
OR chain where with meta_where:
|
60
|
+
>> users = User.where( 'users.email LIKE "%@gmail.com"' ).meta_where( :language => "French" )
|
61
|
+
|
62
|
+
= Why would I want to use this Gem instead of just adding more database columns?
|
63
|
+
Well, there isn't much difference functionally between a normal database colunn and a meta attribute. But there may be sometimes when you want to add a more dynamic, lightweight attribute that doesn't clutter your schema. That's when you'd use metafy.
|
64
|
+
|
65
|
+
= Version history
|
66
|
+
|
67
|
+
- Version 0.0.5
|
68
|
+
- Added more documentation
|
69
|
+
- Version 0.0.4
|
70
|
+
- Fixed the metas table name issue in 3.0.7
|
71
|
+
- Version 0.0.3
|
72
|
+
- Added documentation
|
73
|
+
- Version 0.0.2
|
74
|
+
- Gem Fixes
|
75
|
+
- Version 0.0.1
|
76
|
+
- Initial Version
|
77
|
+
|
78
|
+
= Contact and copyright
|
79
|
+
|
80
|
+
Bug report? Faulty/incomplete documentation? Feature request? Please post an issue on 'http://github.com/Caseproof/metafy/issues'. If its urgent, please contact me from my website at 'http://blairwilliams.com/contact'
|
81
|
+
|
82
|
+
Copyright (c) 2004-2011 Caseproof, LLC, released under the MIT license
|
data/Rakefile
ADDED
data/app/models/mattr.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
class Mattr < ActiveRecord::Base
|
2
|
+
def self.meta(target_type, target_id, meta_key)
|
3
|
+
Mattr.where({ :target_type => target_type, :meta_key => meta_key, :target_id => target_id }).first
|
4
|
+
end
|
5
|
+
|
6
|
+
def self.meta=(args)
|
7
|
+
return false if args.nil? or !args.is_a?(Array) or args.length < 4
|
8
|
+
Mattr.metas = [ args[0], args[1], { args[2] => args[3] } ]
|
9
|
+
end
|
10
|
+
|
11
|
+
def self.metas=(args)
|
12
|
+
return if args.nil?
|
13
|
+
return unless args.is_a?(Array)
|
14
|
+
return unless args[2].is_a?(Hash)
|
15
|
+
|
16
|
+
args[2].each do |k,v|
|
17
|
+
unless meta = self.meta(args[0],args[1],k)
|
18
|
+
meta = Mattr.new
|
19
|
+
end
|
20
|
+
|
21
|
+
meta.target_type = args[0]
|
22
|
+
meta.target_id = args[1]
|
23
|
+
meta.meta_key = k
|
24
|
+
meta.meta_value = v
|
25
|
+
meta.save
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.value(target_type, target_id, meta_key)
|
30
|
+
record = self.meta(target_type, target_id, meta_key)
|
31
|
+
record.meta_value unless record.nil?
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.exists?(target_type, target_id, meta_key)
|
35
|
+
self.meta(target_type, target_id, meta_key) != false
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.metas(target_type, target_id)
|
39
|
+
Mattr.where({ :target_type => target_type, :target_id => target_id })
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
class MetafyGenerator < Rails::Generators::Base
|
4
|
+
include Rails::Generators::Migration
|
5
|
+
|
6
|
+
source_root File.expand_path("../templates", __FILE__)
|
7
|
+
|
8
|
+
def self.next_migration_number(dirname)
|
9
|
+
if ActiveRecord::Base.timestamped_migrations
|
10
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
11
|
+
else
|
12
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def create_migration_file
|
17
|
+
migration_template 'create_mattrs.rb', 'db/migrate/create_mattrs.rb'
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
class CreateMattrs < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table(:mattrs) do |t|
|
4
|
+
t.string :meta_key, :null => false
|
5
|
+
t.text :meta_value
|
6
|
+
t.references :target, :polymorphic => true, :null => false
|
7
|
+
|
8
|
+
t.timestamps
|
9
|
+
end
|
10
|
+
|
11
|
+
add_index :mattrs, :meta_key
|
12
|
+
add_index :mattrs, :meta_value, :length => 255
|
13
|
+
add_index :mattrs, :target_id
|
14
|
+
add_index :mattrs, :target_type
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.down
|
18
|
+
drop_table :mattrs
|
19
|
+
end
|
20
|
+
end
|
data/lib/metafy.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
require 'metafy/base'
|
2
|
+
|
3
|
+
# Adds the metafy method in ActiveRecord::Base, which can be used to define the metafied objects.
|
4
|
+
class << ActiveRecord::Base
|
5
|
+
def metafied?
|
6
|
+
@metafied || false
|
7
|
+
end
|
8
|
+
|
9
|
+
def metafy( *options )
|
10
|
+
cattr_accessor :metafied_attrs
|
11
|
+
self.metafied_attrs = options
|
12
|
+
|
13
|
+
# Sets to determine what classes have been metafied
|
14
|
+
@metafied = self.metafied_attrs.count > 0
|
15
|
+
|
16
|
+
meta_joins = []
|
17
|
+
class_name = self.name.classify
|
18
|
+
table_name = self.table_name
|
19
|
+
meta_selects = ["#{table_name}.*"]
|
20
|
+
self.metafied_attrs.each do |col|
|
21
|
+
meta_joins.push("LEFT JOIN mattrs m_#{col.to_s} ON m_#{col.to_s}.target_id=#{table_name}.id AND m_#{col.to_s}.meta_key='#{col.to_s}' AND m_#{col.to_s}.target_type='#{class_name}'")
|
22
|
+
meta_selects.push("m_#{col.to_s}.meta_value AS #{col.to_s}")
|
23
|
+
end
|
24
|
+
|
25
|
+
default_scope :select => meta_selects, :joins => meta_joins
|
26
|
+
|
27
|
+
scope :meta_where, lambda { |options|
|
28
|
+
conditions = {}
|
29
|
+
options.each_pair do |key,val|
|
30
|
+
conditions["m_#{key.to_s}".to_sym] = { :meta_value => val }
|
31
|
+
end
|
32
|
+
|
33
|
+
where(conditions)
|
34
|
+
}
|
35
|
+
|
36
|
+
has_many :mattr, :as => :target
|
37
|
+
|
38
|
+
options.each do | meta_attr |
|
39
|
+
attr_accessor meta_attr.to_sym
|
40
|
+
end
|
41
|
+
|
42
|
+
include Metafy::Base
|
43
|
+
end
|
44
|
+
end
|
data/lib/metafy/base.rb
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
module Metafy
|
2
|
+
module Base
|
3
|
+
def initialize(attributes = nil)
|
4
|
+
unless attributes.nil?
|
5
|
+
super(attributes.except(*self.metafied_attrs))
|
6
|
+
define_accessors_for_attributes
|
7
|
+
attributes.each do |n,v|
|
8
|
+
set_metafied_attribute(n,v) if has_metafied_attribute?(n)
|
9
|
+
end
|
10
|
+
else
|
11
|
+
super(nil)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
def has_metafied_attribute?(attribute)
|
16
|
+
return self.metafied_attrs.include?(attribute.to_sym)
|
17
|
+
end
|
18
|
+
|
19
|
+
def read_metafied_attribute(attribute)
|
20
|
+
has_metafied_attribute?(attribute.to_sym) ? Mattr.value(self.class.name, self.id, attribute.to_s) : nil
|
21
|
+
end
|
22
|
+
|
23
|
+
def write_metafied_attributes
|
24
|
+
self.metafied_attrs.each do | meta_attr |
|
25
|
+
write_metafied_attribute( meta_attr )
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def write_metafied_attribute( meta_attr )
|
30
|
+
meta_attr = meta_attr.to_sym
|
31
|
+
|
32
|
+
old_meta = read_metafied_attribute( meta_attr )
|
33
|
+
new_meta = get_metafied_attribute( meta_attr )
|
34
|
+
|
35
|
+
if( old_meta != new_meta )
|
36
|
+
Mattr.meta = [ self.class.name, self.id, meta_attr.to_s, get_metafied_attribute( meta_attr ) ]
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def populate_metafied_attributes
|
41
|
+
define_accessors_for_attributes
|
42
|
+
|
43
|
+
attributes = Mattr.metas(self.class.name, self.id)
|
44
|
+
attributes.each do | meta_attr |
|
45
|
+
set_metafied_attribute( meta_attr.meta_key.to_sym, meta_attr.meta_value.to_s )
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def singleton_class
|
50
|
+
class << self
|
51
|
+
self
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def define_accessor_for_attribute(att)
|
56
|
+
singleton_class.send(:attr_accessor, att.to_sym)
|
57
|
+
end
|
58
|
+
|
59
|
+
def define_accessors_for_attributes
|
60
|
+
self.metafied_attrs.each do | meta_attr |
|
61
|
+
unless respond_to?(meta_attr.to_s)
|
62
|
+
define_accessor_for_attribute(meta_attr)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def self.included object
|
68
|
+
super
|
69
|
+
object.after_initialize :populate_metafied_attributes
|
70
|
+
object.after_find :populate_metafied_attributes
|
71
|
+
object.after_save :write_metafied_attributes
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_metafied_attribute(att, value = nil)
|
75
|
+
unless respond_to?(att.to_s + '=')
|
76
|
+
define_accessor_for_attribute(att)
|
77
|
+
end
|
78
|
+
|
79
|
+
send(att.to_s + '=', value)
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_metafied_attribute(att)
|
83
|
+
unless respond_to?(att.to_s)
|
84
|
+
define_accessor_for_attribute(att)
|
85
|
+
end
|
86
|
+
|
87
|
+
send(att.to_s)
|
88
|
+
end
|
89
|
+
|
90
|
+
# overrides update_attributes to take meta attributes into account
|
91
|
+
def update_attributes(attributes)
|
92
|
+
attributes.each do |n,v|
|
93
|
+
if has_metafied_attribute?(n.to_sym)
|
94
|
+
set_metafied_attribute( n.to_sym, v )
|
95
|
+
write_metafied_attribute( n.to_sym )
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
attributes.nil? ? super(nil) : super(attributes.except(*self.metafied_attrs))
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
data/metafy.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |s|
|
5
|
+
s.name = "metafy"
|
6
|
+
s.version = "1.0.1"
|
7
|
+
s.platform = Gem::Platform::RUBY
|
8
|
+
s.authors = ["supercleanse","btoone"]
|
9
|
+
s.email = ["support@caseproof.com"]
|
10
|
+
s.homepage = "http://blairwilliams.com/rails/metafy"
|
11
|
+
s.summary = %q{Dynamic Attributes Engine for Rails 3}
|
12
|
+
s.description = %q{Metafy is a Gem for Rails 3 that makes it possible to easily add dynamic attributes to any ActiveRecord model. These lightweight, meta attributes work just like normal database column attributes on your ActiveRecord model and are fully searchable (i.e. you can find records by values contained in these dynamic attributes).}
|
13
|
+
|
14
|
+
s.add_dependency('rails','>= 3.0.3')
|
15
|
+
s.license = 'MIT'
|
16
|
+
|
17
|
+
s.rubyforge_project = "metafy"
|
18
|
+
|
19
|
+
s.files = `git ls-files`.split("\n")
|
20
|
+
#s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
21
|
+
#s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
22
|
+
#s.require_paths = ["app","config","lib"]
|
23
|
+
end
|
data/test/test_helper.rb
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
ENV["RAILS_ENV"] = "test"
|
2
|
+
require File.expand_path('../../config/environment', __FILE__)
|
3
|
+
require 'rails/test_help'
|
4
|
+
|
5
|
+
class ActiveSupport::TestCase
|
6
|
+
# Setup all fixtures in test/fixtures/*.(yml|csv) for all tests in alphabetical order.
|
7
|
+
#
|
8
|
+
# Note: You'll currently still have to declare fixtures explicitly in integration tests
|
9
|
+
# -- they do not yet inherit this setting
|
10
|
+
fixtures :all
|
11
|
+
|
12
|
+
# Add more helper methods to be used by all tests here...
|
13
|
+
end
|
metadata
ADDED
@@ -0,0 +1,80 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: metafy
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 1.0.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- supercleanse
|
9
|
+
- btoone
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
|
14
|
+
date: 2011-05-10 00:00:00 Z
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rails
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 3.0.3
|
25
|
+
type: :runtime
|
26
|
+
version_requirements: *id001
|
27
|
+
description: Metafy is a Gem for Rails 3 that makes it possible to easily add dynamic attributes to any ActiveRecord model. These lightweight, meta attributes work just like normal database column attributes on your ActiveRecord model and are fully searchable (i.e. you can find records by values contained in these dynamic attributes).
|
28
|
+
email:
|
29
|
+
- support@caseproof.com
|
30
|
+
executables: []
|
31
|
+
|
32
|
+
extensions: []
|
33
|
+
|
34
|
+
extra_rdoc_files: []
|
35
|
+
|
36
|
+
files:
|
37
|
+
- .gitignore
|
38
|
+
- Gemfile
|
39
|
+
- MIT-LICENSE
|
40
|
+
- README.rdoc
|
41
|
+
- Rakefile
|
42
|
+
- app/models/mattr.rb
|
43
|
+
- lib/generators/metafy_generator.rb
|
44
|
+
- lib/generators/templates/create_mattrs.rb
|
45
|
+
- lib/metafy.rb
|
46
|
+
- lib/metafy/activerecord_methods.rb
|
47
|
+
- lib/metafy/base.rb
|
48
|
+
- lib/metafy/engine.rb
|
49
|
+
- metafy.gemspec
|
50
|
+
- test/test_helper.rb
|
51
|
+
- test/unit/mattr_test.rb
|
52
|
+
homepage: http://blairwilliams.com/rails/metafy
|
53
|
+
licenses:
|
54
|
+
- MIT
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options: []
|
57
|
+
|
58
|
+
require_paths:
|
59
|
+
- lib
|
60
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
61
|
+
none: false
|
62
|
+
requirements:
|
63
|
+
- - ">="
|
64
|
+
- !ruby/object:Gem::Version
|
65
|
+
version: "0"
|
66
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
67
|
+
none: false
|
68
|
+
requirements:
|
69
|
+
- - ">="
|
70
|
+
- !ruby/object:Gem::Version
|
71
|
+
version: "0"
|
72
|
+
requirements: []
|
73
|
+
|
74
|
+
rubyforge_project: metafy
|
75
|
+
rubygems_version: 1.7.2
|
76
|
+
signing_key:
|
77
|
+
specification_version: 3
|
78
|
+
summary: Dynamic Attributes Engine for Rails 3
|
79
|
+
test_files: []
|
80
|
+
|