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 ADDED
@@ -0,0 +1,10 @@
1
+ source :gemcutter
2
+
3
+ # Rails 3.0
4
+ gem 'rails', '3.0.0'
5
+ gem 'rspec', '2.0.0.beta.22'
6
+ gem 'sqlite3-ruby', :require => 'sqlite3'
7
+ gem 'mysql'
8
+ gem 'pg'
9
+ gem 'jeweler'
10
+ gem 'rcov'
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,7 @@
1
+ class HasCustomAttributesMigrationGenerator < Rails::Generator::Base
2
+ def manifest
3
+ record do |m|
4
+ m.migration_template 'migration.rb', 'db/migrate', :migration_file_name => "has_custom_attributes_migration"
5
+ end
6
+ end
7
+ end
@@ -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,5 @@
1
+ class ActiveRecord::CustomAttributes::CustomAttributeModel < ActiveRecord::Base
2
+ set_table_name "custom_attributes"
3
+
4
+ belongs_to :item, :polymorphic => true
5
+ 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
@@ -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