has_eav 1.0.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.
Files changed (46) hide show
  1. data/.document +5 -0
  2. data/Gemfile +14 -0
  3. data/Gemfile.lock +85 -0
  4. data/LICENSE.txt +20 -0
  5. data/README.rdoc +100 -0
  6. data/Rakefile +53 -0
  7. data/VERSION +1 -0
  8. data/has_eav.gemspec +131 -0
  9. data/init.rb +1 -0
  10. data/lib/has_eav.rb +214 -0
  11. data/test/app/models/post.rb +6 -0
  12. data/test/app/models/post_attribute.rb +3 -0
  13. data/test/app/models/product.rb +8 -0
  14. data/test/app/models/product_attribute.rb +2 -0
  15. data/test/config/application.rb +47 -0
  16. data/test/config/boot.rb +13 -0
  17. data/test/config/database.yml +22 -0
  18. data/test/config/environment.rb +5 -0
  19. data/test/config/environments/development.rb +26 -0
  20. data/test/config/environments/production.rb +49 -0
  21. data/test/config/environments/test.rb +35 -0
  22. data/test/config/initializers/backtrace_silencers.rb +7 -0
  23. data/test/config/initializers/inflections.rb +10 -0
  24. data/test/config/initializers/mime_types.rb +5 -0
  25. data/test/config/initializers/secret_token.rb +7 -0
  26. data/test/config/initializers/session_store.rb +8 -0
  27. data/test/config/locales/en.yml +5 -0
  28. data/test/config/routes.rb +58 -0
  29. data/test/config.ru +4 -0
  30. data/test/db/migrate/20101210093949_create_posts.rb +14 -0
  31. data/test/db/migrate/20101210100309_create_post_attributes.rb +15 -0
  32. data/test/db/migrate/20101210102322_create_products.rb +14 -0
  33. data/test/db/migrate/20101210102347_create_product_attributes.rb +15 -0
  34. data/test/helper.rb +28 -0
  35. data/test/log/development.log +4 -0
  36. data/test/script/rails +6 -0
  37. data/test/test/fixtures/post_attributes.yml +11 -0
  38. data/test/test/fixtures/posts.yml +9 -0
  39. data/test/test/fixtures/product_attributes.yml +9 -0
  40. data/test/test/fixtures/products.yml +9 -0
  41. data/test/test/unit/post_attribute_test.rb +8 -0
  42. data/test/test/unit/post_test.rb +8 -0
  43. data/test/test/unit/product_attribute_test.rb +8 -0
  44. data/test/test/unit/product_test.rb +8 -0
  45. data/test/test_has_eav.rb +90 -0
  46. metadata +242 -0
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,14 @@
1
+ source "http://rubygems.org"
2
+
3
+ gem "activerecord", "~> 3.0.3"
4
+
5
+ # Add dependencies to develop your gem here.
6
+ # Include everything needed to run rake, tests, features, etc.
7
+ group :development do
8
+ gem "shoulda", ">= 0"
9
+ gem "bundler", "~> 1.0.0"
10
+ gem "jeweler", "~> 1.5.1"
11
+ gem "rcov", ">= 0"
12
+ gem "rails", "~> 3.0.3"
13
+ gem "sqlite3-ruby", :require => "sqlite3"
14
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,85 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ abstract (1.0.0)
5
+ actionmailer (3.0.3)
6
+ actionpack (= 3.0.3)
7
+ mail (~> 2.2.9)
8
+ actionpack (3.0.3)
9
+ activemodel (= 3.0.3)
10
+ activesupport (= 3.0.3)
11
+ builder (~> 2.1.2)
12
+ erubis (~> 2.6.6)
13
+ i18n (~> 0.4)
14
+ rack (~> 1.2.1)
15
+ rack-mount (~> 0.6.13)
16
+ rack-test (~> 0.5.6)
17
+ tzinfo (~> 0.3.23)
18
+ activemodel (3.0.3)
19
+ activesupport (= 3.0.3)
20
+ builder (~> 2.1.2)
21
+ i18n (~> 0.4)
22
+ activerecord (3.0.3)
23
+ activemodel (= 3.0.3)
24
+ activesupport (= 3.0.3)
25
+ arel (~> 2.0.2)
26
+ tzinfo (~> 0.3.23)
27
+ activeresource (3.0.3)
28
+ activemodel (= 3.0.3)
29
+ activesupport (= 3.0.3)
30
+ activesupport (3.0.3)
31
+ arel (2.0.6)
32
+ builder (2.1.2)
33
+ erubis (2.6.6)
34
+ abstract (>= 1.0.0)
35
+ git (1.2.5)
36
+ i18n (0.5.0)
37
+ jeweler (1.5.1)
38
+ bundler (~> 1.0.0)
39
+ git (>= 1.2.5)
40
+ rake
41
+ mail (2.2.12)
42
+ activesupport (>= 2.3.6)
43
+ i18n (>= 0.4.0)
44
+ mime-types (~> 1.16)
45
+ treetop (~> 1.4.8)
46
+ mime-types (1.16)
47
+ polyglot (0.3.1)
48
+ rack (1.2.1)
49
+ rack-mount (0.6.13)
50
+ rack (>= 1.0.0)
51
+ rack-test (0.5.6)
52
+ rack (>= 1.0)
53
+ rails (3.0.3)
54
+ actionmailer (= 3.0.3)
55
+ actionpack (= 3.0.3)
56
+ activerecord (= 3.0.3)
57
+ activeresource (= 3.0.3)
58
+ activesupport (= 3.0.3)
59
+ bundler (~> 1.0)
60
+ railties (= 3.0.3)
61
+ railties (3.0.3)
62
+ actionpack (= 3.0.3)
63
+ activesupport (= 3.0.3)
64
+ rake (>= 0.8.7)
65
+ thor (~> 0.14.4)
66
+ rake (0.8.7)
67
+ rcov (0.9.9)
68
+ shoulda (2.11.3)
69
+ sqlite3-ruby (1.3.2)
70
+ thor (0.14.6)
71
+ treetop (1.4.9)
72
+ polyglot (>= 0.3.1)
73
+ tzinfo (0.3.23)
74
+
75
+ PLATFORMS
76
+ ruby
77
+
78
+ DEPENDENCIES
79
+ activerecord (~> 3.0.3)
80
+ bundler (~> 1.0.0)
81
+ jeweler (~> 1.5.1)
82
+ rails (~> 3.0.3)
83
+ rcov
84
+ shoulda
85
+ sqlite3-ruby
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Hartog C. de Mik
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.
data/README.rdoc ADDED
@@ -0,0 +1,100 @@
1
+ = has_eav
2
+
3
+ == Description
4
+
5
+ Enabled EAV behaviour on ActiveRecord (or is at least supposed to).
6
+
7
+ This was inspired by https://github.com/visfleet/acts_as_eav_model but is a
8
+ more minimized approach and is less automated.
9
+
10
+ === EAV
11
+
12
+ Stands for Entity-Attribute-Value and is a database design pattern to unload
13
+ attributes from a table.
14
+
15
+ From wikipedia:
16
+
17
+ Entity-attribute-value model (EAV) is a data model to describe entities where
18
+ the number of attributes (properties, parameters) that can be used to describe
19
+ them is potentially vast, but the number that will actually apply to a given
20
+ entity is relatively modest. In mathematics, this model is known as a
21
+ *sparse matrix*. EAV is also known as *object-attribute-value* model and
22
+ *open schema*.
23
+
24
+
25
+ == Author
26
+ [name] Hartog de Mik (aka: coffeeaddict, coffeeaddict_nl)
27
+ [email] name.downcase.gsub(" ",".")@gmail.com
28
+ [source] https://github.com/coffeeaddict/has_eav
29
+
30
+ == Installation
31
+ gem install has_eav
32
+
33
+ == Setup
34
+
35
+ In your model
36
+
37
+ class Post < ActiveRecord::Base
38
+ has_many :tags
39
+ belongs_to :user
40
+ has_eav :through => :post_attribute do
41
+ eav_attribute :author_name
42
+ eav_attribute :author_age, Integer
43
+ eav_attribute :author_email
44
+ end
45
+ end
46
+
47
+ Now, generate a model to hold the attributes
48
+
49
+ rails generate model PostAttribute post:references name:string value:string
50
+
51
+ And you are good to go.
52
+
53
+ == Usage
54
+
55
+ === Usage of EAV
56
+
57
+ For the general usage of EAV, thread wisely. Be careful and suspicious about
58
+ your needs.
59
+
60
+ === Usage of has_eav
61
+
62
+ Once you have setup +has_eav :through+ on your model, you can call the defined
63
+ attributes as if they where present on your model. You should be able to treat
64
+ your model as if the defined attributes where present in the database.
65
+
66
+ In contradiction to acts_as_eav_model, has_eav will not allow for an all open
67
+ structure. You must define the attributes to be required.
68
+
69
+ ==== Instance eav_attributes
70
+
71
+ If you create a method +instance_eav_attributes+ in your model, has_eav will
72
+ recognize these attributes as well.
73
+
74
+ This might be useful for state machines or STI classes where you want to have
75
+ different attributes based on the current state or type of the instance.
76
+
77
+ == Contributing to has_eav
78
+
79
+ * Check out the latest master to make sure the feature hasn't been implemented
80
+ or the bug hasn't been fixed yet
81
+ * Check out the issue tracker to make sure someone already hasn't requested
82
+ it and/or contributed it
83
+ * Fork the project
84
+ * Start a feature/bugfix branch
85
+ * Commit and push until you are happy with your contribution
86
+ * Make sure to add tests for it. This is important so I don't break it in a
87
+ future version unintentionally.
88
+ * Please try not to mess with the Rakefile, version, or history. If you want to
89
+ have your own version, or is otherwise necessary, that is fine, but please
90
+ isolate to its own commit so I can cherry-pick around it.
91
+
92
+ == TODO
93
+
94
+ * Create a nice generator
95
+ * Put type casting on instance eav attributes
96
+
97
+ == Copyright
98
+
99
+ Copyright (c) 2010 Hartog C. de Mik. See LICENSE.txt for further details.
100
+
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "has_eav"
16
+ gem.homepage = "http://github.com/coffeeaddict/has_eav"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Put EAV behaviour on your ActiveRecord models}
19
+ gem.description = %Q{Put EAV behaviour on your ActiveRecord models}
20
+ gem.email = "hcdm@matchvertise.com"
21
+ gem.authors = ["Hartog C. de Mik"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'test'
32
+ test.pattern = 'test/**/test_*.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ require 'rcov/rcovtask'
37
+ Rcov::RcovTask.new do |test|
38
+ test.libs << 'test'
39
+ test.pattern = 'test/**/test_*.rb'
40
+ test.verbose = true
41
+ end
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "has_eav #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.0
data/has_eav.gemspec ADDED
@@ -0,0 +1,131 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{has_eav}
8
+ s.version = "1.0.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Hartog C. de Mik"]
12
+ s.date = %q{2010-12-10}
13
+ s.description = %q{Put EAV behaviour on your ActiveRecord models}
14
+ s.email = %q{hcdm@matchvertise.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.rdoc"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE.txt",
24
+ "README.rdoc",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "has_eav.gemspec",
28
+ "init.rb",
29
+ "lib/has_eav.rb",
30
+ "test/app/models/post.rb",
31
+ "test/app/models/post_attribute.rb",
32
+ "test/app/models/product.rb",
33
+ "test/app/models/product_attribute.rb",
34
+ "test/config.ru",
35
+ "test/config/application.rb",
36
+ "test/config/boot.rb",
37
+ "test/config/database.yml",
38
+ "test/config/environment.rb",
39
+ "test/config/environments/development.rb",
40
+ "test/config/environments/production.rb",
41
+ "test/config/environments/test.rb",
42
+ "test/config/initializers/backtrace_silencers.rb",
43
+ "test/config/initializers/inflections.rb",
44
+ "test/config/initializers/mime_types.rb",
45
+ "test/config/initializers/secret_token.rb",
46
+ "test/config/initializers/session_store.rb",
47
+ "test/config/locales/en.yml",
48
+ "test/config/routes.rb",
49
+ "test/db/migrate/20101210093949_create_posts.rb",
50
+ "test/db/migrate/20101210100309_create_post_attributes.rb",
51
+ "test/db/migrate/20101210102322_create_products.rb",
52
+ "test/db/migrate/20101210102347_create_product_attributes.rb",
53
+ "test/helper.rb",
54
+ "test/log/development.log",
55
+ "test/script/rails",
56
+ "test/test/fixtures/post_attributes.yml",
57
+ "test/test/fixtures/posts.yml",
58
+ "test/test/fixtures/product_attributes.yml",
59
+ "test/test/fixtures/products.yml",
60
+ "test/test/unit/post_attribute_test.rb",
61
+ "test/test/unit/post_test.rb",
62
+ "test/test/unit/product_attribute_test.rb",
63
+ "test/test/unit/product_test.rb",
64
+ "test/test_has_eav.rb"
65
+ ]
66
+ s.homepage = %q{http://github.com/coffeeaddict/has_eav}
67
+ s.licenses = ["MIT"]
68
+ s.require_paths = ["lib"]
69
+ s.rubygems_version = %q{1.3.7}
70
+ s.summary = %q{Put EAV behaviour on your ActiveRecord models}
71
+ s.test_files = [
72
+ "test/app/models/post.rb",
73
+ "test/app/models/post_attribute.rb",
74
+ "test/app/models/product.rb",
75
+ "test/app/models/product_attribute.rb",
76
+ "test/config/application.rb",
77
+ "test/config/boot.rb",
78
+ "test/config/environment.rb",
79
+ "test/config/environments/development.rb",
80
+ "test/config/environments/production.rb",
81
+ "test/config/environments/test.rb",
82
+ "test/config/initializers/backtrace_silencers.rb",
83
+ "test/config/initializers/inflections.rb",
84
+ "test/config/initializers/mime_types.rb",
85
+ "test/config/initializers/secret_token.rb",
86
+ "test/config/initializers/session_store.rb",
87
+ "test/config/routes.rb",
88
+ "test/db/migrate/20101210093949_create_posts.rb",
89
+ "test/db/migrate/20101210100309_create_post_attributes.rb",
90
+ "test/db/migrate/20101210102322_create_products.rb",
91
+ "test/db/migrate/20101210102347_create_product_attributes.rb",
92
+ "test/helper.rb",
93
+ "test/test/unit/post_attribute_test.rb",
94
+ "test/test/unit/post_test.rb",
95
+ "test/test/unit/product_attribute_test.rb",
96
+ "test/test/unit/product_test.rb",
97
+ "test/test_has_eav.rb"
98
+ ]
99
+
100
+ if s.respond_to? :specification_version then
101
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
102
+ s.specification_version = 3
103
+
104
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
105
+ s.add_runtime_dependency(%q<activerecord>, ["~> 3.0.3"])
106
+ s.add_development_dependency(%q<shoulda>, [">= 0"])
107
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
108
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
109
+ s.add_development_dependency(%q<rcov>, [">= 0"])
110
+ s.add_development_dependency(%q<rails>, ["~> 3.0.3"])
111
+ s.add_development_dependency(%q<sqlite3-ruby>, [">= 0"])
112
+ else
113
+ s.add_dependency(%q<activerecord>, ["~> 3.0.3"])
114
+ s.add_dependency(%q<shoulda>, [">= 0"])
115
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
116
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
117
+ s.add_dependency(%q<rcov>, [">= 0"])
118
+ s.add_dependency(%q<rails>, ["~> 3.0.3"])
119
+ s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
120
+ end
121
+ else
122
+ s.add_dependency(%q<activerecord>, ["~> 3.0.3"])
123
+ s.add_dependency(%q<shoulda>, [">= 0"])
124
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
125
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
126
+ s.add_dependency(%q<rcov>, [">= 0"])
127
+ s.add_dependency(%q<rails>, ["~> 3.0.3"])
128
+ s.add_dependency(%q<sqlite3-ruby>, [">= 0"])
129
+ end
130
+ end
131
+
data/init.rb ADDED
@@ -0,0 +1 @@
1
+ require 'lib/has_eav.rb'
data/lib/has_eav.rb ADDED
@@ -0,0 +1,214 @@
1
+ module ActiveRecord
2
+ module ActsAs
3
+ module HasEav
4
+ def self.included(base)
5
+ base.extend ActiveRecord::ActsAs::HasEav::ClassMethods
6
+ end
7
+
8
+ module ClassMethods
9
+ # Specify that the ActiveModel is an EAV model
10
+ #
11
+ # == Usage
12
+ # # specifiy eav_attributes at instance level
13
+ # has_eav :through => :some_class_with_name_and_value_attributes
14
+ # def available_eav_attributes
15
+ # case self.origin
16
+ # when "remote"
17
+ # %(remote_ip user_agent)
18
+ # when "local"
19
+ # %(user)
20
+ # end + [ :uniq_id ]
21
+ # end
22
+ #
23
+ # # specify some eav_attributes at class level
24
+ # has_eav :through => :x_attributes do
25
+ # eav_attribute :remote_ip
26
+ # eav_attribute :uniq_id
27
+ # end
28
+ #
29
+ # == Mixing class and instance defined EAV attributes
30
+ # You can define EAV attributes both in class and instance context and
31
+ # they will be both adhered
32
+ #
33
+ def has_eav opts={}, &block
34
+ klass = opts.delete :through
35
+ raise(
36
+ "Eav Class cannot be nil. Specify a class using " +
37
+ "has_eav :through => :class"
38
+ ) if klass.blank?
39
+
40
+ class_eval do
41
+ after_save :save_eav_attributes
42
+ end
43
+
44
+ @eav_class = klass.to_s.camelize.constantize
45
+ @eav_attributes = {}
46
+
47
+ yield if block_given?
48
+
49
+ send :include, ActiveRecord::ActsAs::HasEav::InstanceMethods
50
+ end
51
+
52
+ # Add an other attribute to the class list
53
+ def eav_attribute name, type = String
54
+ name = name.to_s if !name.is_a? String
55
+
56
+ self.class_eav_attributes[name] = type
57
+ end
58
+
59
+ # class accessor
60
+ def class_eav_attributes # :nodoc:
61
+ @eav_attributes
62
+ end
63
+
64
+ # class accessor
65
+ def eav_class # :nodoc:
66
+ @eav_class
67
+ end
68
+ end # /ClassMethods
69
+
70
+ module InstanceMethods
71
+ # get to the eav class
72
+ def eav_class
73
+ self.class.eav_class
74
+ end
75
+
76
+ # get the class eav attributes
77
+ def class_eav_attributes
78
+ self.class.class_eav_attributes
79
+ end
80
+
81
+ # Override this to get some usable attributes
82
+ #
83
+ # Cowardly refusing to adhere to all
84
+ def instance_eav_attributes
85
+ []
86
+ end
87
+
88
+ # override method missing, but only kick in when super fails with a
89
+ # NoMethodError
90
+ #
91
+ def method_missing method_symbol, *args, &block
92
+ super
93
+ rescue NoMethodError => e
94
+ method_name = method_symbol.to_s
95
+ attribute_name = method_name.gsub(/[\?=]$/, '')
96
+
97
+ raise e unless eav_attributes_list.include? attribute_name
98
+
99
+ attribute = self.eav_attributes.select { |a|
100
+ a.name == attribute_name
101
+ }.first
102
+
103
+ if method_name =~ /\=$/
104
+ value = args[0]
105
+
106
+ if attribute
107
+ if value
108
+ return attribute.send(:write_attribute, "value", value)
109
+
110
+ elsif value.nil?
111
+ return attribute.destroy
112
+
113
+ end
114
+
115
+ else
116
+ @eav_attributes << eav_class.new(
117
+ :name => attribute_name,
118
+ :value => "#{value}"
119
+ )
120
+
121
+ return cast_eav_value(value, attribute_name)
122
+
123
+ end
124
+ elsif method_name =~ /\?$/
125
+ return ( attribute and attribute.value == true ) ? true : false
126
+
127
+ else
128
+ return attribute ?
129
+ cast_eav_value(attribute.value, attribute_name) :
130
+ nil
131
+
132
+ end
133
+
134
+ raise e
135
+ end
136
+
137
+ # override respond_to?
138
+ def respond_to? method_symbol, is_private=false
139
+ if super == false
140
+ method_name = method_symbol.to_s.gsub(/[\?=]$/, '')
141
+ return true if eav_attributes_list.include? method_name
142
+
143
+ false
144
+ else
145
+ true
146
+ end
147
+ end
148
+
149
+ # get all the eav attribute instances available for this model instance
150
+ #
151
+ # eg: if you model says 'has_eav :through => :post_attribute' these are
152
+ # all PostAttribute's
153
+ #
154
+ def eav_attributes
155
+ @eav_attributes ||= eav_class.all(
156
+ :conditions => { self_key => self.id }
157
+ )
158
+ end
159
+
160
+ # save the list of eav_attribute back to the database
161
+ def save_eav_attributes # :nodoc:
162
+ eav_attributes.select { |a| a.changed? }.each do |a|
163
+ if a.new_record?
164
+ a.send( :write_attribute, self_key, self.id )
165
+ end
166
+
167
+ a.save!
168
+ end
169
+ end
170
+
171
+ # override changed - if any of the eav_attributes has changed, the
172
+ # object has changed.
173
+ #
174
+ def changed?
175
+ eav_attributes.each do |attribute|
176
+ return true if ( attribute.changed? || attribute.new_record? )
177
+ end
178
+
179
+ super
180
+ end
181
+
182
+ # get a complete list of eav_attributes (class + instance)
183
+ def eav_attributes_list # :nodoc:
184
+ (
185
+ self.instance_eav_attributes + self.class_eav_attributes.keys
186
+ ).collect { |attribute| attribute.to_s }.uniq
187
+ end
188
+
189
+ # get the key to my <3
190
+ def self_key # :nodoc:
191
+ "#{self.class.name.underscore}_id".to_sym
192
+ end
193
+
194
+ # cast an eav value to it's desired class
195
+ def cast_eav_value value, attribute # :nodoc:
196
+ attributes = self.class_eav_attributes.stringify_keys
197
+ return value unless attributes.keys.include?(attribute)
198
+
199
+ return (
200
+ eval("#{attributes[attribute]} '#{value}'") ||
201
+ eval("#{attributes[attribute]}.new('#{value}')") ||
202
+ value
203
+ )
204
+ end
205
+
206
+ protected :save_eav_attributes, :self_key, :cast_eav_value
207
+
208
+ end # /InstanceMethods
209
+ end # /HasEav
210
+ end # /ActsAs
211
+ end # /ActiveRecord
212
+
213
+ # insert the has_eav method into ActiveRecord
214
+ ActiveRecord::Base.send( :include, ActiveRecord::ActsAs::HasEav )
@@ -0,0 +1,6 @@
1
+ class Post < ActiveRecord::Base
2
+ has_eav :through => :post_attribute do
3
+ eav_attribute :author_name
4
+ eav_attribute :author_email
5
+ end
6
+ end
@@ -0,0 +1,3 @@
1
+ class PostAttribute < ActiveRecord::Base
2
+ belongs_to :post
3
+ end
@@ -0,0 +1,8 @@
1
+ # class for testing attribute casting
2
+ #
3
+ class Product < ActiveRecord::Base
4
+ has_eav :through => :product_attribute do
5
+ eav_attribute :price, Float
6
+ eav_attribute :cost_price, BigDecimal
7
+ end
8
+ end
@@ -0,0 +1,2 @@
1
+ class ProductAttribute < ActiveRecord::Base
2
+ end