has_eav 1.0.0

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