acts_as_metadata 0.1.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/.gitignore +1 -0
- data/Gemfile +4 -0
- data/Gemfile.lock +105 -0
- data/Rakefile +2 -0
- data/Readme +11 -0
- data/acts_as_metadata.gemspec +24 -0
- data/lib/acts_as_metadata.rb +20 -0
- data/lib/generators/metadata/migration/migration_generator.rb +24 -0
- data/lib/generators/metadata/migration/templates/migration.rb +42 -0
- data/lib/metadata/acts_as_metadata.rb +132 -0
- data/lib/metadata/acts_as_metadata_helper.rb +37 -0
- data/lib/metadata/metadata.rb +25 -0
- data/lib/metadata/metadata_type.rb +148 -0
- data/lib/metadata/version.rb +3 -0
- data/spec/acts_as_metadata_spec.rb +78 -0
- data/spec/spec_helper.rb +52 -0
- metadata +110 -0
data/.gitignore
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
db/*
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
acts_as_metadata (0.0.2)
|
5
|
+
rails (>= 3.0.0)
|
6
|
+
|
7
|
+
GEM
|
8
|
+
remote: http://rubygems.org/
|
9
|
+
specs:
|
10
|
+
actionmailer (3.1.3)
|
11
|
+
actionpack (= 3.1.3)
|
12
|
+
mail (~> 2.3.0)
|
13
|
+
actionpack (3.1.3)
|
14
|
+
activemodel (= 3.1.3)
|
15
|
+
activesupport (= 3.1.3)
|
16
|
+
builder (~> 3.0.0)
|
17
|
+
erubis (~> 2.7.0)
|
18
|
+
i18n (~> 0.6)
|
19
|
+
rack (~> 1.3.5)
|
20
|
+
rack-cache (~> 1.1)
|
21
|
+
rack-mount (~> 0.8.2)
|
22
|
+
rack-test (~> 0.6.1)
|
23
|
+
sprockets (~> 2.0.3)
|
24
|
+
activemodel (3.1.3)
|
25
|
+
activesupport (= 3.1.3)
|
26
|
+
builder (~> 3.0.0)
|
27
|
+
i18n (~> 0.6)
|
28
|
+
activerecord (3.1.3)
|
29
|
+
activemodel (= 3.1.3)
|
30
|
+
activesupport (= 3.1.3)
|
31
|
+
arel (~> 2.2.1)
|
32
|
+
tzinfo (~> 0.3.29)
|
33
|
+
activeresource (3.1.3)
|
34
|
+
activemodel (= 3.1.3)
|
35
|
+
activesupport (= 3.1.3)
|
36
|
+
activesupport (3.1.3)
|
37
|
+
multi_json (~> 1.0)
|
38
|
+
arel (2.2.1)
|
39
|
+
builder (3.0.0)
|
40
|
+
diff-lcs (1.1.3)
|
41
|
+
erubis (2.7.0)
|
42
|
+
hike (1.2.1)
|
43
|
+
i18n (0.6.0)
|
44
|
+
json (1.6.1)
|
45
|
+
mail (2.3.0)
|
46
|
+
i18n (>= 0.4.0)
|
47
|
+
mime-types (~> 1.16)
|
48
|
+
treetop (~> 1.4.8)
|
49
|
+
mime-types (1.17.2)
|
50
|
+
multi_json (1.0.3)
|
51
|
+
polyglot (0.3.3)
|
52
|
+
rack (1.3.5)
|
53
|
+
rack-cache (1.1)
|
54
|
+
rack (>= 0.4)
|
55
|
+
rack-mount (0.8.3)
|
56
|
+
rack (>= 1.0.0)
|
57
|
+
rack-ssl (1.3.2)
|
58
|
+
rack
|
59
|
+
rack-test (0.6.1)
|
60
|
+
rack (>= 1.0)
|
61
|
+
rails (3.1.3)
|
62
|
+
actionmailer (= 3.1.3)
|
63
|
+
actionpack (= 3.1.3)
|
64
|
+
activerecord (= 3.1.3)
|
65
|
+
activeresource (= 3.1.3)
|
66
|
+
activesupport (= 3.1.3)
|
67
|
+
bundler (~> 1.0)
|
68
|
+
railties (= 3.1.3)
|
69
|
+
railties (3.1.3)
|
70
|
+
actionpack (= 3.1.3)
|
71
|
+
activesupport (= 3.1.3)
|
72
|
+
rack-ssl (~> 1.3.2)
|
73
|
+
rake (>= 0.8.7)
|
74
|
+
rdoc (~> 3.4)
|
75
|
+
thor (~> 0.14.6)
|
76
|
+
rake (0.9.2.2)
|
77
|
+
rdoc (3.11)
|
78
|
+
json (~> 1.4)
|
79
|
+
rspec (2.7.0)
|
80
|
+
rspec-core (~> 2.7.0)
|
81
|
+
rspec-expectations (~> 2.7.0)
|
82
|
+
rspec-mocks (~> 2.7.0)
|
83
|
+
rspec-core (2.7.1)
|
84
|
+
rspec-expectations (2.7.0)
|
85
|
+
diff-lcs (~> 1.1.2)
|
86
|
+
rspec-mocks (2.7.0)
|
87
|
+
sprockets (2.0.3)
|
88
|
+
hike (~> 1.2)
|
89
|
+
rack (~> 1.0)
|
90
|
+
tilt (~> 1.1, != 1.3.0)
|
91
|
+
sqlite3 (1.3.4)
|
92
|
+
thor (0.14.6)
|
93
|
+
tilt (1.3.3)
|
94
|
+
treetop (1.4.10)
|
95
|
+
polyglot
|
96
|
+
polyglot (>= 0.3.1)
|
97
|
+
tzinfo (0.3.31)
|
98
|
+
|
99
|
+
PLATFORMS
|
100
|
+
ruby
|
101
|
+
|
102
|
+
DEPENDENCIES
|
103
|
+
acts_as_metadata!
|
104
|
+
rspec (>= 2.0.0)
|
105
|
+
sqlite3
|
data/Rakefile
ADDED
data/Readme
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
$:.push File.expand_path("../lib", __FILE__)
|
3
|
+
require "metadata/version"
|
4
|
+
|
5
|
+
Gem::Specification.new do |s|
|
6
|
+
s.name = "acts_as_metadata"
|
7
|
+
s.version = ActsAsMetadata::VERSION
|
8
|
+
s.platform = Gem::Platform::RUBY
|
9
|
+
s.authors = ["Gropher"]
|
10
|
+
s.email = ["grophen@gmail.com"]
|
11
|
+
s.homepage = ""
|
12
|
+
s.summary = %q{Additional on-demand fields for ActiveRecord models}
|
13
|
+
s.description = %q{This gem allows to add additional database-stored fields to your models}
|
14
|
+
|
15
|
+
s.rubyforge_project = "acts_as_metadata"
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n")
|
18
|
+
s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
19
|
+
s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
20
|
+
s.require_paths = ["lib"]
|
21
|
+
s.add_dependency('rails', '>= 3.0.0' )
|
22
|
+
s.add_development_dependency("rspec", ">= 2.0.0")
|
23
|
+
s.add_development_dependency("sqlite3")
|
24
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require "action_view"
|
3
|
+
|
4
|
+
require 'metadata/metadata'
|
5
|
+
require 'metadata/metadata_type'
|
6
|
+
require 'metadata/acts_as_metadata'
|
7
|
+
require 'metadata/acts_as_metadata_helper'
|
8
|
+
require 'metadata/version'
|
9
|
+
|
10
|
+
if defined?(ActiveRecord::Base)
|
11
|
+
ActiveRecord::Base.extend ActsAsMetadata
|
12
|
+
end
|
13
|
+
|
14
|
+
module ApplicationHelper
|
15
|
+
include ActsAsMetadataHelper
|
16
|
+
end
|
17
|
+
|
18
|
+
module ActsAsMetadata
|
19
|
+
# Your code goes here...
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
require 'rails/generators/migration'
|
3
|
+
|
4
|
+
module Metadata
|
5
|
+
class MigrationGenerator < Rails::Generators::Base
|
6
|
+
include Rails::Generators::Migration
|
7
|
+
|
8
|
+
def self.source_root
|
9
|
+
@source_root ||= File.join(File.dirname(__FILE__), 'templates')
|
10
|
+
end
|
11
|
+
|
12
|
+
|
13
|
+
def self.next_migration_number(dirname)
|
14
|
+
#if ActiveRecord::Base.timestamped_migrations
|
15
|
+
Time.now.utc.strftime("%Y%m%d%H%M%S")
|
16
|
+
"%.3d" % (current_migration_number(dirname) + 1)
|
17
|
+
#end
|
18
|
+
end
|
19
|
+
|
20
|
+
def create_migration_file
|
21
|
+
migration_template 'migration.rb', 'db/migrate/create_metadata.rb'
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,42 @@
|
|
1
|
+
class CreateMetadata < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :metadata do |t|
|
4
|
+
t.column :id, :integer
|
5
|
+
t.column :metadata_type, :string
|
6
|
+
t.column :model_id, :integer
|
7
|
+
t.column :model_type, :string
|
8
|
+
t.column :value, :text
|
9
|
+
t.column :search_value, :string
|
10
|
+
|
11
|
+
t.timestamp :deleted_at, :default => nil
|
12
|
+
t.timestamps
|
13
|
+
end
|
14
|
+
|
15
|
+
create_table :metadata_types do |t|
|
16
|
+
t.column :id, :integer
|
17
|
+
t.column :name, :string
|
18
|
+
t.column :description, :text
|
19
|
+
t.column :tag, :string
|
20
|
+
t.column :datatype, :string, :default => "string"
|
21
|
+
t.column :mandatory, :boolean, :default => false
|
22
|
+
t.column :format, :string
|
23
|
+
t.column :values, :text
|
24
|
+
t.column :models, :string, :default => '--- []'
|
25
|
+
t.column :default, :text
|
26
|
+
|
27
|
+
t.timestamp :deleted_at, :default => nil
|
28
|
+
t.timestamps
|
29
|
+
end
|
30
|
+
|
31
|
+
add_index :metadata_types, :datatype
|
32
|
+
add_index :metadata_types, :tag, :unique => true
|
33
|
+
add_index :metadata, :metadata_type
|
34
|
+
add_index :metadata, [:metadata_type, :search_value]
|
35
|
+
add_index :metadata, [:model_id, :model_type]
|
36
|
+
end
|
37
|
+
|
38
|
+
def self.down
|
39
|
+
drop_table :metadata
|
40
|
+
drop_table :metadata_types
|
41
|
+
end
|
42
|
+
end
|
@@ -0,0 +1,132 @@
|
|
1
|
+
module ActsAsMetadata
|
2
|
+
def acts_as_metadata(options={})
|
3
|
+
if options[:scope].is_a?(Symbol) && options[:scope].to_s !~ /_id$/
|
4
|
+
scope = "#{options[:scope]}_id".to_sym
|
5
|
+
else
|
6
|
+
scope = nil
|
7
|
+
end
|
8
|
+
|
9
|
+
MetadataType.class_variable_set("@@metadata_scope", scope)
|
10
|
+
class_variable_set("@@metadata_scope", scope)
|
11
|
+
class_eval do
|
12
|
+
serialize :metadata_cache
|
13
|
+
has_many :metadata, :as => :model, :dependent => :destroy, :class_name => "Metadata::Metadata"
|
14
|
+
before_create :create_accessors_and_save_metadata
|
15
|
+
before_update :create_accessors_and_save_metadata
|
16
|
+
validate :metadata_constraints
|
17
|
+
|
18
|
+
def metadata_constraints
|
19
|
+
metadata_types.each do |type_name|
|
20
|
+
type = MetadataType.type(type_name, metadata_scope)
|
21
|
+
value = get_metadata(type.tag)
|
22
|
+
values = type.values.map {|v| v.is_a?(Array) ? v[1] : v } rescue []
|
23
|
+
errors.add(type.tag, I18n.t('acts_as_metadata.errors.blank')) if type.mandatory && value.blank?
|
24
|
+
errors.add(type.tag, I18n.t('acts_as_metadata.errors.format')) if !type.format.blank? && !value.blank? && value.to_s !~ Regexp.new("^#{type.format}$")
|
25
|
+
errors.add(type.tag, I18n.t('acts_as_metadata.errors.values')) if !values.blank? && !value.blank? && !values.include?(value)
|
26
|
+
end unless @skip_metadata_validation
|
27
|
+
end
|
28
|
+
|
29
|
+
def skip_metadata_validation!
|
30
|
+
@skip_metadata_validation = true
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def mass_assignment_authorizer(role = :default)
|
35
|
+
super + metadata_types
|
36
|
+
end
|
37
|
+
|
38
|
+
def initialize(args=nil, options = {})
|
39
|
+
scope = self.class.class_variable_get('@@metadata_scope') ? args[self.class.class_variable_get('@@metadata_scope')] : nil rescue nil
|
40
|
+
types = MetadataType.model_types(model_name, scope)
|
41
|
+
types.each do |type|
|
42
|
+
create_accessor type
|
43
|
+
end
|
44
|
+
super
|
45
|
+
end
|
46
|
+
|
47
|
+
def update_attributes(attributes)
|
48
|
+
create_accessors
|
49
|
+
super
|
50
|
+
end
|
51
|
+
|
52
|
+
def create_accessors
|
53
|
+
metadata_types.each do |type|
|
54
|
+
create_accessor type
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
def create_accessor type
|
59
|
+
class_eval "attr_accessor :#{type}"
|
60
|
+
class_eval "def #{type}; get_metadata('#{type}'); end"
|
61
|
+
class_eval "def #{type}=(value); set_metadata('#{type}', value); end"
|
62
|
+
end
|
63
|
+
|
64
|
+
def create_accessors_and_save_metadata
|
65
|
+
create_accessors
|
66
|
+
save_metadata
|
67
|
+
end
|
68
|
+
|
69
|
+
def method_missing(meth, *args, &block)
|
70
|
+
begin
|
71
|
+
super
|
72
|
+
rescue NoMethodError
|
73
|
+
name = meth.to_s
|
74
|
+
if name =~ /^(.+)=$/
|
75
|
+
name = name[0..-2]
|
76
|
+
if metadata_types.include?(name)
|
77
|
+
set_metadata(name, args.first)
|
78
|
+
else
|
79
|
+
raise
|
80
|
+
end
|
81
|
+
else
|
82
|
+
if metadata_types.include?(name)
|
83
|
+
get_metadata(name)
|
84
|
+
else
|
85
|
+
raise
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
def metadata_scope
|
92
|
+
self.class.class_variable_get('@@metadata_scope') ? self.send(self.class.class_variable_get('@@metadata_scope')) : nil
|
93
|
+
end
|
94
|
+
|
95
|
+
def model_name
|
96
|
+
self.class.name.underscore.to_sym
|
97
|
+
end
|
98
|
+
|
99
|
+
def metadata_types
|
100
|
+
MetadataType.model_types(model_name, metadata_scope)
|
101
|
+
end
|
102
|
+
|
103
|
+
def self.metadata_types(scope=nil)
|
104
|
+
MetadataType.model_types(self.name.underscore.to_sym, scope)
|
105
|
+
end
|
106
|
+
|
107
|
+
def get_metadata(name)
|
108
|
+
load_metadata unless metadata_cache.is_a?(Hash)
|
109
|
+
metadata_cache[name]
|
110
|
+
end
|
111
|
+
|
112
|
+
def set_metadata(name, value)
|
113
|
+
type = MetadataType.type(name, metadata_scope)
|
114
|
+
raise NoMethodError if type.nil?
|
115
|
+
load_metadata unless metadata_cache.is_a?(Hash)
|
116
|
+
self.metadata_cache[name] = type.type_cast(value) || type.type_cast(type.default)
|
117
|
+
end
|
118
|
+
|
119
|
+
def save_metadata
|
120
|
+
Metadata::Metadata.delete_all(:model_type => self.class.name, :model_id => self.id) unless self.id.blank?
|
121
|
+
self.metadata_types.each do |type_name|
|
122
|
+
value = self.get_metadata(type_name)
|
123
|
+
self.metadata.build(:metadata_type => type_name, :value => value) unless value.nil?
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def load_metadata
|
128
|
+
self.metadata_cache = Hash[self.metadata.all.map { |m| [m.metadata_type, m.value] }]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module ActsAsMetadataHelper
|
2
|
+
def metadata_type_datatype_names
|
3
|
+
return MetadataType::DATATYPES.map { |key, value| [t("activerecord.attributes.metadata_type.datatypes.#{value}"), key] }
|
4
|
+
end
|
5
|
+
|
6
|
+
def metadata_type_form_fields(f)
|
7
|
+
res = ""
|
8
|
+
f.object.metadata_types.each do |tag|
|
9
|
+
type = MetadataType.type(tag)
|
10
|
+
res += f.label(type.name)
|
11
|
+
case type.datatype
|
12
|
+
when "boolean"
|
13
|
+
res += f.check_box(type.tag)
|
14
|
+
when "text"
|
15
|
+
res += f.text_area(type.tag)
|
16
|
+
when "datetime"
|
17
|
+
res += f.text_field(type.tag)
|
18
|
+
when "date"
|
19
|
+
res += f.text_field(type.tag)
|
20
|
+
when "number"
|
21
|
+
res += f.text_field(type.tag)
|
22
|
+
when "string"
|
23
|
+
res += f.text_field(type.tag)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
res.html_safe
|
27
|
+
end
|
28
|
+
|
29
|
+
def metadata_type_datatype_name(type)
|
30
|
+
return t("activerecord.attributes.metadata_type.datatypes.#{MetadataType::DATATYPES[type.datatype.to_sym]}")
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
def metadata_type_mandatory_name(type)
|
35
|
+
return t("activerecord.attributes.metadata_type.mandatory_values.#{type.mandatory}")
|
36
|
+
end
|
37
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module Metadata
|
2
|
+
class Metadata < ActiveRecord::Base
|
3
|
+
default_scope :conditions => {:deleted_at => nil}, :order => 'created_at DESC'
|
4
|
+
before_save :set_search_value
|
5
|
+
|
6
|
+
def undelete
|
7
|
+
self.deleted_at=nil
|
8
|
+
self.save
|
9
|
+
end
|
10
|
+
|
11
|
+
def destroy(real = false)
|
12
|
+
if real
|
13
|
+
Metadata.unscoped.delete_all(:id => self.id)
|
14
|
+
else
|
15
|
+
self.run_callbacks(:destroy)
|
16
|
+
self.update_attribute(:deleted_at, Time.now.utc)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
protected
|
21
|
+
def set_search_value
|
22
|
+
self.search_value = value.to_s[0,255] rescue nil
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,148 @@
|
|
1
|
+
class MetadataType < ActiveRecord::Base
|
2
|
+
DATATYPES = {
|
3
|
+
:string => "string",
|
4
|
+
:text => "text",
|
5
|
+
:date => "date",
|
6
|
+
:datetime => "datetime",
|
7
|
+
:number => "number",
|
8
|
+
:boolean => "boolean",
|
9
|
+
:array => "array"
|
10
|
+
}
|
11
|
+
|
12
|
+
serialize :default
|
13
|
+
serialize :values
|
14
|
+
serialize :models
|
15
|
+
after_create :set_default_values
|
16
|
+
after_update :refresh_metadata
|
17
|
+
before_destroy :refresh_metadata
|
18
|
+
attr_accessor :models_json, :values_json, :default_json
|
19
|
+
attr_accessible :tag, :name, :description, :models, :mandatory,
|
20
|
+
:default, :format, :datatype, :values,
|
21
|
+
:models_json, :values_json, :default_json
|
22
|
+
validates :tag, :presence => true, :format => {:with => /^[a-z]+[a-z0-9]*$/},
|
23
|
+
:exclusion => { :in => %w(format errors callback action categorie accept attributes host key layout notify open render save template type id parent_id lft rgt test select),
|
24
|
+
:message => "this name is reserved"}
|
25
|
+
validates :datatype, :presence => true
|
26
|
+
default_scope :conditions => {:deleted_at => nil}, :order => 'created_at DESC'
|
27
|
+
|
28
|
+
def self.default
|
29
|
+
self.new({
|
30
|
+
:tag => :sample,
|
31
|
+
:name => "Sample",
|
32
|
+
:models => [:any],
|
33
|
+
:mandatory => false,
|
34
|
+
:default => 'default',
|
35
|
+
:datatype => "string",
|
36
|
+
:format => nil,
|
37
|
+
:values => nil
|
38
|
+
})
|
39
|
+
end
|
40
|
+
|
41
|
+
def type_cast(value)
|
42
|
+
return nil if value.nil?
|
43
|
+
return value unless value.is_a? String
|
44
|
+
case datatype
|
45
|
+
when 'date'
|
46
|
+
ActiveRecord::ConnectionAdapters::Column.string_to_date value
|
47
|
+
when 'datetime'
|
48
|
+
ActiveRecord::ConnectionAdapters::Column.string_to_time value
|
49
|
+
when 'number'
|
50
|
+
Integer value
|
51
|
+
when 'boolean'
|
52
|
+
ActiveRecord::ConnectionAdapters::Column.value_to_boolean value
|
53
|
+
else
|
54
|
+
value
|
55
|
+
end rescue nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def models_json
|
59
|
+
self.models ? self.models.to_json : [].to_json
|
60
|
+
end
|
61
|
+
|
62
|
+
def models_json=(value)
|
63
|
+
self.models = JSON.parse(value) rescue []
|
64
|
+
end
|
65
|
+
|
66
|
+
def values_json
|
67
|
+
self.values ? self.values.to_json : [].to_json
|
68
|
+
end
|
69
|
+
|
70
|
+
def values_json=(value)
|
71
|
+
self.values = JSON.parse(value) rescue []
|
72
|
+
end
|
73
|
+
|
74
|
+
def default_json
|
75
|
+
self.default.to_json
|
76
|
+
end
|
77
|
+
|
78
|
+
def default_json=(value)
|
79
|
+
self.default = JSON.parse(value) rescue value[/"(.*)"/, 1]
|
80
|
+
end
|
81
|
+
|
82
|
+
def self.scheme_data(scope=nil)
|
83
|
+
Rails.cache.fetch("metadata_scheme_#{@@metadata_scope}#{scope}_data", :expires_in => 60.minutes) do
|
84
|
+
uncached do
|
85
|
+
scheme_data = scope.blank? ? self.all : self.where(@@metadata_scope => scope).all
|
86
|
+
if scheme_data.count > 0
|
87
|
+
scheme_data
|
88
|
+
else
|
89
|
+
[]
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
def self.scheme(scope=nil)
|
96
|
+
Rails.cache.fetch("metadata_scheme_#{@@metadata_scope}#{scope}_types", :expires_in => 60.minutes) do
|
97
|
+
res = {}
|
98
|
+
self.scheme_data(scope).each do |type|
|
99
|
+
res[type.tag] = type
|
100
|
+
end
|
101
|
+
res
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
def self.model_types(model, scope=nil)
|
106
|
+
types = Rails.cache.fetch("metadata_scheme_#{@@metadata_scope}#{scope}_modeltypes", :expires_in => 60.minutes) do
|
107
|
+
res = { :any => [] }
|
108
|
+
self.scheme(scope).each do |tag, type|
|
109
|
+
type.models.each do |model|
|
110
|
+
res[model] = [] if res[model].blank?
|
111
|
+
res[model] << tag
|
112
|
+
end if type.models
|
113
|
+
end
|
114
|
+
res
|
115
|
+
end
|
116
|
+
types[model] ? (types[model] | types[:any]).uniq : types[:any]
|
117
|
+
end
|
118
|
+
|
119
|
+
def self.type(name, scope=nil)
|
120
|
+
self.scheme(scope)[name]
|
121
|
+
end
|
122
|
+
|
123
|
+
def self.drop_cache(scope=nil)
|
124
|
+
Rails.cache.delete("metadata_scheme_#{@@metadata_scope}#{scope}_data")
|
125
|
+
Rails.cache.delete("metadata_scheme_#{@@metadata_scope}#{scope}_types")
|
126
|
+
Rails.cache.delete("metadata_scheme_#{@@metadata_scope}#{scope}_modeltypes")
|
127
|
+
end
|
128
|
+
|
129
|
+
private
|
130
|
+
def set_default_values
|
131
|
+
self.models = [] if self.models.nil?
|
132
|
+
self.values = [] if self.values.nil?
|
133
|
+
self.save
|
134
|
+
if @@metadata_scope
|
135
|
+
MetadataType.drop_cache(self.send(@@metadata_scope))
|
136
|
+
else
|
137
|
+
MetadataType.drop_cache
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
def refresh_metadata
|
142
|
+
if @@metadata_scope
|
143
|
+
MetadataType.drop_cache(self.send(@@metadata_scope))
|
144
|
+
else
|
145
|
+
MetadataType.drop_cache
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
class MyModel < ActiveRecord::Base
|
4
|
+
acts_as_metadata
|
5
|
+
end
|
6
|
+
|
7
|
+
describe ActsAsMetadata do
|
8
|
+
it 'creates metadata_type' do
|
9
|
+
MetadataType.default.save!
|
10
|
+
MetadataType.scheme_data.count.should == 1
|
11
|
+
end
|
12
|
+
|
13
|
+
it 'has default metadata value' do
|
14
|
+
mymodel = MyModel.create
|
15
|
+
mymodel.sample.should == 'default'
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'set metadata value' do
|
19
|
+
mymodel = MyModel.new
|
20
|
+
mymodel.sample = 'test'
|
21
|
+
mymodel.save!
|
22
|
+
mymodel = MyModel.last
|
23
|
+
mymodel.sample.should == 'test'
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'updates metadata value' do
|
27
|
+
mymodel = MyModel.last
|
28
|
+
mymodel.sample = 'test2'
|
29
|
+
mymodel.save!
|
30
|
+
mymodel = MyModel.last
|
31
|
+
mymodel.sample.should == 'test2'
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'detete metadata when model deleted' do
|
35
|
+
MyModel.destroy_all
|
36
|
+
Metadata::Metadata.all.count.should == 0
|
37
|
+
end
|
38
|
+
|
39
|
+
it 'check presence validation' do
|
40
|
+
mt = MetadataType.first
|
41
|
+
mt.mandatory = true
|
42
|
+
mt.default = nil
|
43
|
+
mt.save!
|
44
|
+
mymodel = MyModel.new
|
45
|
+
mymodel.save
|
46
|
+
mymodel.errors.include?(:sample).should == true
|
47
|
+
mymodel.sample = 'wqwewew'
|
48
|
+
mymodel.save
|
49
|
+
mymodel.errors.count.should == 0
|
50
|
+
end
|
51
|
+
|
52
|
+
it 'check format validation' do
|
53
|
+
mt = MetadataType.first
|
54
|
+
mt.mandatory = false
|
55
|
+
mt.format = '[a-z]'
|
56
|
+
mt.save!
|
57
|
+
mymodel = MyModel.new
|
58
|
+
mymodel.sample = '12323'
|
59
|
+
mymodel.save
|
60
|
+
mymodel.errors.include?(:sample).should == true
|
61
|
+
mymodel.sample = 'wqwewew'
|
62
|
+
mymodel.save
|
63
|
+
mymodel.errors.count.should == 0
|
64
|
+
end
|
65
|
+
|
66
|
+
it 'check values validation' do
|
67
|
+
mt = MetadataType.first
|
68
|
+
mt.values = ['aaa', 'bbb']
|
69
|
+
mt.save!
|
70
|
+
mymodel = MyModel.new
|
71
|
+
mymodel.sample = 'ccc'
|
72
|
+
mymodel.save
|
73
|
+
mymodel.errors.include?(:sample).should == true
|
74
|
+
mymodel.sample = 'bbb'
|
75
|
+
mymodel.save
|
76
|
+
mymodel.errors.count.should == 0
|
77
|
+
end
|
78
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,52 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler/setup'
|
3
|
+
|
4
|
+
require 'rails/all'
|
5
|
+
require 'acts_as_metadata' # and any other gems you need
|
6
|
+
|
7
|
+
root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
8
|
+
ActiveRecord::Base.establish_connection(
|
9
|
+
:adapter => "sqlite3",
|
10
|
+
:database => "#{root}/db/acts_as_metadata.db"
|
11
|
+
)
|
12
|
+
ActionController::Base.cache_store = ActiveSupport::Cache::MemoryStore.new
|
13
|
+
RAILS_CACHE = ActionController::Base.cache_store
|
14
|
+
|
15
|
+
RSpec.configure do |config|
|
16
|
+
# some (optional) config here
|
17
|
+
end
|
18
|
+
|
19
|
+
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS 'my_models'")
|
20
|
+
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS 'metadata'")
|
21
|
+
ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS 'metadata_types'")
|
22
|
+
ActiveRecord::Base.connection.create_table(:my_models) do |t|
|
23
|
+
t.integer :id
|
24
|
+
t.string :name
|
25
|
+
t.text :description
|
26
|
+
t.text :metadata_cache
|
27
|
+
t.datetime "created_at"
|
28
|
+
t.datetime "updated_at"
|
29
|
+
end
|
30
|
+
ActiveRecord::Base.connection.create_table(:metadata) do |t|
|
31
|
+
t.string "metadata_type"
|
32
|
+
t.integer "model_id"
|
33
|
+
t.string "model_type"
|
34
|
+
t.text "value"
|
35
|
+
t.datetime "deleted_at"
|
36
|
+
t.datetime "created_at"
|
37
|
+
t.datetime "updated_at"
|
38
|
+
end
|
39
|
+
ActiveRecord::Base.connection.create_table(:metadata_types) do |t|
|
40
|
+
t.string "name"
|
41
|
+
t.text "description"
|
42
|
+
t.string "tag"
|
43
|
+
t.string "datatype", :default => "string"
|
44
|
+
t.boolean "mandatory", :default => false
|
45
|
+
t.string "format"
|
46
|
+
t.text "values"
|
47
|
+
t.string "models", :default => "--- []"
|
48
|
+
t.text "default"
|
49
|
+
t.datetime "deleted_at"
|
50
|
+
t.datetime "created_at"
|
51
|
+
t.datetime "updated_at"
|
52
|
+
end
|
metadata
ADDED
@@ -0,0 +1,110 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: acts_as_metadata
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Gropher
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
date: 2012-10-17 00:00:00.000000000 Z
|
13
|
+
dependencies:
|
14
|
+
- !ruby/object:Gem::Dependency
|
15
|
+
name: rails
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
17
|
+
none: false
|
18
|
+
requirements:
|
19
|
+
- - ! '>='
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 3.0.0
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
none: false
|
26
|
+
requirements:
|
27
|
+
- - ! '>='
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: 3.0.0
|
30
|
+
- !ruby/object:Gem::Dependency
|
31
|
+
name: rspec
|
32
|
+
requirement: !ruby/object:Gem::Requirement
|
33
|
+
none: false
|
34
|
+
requirements:
|
35
|
+
- - ! '>='
|
36
|
+
- !ruby/object:Gem::Version
|
37
|
+
version: 2.0.0
|
38
|
+
type: :development
|
39
|
+
prerelease: false
|
40
|
+
version_requirements: !ruby/object:Gem::Requirement
|
41
|
+
none: false
|
42
|
+
requirements:
|
43
|
+
- - ! '>='
|
44
|
+
- !ruby/object:Gem::Version
|
45
|
+
version: 2.0.0
|
46
|
+
- !ruby/object:Gem::Dependency
|
47
|
+
name: sqlite3
|
48
|
+
requirement: !ruby/object:Gem::Requirement
|
49
|
+
none: false
|
50
|
+
requirements:
|
51
|
+
- - ! '>='
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '0'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
none: false
|
58
|
+
requirements:
|
59
|
+
- - ! '>='
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
description: This gem allows to add additional database-stored fields to your models
|
63
|
+
email:
|
64
|
+
- grophen@gmail.com
|
65
|
+
executables: []
|
66
|
+
extensions: []
|
67
|
+
extra_rdoc_files: []
|
68
|
+
files:
|
69
|
+
- .gitignore
|
70
|
+
- Gemfile
|
71
|
+
- Gemfile.lock
|
72
|
+
- Rakefile
|
73
|
+
- Readme
|
74
|
+
- acts_as_metadata.gemspec
|
75
|
+
- lib/acts_as_metadata.rb
|
76
|
+
- lib/generators/metadata/migration/migration_generator.rb
|
77
|
+
- lib/generators/metadata/migration/templates/migration.rb
|
78
|
+
- lib/metadata/acts_as_metadata.rb
|
79
|
+
- lib/metadata/acts_as_metadata_helper.rb
|
80
|
+
- lib/metadata/metadata.rb
|
81
|
+
- lib/metadata/metadata_type.rb
|
82
|
+
- lib/metadata/version.rb
|
83
|
+
- spec/acts_as_metadata_spec.rb
|
84
|
+
- spec/spec_helper.rb
|
85
|
+
homepage: ''
|
86
|
+
licenses: []
|
87
|
+
post_install_message:
|
88
|
+
rdoc_options: []
|
89
|
+
require_paths:
|
90
|
+
- lib
|
91
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
92
|
+
none: false
|
93
|
+
requirements:
|
94
|
+
- - ! '>='
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ! '>='
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0'
|
103
|
+
requirements: []
|
104
|
+
rubyforge_project: acts_as_metadata
|
105
|
+
rubygems_version: 1.8.24
|
106
|
+
signing_key:
|
107
|
+
specification_version: 3
|
108
|
+
summary: Additional on-demand fields for ActiveRecord models
|
109
|
+
test_files: []
|
110
|
+
has_rdoc:
|