custom-attributes 0.0.1
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/Gemfile +10 -0
- data/Gemfile.lock +101 -0
- data/MIT-LICENSE +21 -0
- data/README.markdown +9 -0
- data/Rakefile +51 -0
- data/VERSION +1 -0
- data/generators/has_custom_attributes_migration/has_custom_attributes_migration_generator.rb +7 -0
- data/generators/has_custom_attributes_migration/templates/migration.rb +33 -0
- data/lib/active_record/custom_attributes.rb +129 -0
- data/lib/active_record/custom_attributes/custom_attribute.rb +63 -0
- data/lib/active_record/custom_attributes/custom_attribute_list.rb +123 -0
- data/lib/active_record/custom_attributes/custom_attribute_model.rb +5 -0
- data/lib/custom-attributes.rb +15 -0
- data/rails/init.rb +1 -0
- data/spec/custom_attributes/has_custom_attributes_spec.rb +117 -0
- data/spec/database.yml +17 -0
- data/spec/database.yml.sample +17 -0
- data/spec/models.rb +21 -0
- data/spec/schema.rb +43 -0
- data/spec/spec_helper.rb +59 -0
- metadata +89 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
GEM
|
2
|
+
remote: http://rubygems.org/
|
3
|
+
specs:
|
4
|
+
abstract (1.0.0)
|
5
|
+
actionmailer (3.0.0)
|
6
|
+
actionpack (= 3.0.0)
|
7
|
+
mail (~> 2.2.5)
|
8
|
+
actionpack (3.0.0)
|
9
|
+
activemodel (= 3.0.0)
|
10
|
+
activesupport (= 3.0.0)
|
11
|
+
builder (~> 2.1.2)
|
12
|
+
erubis (~> 2.6.6)
|
13
|
+
i18n (~> 0.4.1)
|
14
|
+
rack (~> 1.2.1)
|
15
|
+
rack-mount (~> 0.6.12)
|
16
|
+
rack-test (~> 0.5.4)
|
17
|
+
tzinfo (~> 0.3.23)
|
18
|
+
activemodel (3.0.0)
|
19
|
+
activesupport (= 3.0.0)
|
20
|
+
builder (~> 2.1.2)
|
21
|
+
i18n (~> 0.4.1)
|
22
|
+
activerecord (3.0.0)
|
23
|
+
activemodel (= 3.0.0)
|
24
|
+
activesupport (= 3.0.0)
|
25
|
+
arel (~> 1.0.0)
|
26
|
+
tzinfo (~> 0.3.23)
|
27
|
+
activeresource (3.0.0)
|
28
|
+
activemodel (= 3.0.0)
|
29
|
+
activesupport (= 3.0.0)
|
30
|
+
activesupport (3.0.0)
|
31
|
+
arel (1.0.1)
|
32
|
+
activesupport (~> 3.0.0)
|
33
|
+
builder (2.1.2)
|
34
|
+
diff-lcs (1.1.2)
|
35
|
+
erubis (2.6.6)
|
36
|
+
abstract (>= 1.0.0)
|
37
|
+
gemcutter (0.6.1)
|
38
|
+
git (1.2.5)
|
39
|
+
i18n (0.4.1)
|
40
|
+
jeweler (1.4.0)
|
41
|
+
gemcutter (>= 0.1.0)
|
42
|
+
git (>= 1.2.5)
|
43
|
+
rubyforge (>= 2.0.0)
|
44
|
+
json_pure (1.4.6)
|
45
|
+
mail (2.2.6.1)
|
46
|
+
activesupport (>= 2.3.6)
|
47
|
+
mime-types
|
48
|
+
treetop (>= 1.4.5)
|
49
|
+
mime-types (1.16)
|
50
|
+
mysql (2.8.1)
|
51
|
+
pg (0.9.0)
|
52
|
+
polyglot (0.3.1)
|
53
|
+
rack (1.2.1)
|
54
|
+
rack-mount (0.6.13)
|
55
|
+
rack (>= 1.0.0)
|
56
|
+
rack-test (0.5.6)
|
57
|
+
rack (>= 1.0)
|
58
|
+
rails (3.0.0)
|
59
|
+
actionmailer (= 3.0.0)
|
60
|
+
actionpack (= 3.0.0)
|
61
|
+
activerecord (= 3.0.0)
|
62
|
+
activeresource (= 3.0.0)
|
63
|
+
activesupport (= 3.0.0)
|
64
|
+
bundler (~> 1.0.0)
|
65
|
+
railties (= 3.0.0)
|
66
|
+
railties (3.0.0)
|
67
|
+
actionpack (= 3.0.0)
|
68
|
+
activesupport (= 3.0.0)
|
69
|
+
rake (>= 0.8.4)
|
70
|
+
thor (~> 0.14.0)
|
71
|
+
rake (0.8.7)
|
72
|
+
rcov (0.9.9)
|
73
|
+
rspec (2.0.0.beta.22)
|
74
|
+
rspec-core (= 2.0.0.beta.22)
|
75
|
+
rspec-expectations (= 2.0.0.beta.22)
|
76
|
+
rspec-mocks (= 2.0.0.beta.22)
|
77
|
+
rspec-core (2.0.0.beta.22)
|
78
|
+
rspec-expectations (2.0.0.beta.22)
|
79
|
+
diff-lcs (>= 1.1.2)
|
80
|
+
rspec-mocks (2.0.0.beta.22)
|
81
|
+
rspec-core (= 2.0.0.beta.22)
|
82
|
+
rspec-expectations (= 2.0.0.beta.22)
|
83
|
+
rubyforge (2.0.4)
|
84
|
+
json_pure (>= 1.1.7)
|
85
|
+
sqlite3-ruby (1.3.1)
|
86
|
+
thor (0.14.2)
|
87
|
+
treetop (1.4.8)
|
88
|
+
polyglot (>= 0.3.1)
|
89
|
+
tzinfo (0.3.23)
|
90
|
+
|
91
|
+
PLATFORMS
|
92
|
+
ruby
|
93
|
+
|
94
|
+
DEPENDENCIES
|
95
|
+
jeweler
|
96
|
+
mysql
|
97
|
+
pg
|
98
|
+
rails (= 3.0.0)
|
99
|
+
rcov
|
100
|
+
rspec (= 2.0.0.beta.22)
|
101
|
+
sqlite3-ruby
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
Copyright (c) 2010 Matthijs Groen
|
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.markdown
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
Custom Attributes
|
2
|
+
=================
|
3
|
+
|
4
|
+
Makes it easy to extend models with custom attributes. Think about adding telephone numbers to people,
|
5
|
+
or weight and size measurements to products.
|
6
|
+
|
7
|
+
Most of these fields can be used purely dynamic and objects could have multiples of them (think telephone numbers, or email
|
8
|
+
addresses)
|
9
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,51 @@
|
|
1
|
+
begin
|
2
|
+
# Rspec 1.3.0
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
|
5
|
+
desc 'Default: run specs'
|
6
|
+
task :default => :spec
|
7
|
+
Spec::Rake::SpecTask.new do |t|
|
8
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
9
|
+
end
|
10
|
+
|
11
|
+
Spec::Rake::SpecTask.new('rcov') do |t|
|
12
|
+
t.spec_files = FileList["spec/**/*_spec.rb"]
|
13
|
+
t.rcov = true
|
14
|
+
t.rcov_opts = ['--exclude', 'spec']
|
15
|
+
end
|
16
|
+
|
17
|
+
rescue LoadError
|
18
|
+
# Rspec 2.0
|
19
|
+
require 'rspec/core/rake_task'
|
20
|
+
|
21
|
+
desc 'Default: run specs'
|
22
|
+
task :default => :spec
|
23
|
+
Rspec::Core::RakeTask.new do |t|
|
24
|
+
t.pattern = "spec/**/*_spec.rb"
|
25
|
+
end
|
26
|
+
|
27
|
+
Rspec::Core::RakeTask.new('rcov') do |t|
|
28
|
+
t.pattern = "spec/**/*_spec.rb"
|
29
|
+
t.rcov = true
|
30
|
+
t.rcov_opts = ['--exclude', 'spec']
|
31
|
+
end
|
32
|
+
|
33
|
+
rescue LoadError
|
34
|
+
puts "Rspec not available. Install it with: gem install rspec"
|
35
|
+
end
|
36
|
+
|
37
|
+
begin
|
38
|
+
require 'jeweler'
|
39
|
+
Jeweler::Tasks.new do |gemspec|
|
40
|
+
gemspec.name = "custom-attributes"
|
41
|
+
gemspec.summary = "Management for custom model attributes."
|
42
|
+
gemspec.description = "Easy management of extra model attributes. Can store fields in the model if provided"
|
43
|
+
gemspec.email = "matthijs.groen@gmail.com"
|
44
|
+
gemspec.homepage = "http://github.com/matthijsgroen/custom-attributes"
|
45
|
+
gemspec.authors = ["Matthijs Groen"]
|
46
|
+
gemspec.files = FileList["[A-Z]*", "{generators,lib,spec,rails}/**/*"] - FileList["**/*.log"]
|
47
|
+
end
|
48
|
+
Jeweler::GemcutterTasks.new
|
49
|
+
rescue LoadError
|
50
|
+
puts "Jeweler not available. Install it with: gem install jeweler"
|
51
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.0.1
|
@@ -0,0 +1,33 @@
|
|
1
|
+
class ActsAsTaggableOnMigration < ActiveRecord::Migration
|
2
|
+
def self.up
|
3
|
+
create_table :custom_attributes do |t|
|
4
|
+
# label
|
5
|
+
t.string :field_name
|
6
|
+
t.string :field_label
|
7
|
+
|
8
|
+
# value type
|
9
|
+
t.string :value_type
|
10
|
+
|
11
|
+
# fields for value storage
|
12
|
+
t.string :text_value
|
13
|
+
t.datetime :date_time_value
|
14
|
+
t.integer :number_value
|
15
|
+
t.float :float_value
|
16
|
+
|
17
|
+
# sorting
|
18
|
+
t.integer :position
|
19
|
+
# link to object
|
20
|
+
t.references :item, :polymorphic => true
|
21
|
+
|
22
|
+
t.timestamps
|
23
|
+
end
|
24
|
+
|
25
|
+
add_index :custom_attributes, :value_type
|
26
|
+
add_index :custom_attributes, [:item_id, :item_type]
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.down
|
30
|
+
drop_table :custom_attributes
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
|
3
|
+
module CustomAttributes
|
4
|
+
|
5
|
+
CUSTOM_ATTRIBUTE_TYPES = %w(string integer boolean date time datetime float)
|
6
|
+
|
7
|
+
def self.included(base) # :nodoc:
|
8
|
+
base.extend ClassMethods
|
9
|
+
end
|
10
|
+
|
11
|
+
module ClassMethods
|
12
|
+
|
13
|
+
def has_custom_attributes?
|
14
|
+
false
|
15
|
+
end
|
16
|
+
|
17
|
+
def has_custom_attributes(extra_field_types = {}, &block)
|
18
|
+
raise "Block expected" unless block_given?
|
19
|
+
|
20
|
+
field_definitions = CustomAttributeDefinitionHelper.new extra_field_types
|
21
|
+
yield field_definitions
|
22
|
+
defined_custom_attributes = field_definitions.defined_attributes
|
23
|
+
|
24
|
+
if has_custom_attributes?
|
25
|
+
write_inheritable_attribute(:defined_custom_attributes, self.defined_custom_attributes.deep_merge(defined_custom_attributes))
|
26
|
+
write_inheritable_attribute(:defined_custom_field_types, self.extra_field_types.merge(extra_field_types))
|
27
|
+
else
|
28
|
+
write_inheritable_attribute(:defined_custom_attributes, defined_custom_attributes)
|
29
|
+
class_inheritable_reader(:defined_custom_attributes)
|
30
|
+
|
31
|
+
write_inheritable_attribute(:defined_custom_field_types, extra_field_types)
|
32
|
+
class_inheritable_reader(:defined_custom_field_types)
|
33
|
+
|
34
|
+
class_eval do
|
35
|
+
def self.has_custom_attributes?
|
36
|
+
true
|
37
|
+
end
|
38
|
+
|
39
|
+
include ActiveRecord::CustomAttributes::Core
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
|
46
|
+
class CustomAttributeDefinitionHelper
|
47
|
+
|
48
|
+
def initialize extra_field_types
|
49
|
+
@defined_attributes = {}
|
50
|
+
|
51
|
+
extra_field_types.each do |key, value|
|
52
|
+
class_eval do
|
53
|
+
define_method key do |*args|
|
54
|
+
define_field(key, *args)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
ActiveRecord::CustomAttributes::CUSTOM_ATTRIBUTE_TYPES.each do |type|
|
61
|
+
define_method type do |*args|
|
62
|
+
define_field(type.to_sym, *args)
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
attr_reader :defined_attributes
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def define_field(type, *args)
|
71
|
+
options = args.extract_options!
|
72
|
+
attributes_on_model = {}
|
73
|
+
if options[:on_model].is_a? Array
|
74
|
+
options[:on_model].each do |attribute|
|
75
|
+
attributes_on_model[attribute] = attribute
|
76
|
+
end
|
77
|
+
elsif options[:on_model].is_a? Hash
|
78
|
+
attributes_on_model = options[:on_model]
|
79
|
+
end
|
80
|
+
args.each do |field|
|
81
|
+
field = field.to_sym
|
82
|
+
@defined_attributes[type] ||= {}
|
83
|
+
@defined_attributes[type][field] = {
|
84
|
+
:type => type,
|
85
|
+
:name => field,
|
86
|
+
:on_model => attributes_on_model[field] || false
|
87
|
+
}
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
module Core
|
94
|
+
|
95
|
+
def self.included(base)
|
96
|
+
base.send :include, ActiveRecord::CustomAttributes::Core::InstanceMethods
|
97
|
+
base.extend ActiveRecord::CustomAttributes::Core::ClassMethods
|
98
|
+
base.has_many :external_custom_attributes, :class_name => "ActiveRecord::CustomAttributes::CustomAttributeModel",
|
99
|
+
:as => :item, :dependent => :destroy
|
100
|
+
|
101
|
+
base.before_save :cache_custom_attributes
|
102
|
+
base.after_save :save_custom_attributes
|
103
|
+
end
|
104
|
+
|
105
|
+
module ClassMethods
|
106
|
+
|
107
|
+
end
|
108
|
+
|
109
|
+
module InstanceMethods
|
110
|
+
def custom_attributes
|
111
|
+
@custom_attributes ||= ActiveRecord::CustomAttributes::CustomAttributeList.new(self)
|
112
|
+
end
|
113
|
+
|
114
|
+
private
|
115
|
+
|
116
|
+
def cache_custom_attributes
|
117
|
+
custom_attributes.update_cache
|
118
|
+
end
|
119
|
+
|
120
|
+
def save_custom_attributes
|
121
|
+
custom_attributes.save
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class ActiveRecord::CustomAttributes::CustomAttribute
|
2
|
+
|
3
|
+
def initialize(item_list, main_model, attribute_model)
|
4
|
+
@main_model = main_model
|
5
|
+
@item_list = item_list
|
6
|
+
@attribute_model = attribute_model
|
7
|
+
load if @attribute_model
|
8
|
+
end
|
9
|
+
|
10
|
+
attr_accessor :type, :value, :label, :internal_label
|
11
|
+
|
12
|
+
def rename_to new_name
|
13
|
+
item_list.rename_label_of self, new_name
|
14
|
+
end
|
15
|
+
|
16
|
+
def save
|
17
|
+
attribute_model.value_type = type.to_s
|
18
|
+
attribute_model.field_name = internal_label.to_s
|
19
|
+
attribute_model.field_label = label
|
20
|
+
write_value = item_list.supported_attribute_types[type]
|
21
|
+
field = FIELD_MAPPING[write_value]
|
22
|
+
|
23
|
+
converted_value = value
|
24
|
+
converted_value = value ? 1 : 0 if write_value == :boolean
|
25
|
+
|
26
|
+
([:text, :date_time, :number, :float] - [field]).each { |value_field| attribute_model.send("#{value_field}_value=", nil) }
|
27
|
+
attribute_model.send("#{field}_value=", value)
|
28
|
+
attribute_model.save
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
attr_reader :main_model, :item_list
|
34
|
+
|
35
|
+
FIELD_MAPPING = {
|
36
|
+
:text => :text,
|
37
|
+
:string => :text,
|
38
|
+
:float => :float,
|
39
|
+
:number => :number,
|
40
|
+
:boolean => :number,
|
41
|
+
:date_time => :date_time,
|
42
|
+
:date => :date_time,
|
43
|
+
:time => :date_time
|
44
|
+
}
|
45
|
+
|
46
|
+
def load
|
47
|
+
self.type = attribute_model.value_type.to_sym
|
48
|
+
self.internal_label = attribute_model.field_name.to_sym
|
49
|
+
self.label = attribute_model.field_label
|
50
|
+
|
51
|
+
read_value = item_list.supported_attribute_types[self.type]
|
52
|
+
field = FIELD_MAPPING[read_value]
|
53
|
+
value = attribute_model.send("#{field}_value")
|
54
|
+
value = value == 0 ? false : true if read_value == :boolean
|
55
|
+
#puts "#{self.type} => #{read_value} = (#{field}) #{value} - #{self.label} (#{self.internal_label})"
|
56
|
+
self.value = value
|
57
|
+
end
|
58
|
+
|
59
|
+
def attribute_model
|
60
|
+
@attribute_model ||= main_model.external_custom_attributes.build
|
61
|
+
end
|
62
|
+
|
63
|
+
end
|
@@ -0,0 +1,123 @@
|
|
1
|
+
class ActiveRecord::CustomAttributes::CustomAttributeList
|
2
|
+
|
3
|
+
def initialize(record)
|
4
|
+
@record = record
|
5
|
+
@defined_attributes = @record.class.defined_custom_attributes
|
6
|
+
@extra_attribute_types = @record.class.defined_custom_field_types
|
7
|
+
define_attribute_methods
|
8
|
+
end
|
9
|
+
|
10
|
+
def add(type, label, value)
|
11
|
+
type = type.to_sym
|
12
|
+
internal_label = convert_to_internal_label(type, label)
|
13
|
+
attribute = get_attribute(type, internal_label || label, true)
|
14
|
+
attribute.value = value
|
15
|
+
end
|
16
|
+
|
17
|
+
def update_cache
|
18
|
+
defined_attributes.each do |type, attributes|
|
19
|
+
attributes.each do |name, options|
|
20
|
+
if cache_attribute = options[:on_model]
|
21
|
+
if attribute = get_attribute(type, name)
|
22
|
+
record.send("#{cache_attribute}=", attribute.value)
|
23
|
+
else
|
24
|
+
record.send("#{cache_attribute}=", nil)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
def save
|
32
|
+
loaded_attributes.each(&:save)
|
33
|
+
end
|
34
|
+
|
35
|
+
def supported_attribute_types
|
36
|
+
return @supported_attribute_types if @supported_attribute_types
|
37
|
+
standard_attribute_types = {}
|
38
|
+
ActiveRecord::CustomAttributes::CUSTOM_ATTRIBUTE_TYPES.each { |t| standard_attribute_types[t.to_sym] = t.to_sym }
|
39
|
+
@supported_attribute_types = (standard_attribute_types.merge extra_attribute_types)
|
40
|
+
end
|
41
|
+
|
42
|
+
def rename_label_of attribute, new_name
|
43
|
+
internal_label = convert_to_internal_label(attribute.type, new_name)
|
44
|
+
attribute.label = new_name
|
45
|
+
attribute.internal_label = internal_label
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
attr_reader :defined_attributes, :extra_attribute_types, :record
|
51
|
+
|
52
|
+
def get_attribute(type, internal_label, auto_create = false)
|
53
|
+
if internal_label.is_a? Symbol
|
54
|
+
attribute = loaded_attributes.find { |a| a.type == type and a.internal_label.to_s == internal_label.to_s }
|
55
|
+
else
|
56
|
+
attribute = loaded_attributes.find { |a| a.type == type and a.label == internal_label }
|
57
|
+
end
|
58
|
+
return attribute if attribute
|
59
|
+
return nil unless auto_create
|
60
|
+
|
61
|
+
new_attribute = ActiveRecord::CustomAttributes::CustomAttribute.new(self, record, nil)
|
62
|
+
new_attribute.type = type.to_sym
|
63
|
+
|
64
|
+
if internal_label.is_a? Symbol
|
65
|
+
new_attribute.internal_label = internal_label
|
66
|
+
new_attribute.label = human_label_for(type, internal_label)
|
67
|
+
else
|
68
|
+
new_attribute.internal_label = convert_to_internal_label(type, internal_label)
|
69
|
+
new_attribute.label = internal_label
|
70
|
+
end
|
71
|
+
|
72
|
+
loaded_attributes << new_attribute
|
73
|
+
new_attribute
|
74
|
+
end
|
75
|
+
|
76
|
+
def convert_to_internal_label(type, label)
|
77
|
+
(defined_attributes[type] || {}).each do |name, options|
|
78
|
+
return name if label == human_label_for(type, name)
|
79
|
+
end
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
|
83
|
+
def human_label_for(type, internal_name)
|
84
|
+
translate_scope = [:activerecord, :custom_attributes, record.class.name.underscore.to_sym, type.to_sym]
|
85
|
+
defaults = [internal_name.to_s.underscore.gsub("_", " ").capitalize]
|
86
|
+
I18n.t(internal_name, :scope => translate_scope, :default => defaults)
|
87
|
+
end
|
88
|
+
|
89
|
+
def get_attributes_of_type(type)
|
90
|
+
loaded_attributes.select { |i| i.type == type }
|
91
|
+
end
|
92
|
+
|
93
|
+
def get_value_of(type, internal_label)
|
94
|
+
found = get_attribute(type, internal_label)
|
95
|
+
found.value if found
|
96
|
+
end
|
97
|
+
|
98
|
+
def loaded_attributes
|
99
|
+
@loaded_attributes ||= record.external_custom_attributes.collect do |attribute_record|
|
100
|
+
ActiveRecord::CustomAttributes::CustomAttribute.new(self, record, attribute_record)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def define_attribute_methods
|
105
|
+
supported_attribute_types.each do |key, value|
|
106
|
+
class_eval do
|
107
|
+
|
108
|
+
define_method "add_#{key}" do |label, value|
|
109
|
+
add(key.to_sym, label, value)
|
110
|
+
end
|
111
|
+
|
112
|
+
define_method "#{key}_attributes" do
|
113
|
+
get_attributes_of_type(key.to_sym)
|
114
|
+
end
|
115
|
+
|
116
|
+
define_method "#{key}_value_of" do |internal_name|
|
117
|
+
get_value_of(key.to_sym, internal_name)
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require "active_record"
|
2
|
+
require "active_support"
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift(File.dirname(__FILE__))
|
5
|
+
|
6
|
+
require "active_record/custom_attributes"
|
7
|
+
require "active_record/custom_attributes/custom_attribute"
|
8
|
+
require "active_record/custom_attributes/custom_attribute_list"
|
9
|
+
require "active_record/custom_attributes/custom_attribute_model"
|
10
|
+
|
11
|
+
$LOAD_PATH.shift
|
12
|
+
|
13
|
+
if defined?(ActiveRecord::Base)
|
14
|
+
ActiveRecord::Base.send :include, ActiveRecord::CustomAttributes
|
15
|
+
end
|
data/rails/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'custom-attributes'
|
@@ -0,0 +1,117 @@
|
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
2
|
+
|
3
|
+
describe "Model without custom attributes" do
|
4
|
+
|
5
|
+
subject { Location }
|
6
|
+
|
7
|
+
it { should_not have_custom_attributes }
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
describe "Model with custom attributes" do
|
12
|
+
|
13
|
+
subject { Person }
|
14
|
+
|
15
|
+
it { should have_custom_attributes }
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "Custom attributes of a person" do
|
20
|
+
before(:each) do
|
21
|
+
clean_database!
|
22
|
+
|
23
|
+
I18n.backend.store_translations :'nl', {
|
24
|
+
:activerecord => { :custom_attributes => { :person => { :telephone => { :private => "Prive" }}}}
|
25
|
+
}
|
26
|
+
I18n.default_locale = 'nl'
|
27
|
+
|
28
|
+
@person = Person.new
|
29
|
+
end
|
30
|
+
|
31
|
+
subject { @person.custom_attributes }
|
32
|
+
|
33
|
+
it { should respond_to :add_telephone }
|
34
|
+
it { should_not respond_to :add_size }
|
35
|
+
|
36
|
+
it "should add custom field values" do
|
37
|
+
@person.custom_attributes.add_telephone "Prive", "06 28 61 06 28"
|
38
|
+
fields = @person.custom_attributes.telephone_attributes
|
39
|
+
fields.should have(1).item
|
40
|
+
fields[0].value.should == "06 28 61 06 28"
|
41
|
+
end
|
42
|
+
|
43
|
+
it "should cache fields locally" do
|
44
|
+
@person.custom_attributes.add_date "Born on", Date.civil(1981, 5, 31)
|
45
|
+
@person.save
|
46
|
+
@person.born_on.should == Date.civil(1981,5, 31)
|
47
|
+
end
|
48
|
+
|
49
|
+
it "should store values in the database" do
|
50
|
+
@person.custom_attributes.add_telephone "Prive", "06 28 61 06 28"
|
51
|
+
@person.save
|
52
|
+
|
53
|
+
loaded_person = Person.find @person.id
|
54
|
+
fields = loaded_person.custom_attributes.telephone_attributes
|
55
|
+
fields.should have(1).item
|
56
|
+
fields[0].value.should == "06 28 61 06 28"
|
57
|
+
end
|
58
|
+
|
59
|
+
it "should load values by key" do
|
60
|
+
@person.custom_attributes.add_telephone "Prive", "06 28 61 06 28"
|
61
|
+
@person.save
|
62
|
+
|
63
|
+
loaded_person = Person.find @person.id
|
64
|
+
loaded_person.custom_attributes.telephone_value_of(:private).should == "06 28 61 06 28"
|
65
|
+
end
|
66
|
+
|
67
|
+
it "should rename labels" do
|
68
|
+
@person.custom_attributes.add_telephone "Werk", "06 28 61 06 28"
|
69
|
+
|
70
|
+
fields = @person.custom_attributes.telephone_attributes
|
71
|
+
fields.should have(1).item
|
72
|
+
fields[0].value.should == "06 28 61 06 28"
|
73
|
+
fields[0].rename_to "Prive"
|
74
|
+
|
75
|
+
@person.custom_attributes.telephone_value_of(:private).should == "06 28 61 06 28"
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
describe "Custom attributes of a product" do
|
82
|
+
before(:each) do
|
83
|
+
clean_database!
|
84
|
+
@product = Product.new
|
85
|
+
end
|
86
|
+
|
87
|
+
subject { @product.custom_attributes }
|
88
|
+
|
89
|
+
it { should_not respond_to :add_telephone }
|
90
|
+
it { should respond_to :add_size }
|
91
|
+
|
92
|
+
it "should add custom field values" do
|
93
|
+
@product.custom_attributes.add_size "Width", 5.60
|
94
|
+
fields = @product.custom_attributes.size_attributes
|
95
|
+
fields.should have(1).item
|
96
|
+
fields[0].value.should == 5.60
|
97
|
+
end
|
98
|
+
|
99
|
+
it "should cache not fields locally" do
|
100
|
+
@product.custom_attributes.add_size "Width", 5.60
|
101
|
+
@product.save
|
102
|
+
@product.width.should == nil
|
103
|
+
@product.custom_attributes.size_value_of(:width).should == 5.60
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should rename labels" do
|
107
|
+
@product.custom_attributes.add_size "Width", 5.60
|
108
|
+
|
109
|
+
fields = @product.custom_attributes.size_attributes
|
110
|
+
fields.should have(1).item
|
111
|
+
fields[0].value.should == 5.60
|
112
|
+
fields[0].rename_to "Height"
|
113
|
+
|
114
|
+
@product.custom_attributes.size_value_of(:width).should == nil
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
data/spec/database.yml
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
sqlite3:
|
2
|
+
adapter: sqlite3
|
3
|
+
database: custom_attributes.sqlite3
|
4
|
+
|
5
|
+
mysql:
|
6
|
+
adapter: mysql
|
7
|
+
hostname: localhost
|
8
|
+
username: root
|
9
|
+
password:
|
10
|
+
database: custom_attributes
|
11
|
+
|
12
|
+
postgresql:
|
13
|
+
adapter: postgresql
|
14
|
+
hostname: localhost
|
15
|
+
username: postgres
|
16
|
+
password:
|
17
|
+
database: custom_attributes
|
@@ -0,0 +1,17 @@
|
|
1
|
+
sqlite3:
|
2
|
+
adapter: sqlite3
|
3
|
+
database: custom_attributes.sqlite3
|
4
|
+
|
5
|
+
mysql:
|
6
|
+
adapter: mysql
|
7
|
+
hostname: localhost
|
8
|
+
username: root
|
9
|
+
password:
|
10
|
+
database: custom_attributes
|
11
|
+
|
12
|
+
postgresql:
|
13
|
+
adapter: postgresql
|
14
|
+
hostname: localhost
|
15
|
+
username: postgres
|
16
|
+
password:
|
17
|
+
database: custom_attributes
|
data/spec/models.rb
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
class Person < ActiveRecord::Base
|
2
|
+
|
3
|
+
has_custom_attributes :telephone => :string, :email => :string do |fields|
|
4
|
+
fields.telephone :work, :private, :mobile, :fax
|
5
|
+
fields.email :work, :private
|
6
|
+
fields.date :born_on, :wed_on, :died_on, :on_model => [ :born_on ]
|
7
|
+
end
|
8
|
+
|
9
|
+
end
|
10
|
+
|
11
|
+
class Product < ActiveRecord::Base
|
12
|
+
|
13
|
+
has_custom_attributes :size => :float do |fields|
|
14
|
+
fields.size :width, :height, :depth
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
class Location < ActiveRecord::Base
|
20
|
+
|
21
|
+
end
|
data/spec/schema.rb
ADDED
@@ -0,0 +1,43 @@
|
|
1
|
+
ActiveRecord::Schema.define :version => 0 do
|
2
|
+
create_table :custom_attributes, :force => true do |t|
|
3
|
+
# label
|
4
|
+
t.string :field_name
|
5
|
+
t.string :field_label
|
6
|
+
|
7
|
+
# value type
|
8
|
+
t.string :value_type
|
9
|
+
|
10
|
+
# fields for value storage
|
11
|
+
t.string :text_value
|
12
|
+
t.datetime :date_time_value
|
13
|
+
t.integer :number_value
|
14
|
+
t.float :float_value
|
15
|
+
|
16
|
+
# sorting
|
17
|
+
t.integer :position
|
18
|
+
# link to object
|
19
|
+
t.references :item, :polymorphic => true
|
20
|
+
|
21
|
+
t.timestamps
|
22
|
+
end
|
23
|
+
|
24
|
+
add_index :custom_attributes, :value_type
|
25
|
+
add_index :custom_attributes, [:item_id, :item_type]
|
26
|
+
|
27
|
+
create_table "people", :force => true do |t|
|
28
|
+
t.string :name
|
29
|
+
t.date :born_on
|
30
|
+
t.string :email
|
31
|
+
end
|
32
|
+
|
33
|
+
create_table "products", :force => true do |t|
|
34
|
+
t.string :name
|
35
|
+
t.float :width
|
36
|
+
t.float :height
|
37
|
+
end
|
38
|
+
|
39
|
+
create_table "locations", :force => true do |t|
|
40
|
+
t.string :name
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,59 @@
|
|
1
|
+
$LOAD_PATH << "." unless $LOAD_PATH.include?(".")
|
2
|
+
|
3
|
+
begin
|
4
|
+
require "rubygems"
|
5
|
+
require "bundler"
|
6
|
+
|
7
|
+
if Gem::Version.new(Bundler::VERSION) <= Gem::Version.new("0.9.5")
|
8
|
+
raise RuntimeError, "Your bundler version is too old." +
|
9
|
+
"Run `gem install bundler` to upgrade."
|
10
|
+
end
|
11
|
+
|
12
|
+
# Set up load paths for all bundled gems
|
13
|
+
Bundler.setup
|
14
|
+
rescue Bundler::GemNotFound
|
15
|
+
raise RuntimeError, "Bundler couldn't find some gems." +
|
16
|
+
"Did you run `bundle install`?"
|
17
|
+
end
|
18
|
+
|
19
|
+
Bundler.require
|
20
|
+
require File.expand_path('../../lib/custom-attributes', __FILE__)
|
21
|
+
|
22
|
+
unless [].respond_to?(:freq)
|
23
|
+
class Array
|
24
|
+
def freq
|
25
|
+
k=Hash.new(0)
|
26
|
+
each {|e| k[e]+=1}
|
27
|
+
k
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
ENV['DB'] ||= 'sqlite3'
|
33
|
+
|
34
|
+
database_yml = File.expand_path('../database.yml', __FILE__)
|
35
|
+
if File.exists?(database_yml)
|
36
|
+
active_record_configuration = YAML.load_file(database_yml)[ENV['DB']]
|
37
|
+
|
38
|
+
ActiveRecord::Base.establish_connection(active_record_configuration)
|
39
|
+
ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
|
40
|
+
|
41
|
+
ActiveRecord::Base.silence do
|
42
|
+
ActiveRecord::Migration.verbose = false
|
43
|
+
|
44
|
+
load(File.dirname(__FILE__) + '/schema.rb')
|
45
|
+
load(File.dirname(__FILE__) + '/models.rb')
|
46
|
+
end
|
47
|
+
|
48
|
+
else
|
49
|
+
raise "Please create #{database_yml} first to configure your database. Take a look at: #{database_yml}.sample"
|
50
|
+
end
|
51
|
+
|
52
|
+
def clean_database!
|
53
|
+
models = [Person]
|
54
|
+
models.each do |model|
|
55
|
+
ActiveRecord::Base.connection.execute "DELETE FROM #{model.table_name}"
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
clean_database!
|
metadata
ADDED
@@ -0,0 +1,89 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: custom-attributes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
hash: 29
|
5
|
+
prerelease: false
|
6
|
+
segments:
|
7
|
+
- 0
|
8
|
+
- 0
|
9
|
+
- 1
|
10
|
+
version: 0.0.1
|
11
|
+
platform: ruby
|
12
|
+
authors:
|
13
|
+
- Matthijs Groen
|
14
|
+
autorequire:
|
15
|
+
bindir: bin
|
16
|
+
cert_chain: []
|
17
|
+
|
18
|
+
date: 2010-10-05 00:00:00 +02:00
|
19
|
+
default_executable:
|
20
|
+
dependencies: []
|
21
|
+
|
22
|
+
description: Easy management of extra model attributes. Can store fields in the model if provided
|
23
|
+
email: matthijs.groen@gmail.com
|
24
|
+
executables: []
|
25
|
+
|
26
|
+
extensions: []
|
27
|
+
|
28
|
+
extra_rdoc_files:
|
29
|
+
- README.markdown
|
30
|
+
files:
|
31
|
+
- Gemfile
|
32
|
+
- Gemfile.lock
|
33
|
+
- MIT-LICENSE
|
34
|
+
- README.markdown
|
35
|
+
- Rakefile
|
36
|
+
- VERSION
|
37
|
+
- generators/has_custom_attributes_migration/has_custom_attributes_migration_generator.rb
|
38
|
+
- generators/has_custom_attributes_migration/templates/migration.rb
|
39
|
+
- lib/active_record/custom_attributes.rb
|
40
|
+
- lib/active_record/custom_attributes/custom_attribute.rb
|
41
|
+
- lib/active_record/custom_attributes/custom_attribute_list.rb
|
42
|
+
- lib/active_record/custom_attributes/custom_attribute_model.rb
|
43
|
+
- lib/custom-attributes.rb
|
44
|
+
- rails/init.rb
|
45
|
+
- spec/custom_attributes/has_custom_attributes_spec.rb
|
46
|
+
- spec/database.yml
|
47
|
+
- spec/database.yml.sample
|
48
|
+
- spec/models.rb
|
49
|
+
- spec/schema.rb
|
50
|
+
- spec/spec_helper.rb
|
51
|
+
has_rdoc: true
|
52
|
+
homepage: http://github.com/matthijsgroen/custom-attributes
|
53
|
+
licenses: []
|
54
|
+
|
55
|
+
post_install_message:
|
56
|
+
rdoc_options:
|
57
|
+
- --charset=UTF-8
|
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
|
+
hash: 3
|
66
|
+
segments:
|
67
|
+
- 0
|
68
|
+
version: "0"
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
none: false
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
hash: 3
|
75
|
+
segments:
|
76
|
+
- 0
|
77
|
+
version: "0"
|
78
|
+
requirements: []
|
79
|
+
|
80
|
+
rubyforge_project:
|
81
|
+
rubygems_version: 1.3.7
|
82
|
+
signing_key:
|
83
|
+
specification_version: 3
|
84
|
+
summary: Management for custom model attributes.
|
85
|
+
test_files:
|
86
|
+
- spec/spec_helper.rb
|
87
|
+
- spec/models.rb
|
88
|
+
- spec/custom_attributes/has_custom_attributes_spec.rb
|
89
|
+
- spec/schema.rb
|