custom-attributes 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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