acts_as_eav_model 0.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.
- data/.rvmrc +1 -0
- data/CHANGELOG +3 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +92 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +117 -0
- data/Rakefile +46 -0
- data/SPECDOC +23 -0
- data/TODO +0 -0
- data/VERSION +1 -0
- data/doc/classes/ActiveRecord/Acts/EavModel.html +213 -0
- data/doc/classes/ActiveRecord/Acts/EavModel/ClassMethods.html +343 -0
- data/doc/classes/ActiveRecord/Acts/EavModel/InstanceMethods.html +474 -0
- data/doc/classes/ActiveRecord/Acts/EavModel/InstanceMethods/ClassMethods.html +192 -0
- data/doc/classes/ActiveRecord/Base.html +170 -0
- data/doc/created.rid +1 -0
- data/doc/files/CHANGELOG.html +113 -0
- data/doc/files/MIT-LICENSE.html +129 -0
- data/doc/files/README_rdoc.html +248 -0
- data/doc/files/SPECDOC.html +170 -0
- data/doc/files/lib/acts_as_eav_model_rb.html +101 -0
- data/doc/fr_class_index.html +31 -0
- data/doc/fr_file_index.html +31 -0
- data/doc/fr_method_index.html +40 -0
- data/doc/index.html +24 -0
- data/doc/rdoc-style.css +208 -0
- data/init.rb +1 -0
- data/install.rb +1 -0
- data/lib/acts_as_eav_model.rb +542 -0
- data/spec/dummy/Rakefile +7 -0
- data/spec/dummy/app/controllers/application_controller.rb +3 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/models/document.rb +7 -0
- data/spec/dummy/app/models/person.rb +13 -0
- data/spec/dummy/app/models/person_contact_info.rb +2 -0
- data/spec/dummy/app/models/post.rb +4 -0
- data/spec/dummy/app/models/post_attribute.rb +2 -0
- data/spec/dummy/app/models/preference.rb +5 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/config.ru +4 -0
- data/spec/dummy/config/application.rb +45 -0
- data/spec/dummy/config/boot.rb +10 -0
- data/spec/dummy/config/database.yml +22 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +26 -0
- data/spec/dummy/config/environments/production.rb +49 -0
- data/spec/dummy/config/environments/test.rb +35 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/inflections.rb +10 -0
- data/spec/dummy/config/initializers/mime_types.rb +5 -0
- data/spec/dummy/config/initializers/secret_token.rb +7 -0
- data/spec/dummy/config/initializers/session_store.rb +8 -0
- data/spec/dummy/config/locales/en.yml +5 -0
- data/spec/dummy/config/routes.rb +58 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/schema.rb +51 -0
- data/spec/dummy/public/404.html +26 -0
- data/spec/dummy/public/422.html +26 -0
- data/spec/dummy/public/500.html +26 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/public/javascripts/application.js +2 -0
- data/spec/dummy/public/javascripts/controls.js +965 -0
- data/spec/dummy/public/javascripts/dragdrop.js +974 -0
- data/spec/dummy/public/javascripts/effects.js +1123 -0
- data/spec/dummy/public/javascripts/prototype.js +6001 -0
- data/spec/dummy/public/javascripts/rails.js +175 -0
- data/spec/dummy/public/stylesheets/.gitkeep +0 -0
- data/spec/dummy/script/rails +6 -0
- data/spec/fixtures/people.yml +4 -0
- data/spec/fixtures/person_contact_infos.yml +10 -0
- data/spec/fixtures/post_attributes.yml +15 -0
- data/spec/fixtures/posts.yml +9 -0
- data/spec/fixtures/preferences.yml +10 -0
- data/spec/models/eav_model_with_no_arguments_spec.rb +82 -0
- data/spec/models/eav_model_with_options_spec.rb +37 -0
- data/spec/models/eav_validation_spec.rb +11 -0
- data/spec/schema.rb +50 -0
- data/spec/spec_helper.rb +38 -0
- data/uninstall.rb +1 -0
- metadata +213 -0
@@ -0,0 +1,101 @@
|
|
1
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
2
|
+
<!DOCTYPE html
|
3
|
+
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
4
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
5
|
+
|
6
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
7
|
+
<head>
|
8
|
+
<title>File: acts_as_eav_model.rb</title>
|
9
|
+
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
10
|
+
<meta http-equiv="Content-Script-Type" content="text/javascript" />
|
11
|
+
<link rel="stylesheet" href="../.././rdoc-style.css" type="text/css" media="screen" />
|
12
|
+
<script type="text/javascript">
|
13
|
+
// <![CDATA[
|
14
|
+
|
15
|
+
function popupCode( url ) {
|
16
|
+
window.open(url, "Code", "resizable=yes,scrollbars=yes,toolbar=no,status=no,height=150,width=400")
|
17
|
+
}
|
18
|
+
|
19
|
+
function toggleCode( id ) {
|
20
|
+
if ( document.getElementById )
|
21
|
+
elem = document.getElementById( id );
|
22
|
+
else if ( document.all )
|
23
|
+
elem = eval( "document.all." + id );
|
24
|
+
else
|
25
|
+
return false;
|
26
|
+
|
27
|
+
elemStyle = elem.style;
|
28
|
+
|
29
|
+
if ( elemStyle.display != "block" ) {
|
30
|
+
elemStyle.display = "block"
|
31
|
+
} else {
|
32
|
+
elemStyle.display = "none"
|
33
|
+
}
|
34
|
+
|
35
|
+
return true;
|
36
|
+
}
|
37
|
+
|
38
|
+
// Make codeblocks hidden by default
|
39
|
+
document.writeln( "<style type=\"text/css\">div.method-source-code { display: none }</style>" )
|
40
|
+
|
41
|
+
// ]]>
|
42
|
+
</script>
|
43
|
+
|
44
|
+
</head>
|
45
|
+
<body>
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
<div id="fileHeader">
|
50
|
+
<h1>acts_as_eav_model.rb</h1>
|
51
|
+
<table class="header-table">
|
52
|
+
<tr class="top-aligned-row">
|
53
|
+
<td><strong>Path:</strong></td>
|
54
|
+
<td>lib/acts_as_eav_model.rb
|
55
|
+
</td>
|
56
|
+
</tr>
|
57
|
+
<tr class="top-aligned-row">
|
58
|
+
<td><strong>Last Update:</strong></td>
|
59
|
+
<td>Thu Dec 18 13:30:11 +1300 2008</td>
|
60
|
+
</tr>
|
61
|
+
</table>
|
62
|
+
</div>
|
63
|
+
<!-- banner header -->
|
64
|
+
|
65
|
+
<div id="bodyContent">
|
66
|
+
|
67
|
+
|
68
|
+
|
69
|
+
<div id="contextContent">
|
70
|
+
|
71
|
+
|
72
|
+
|
73
|
+
</div>
|
74
|
+
|
75
|
+
|
76
|
+
</div>
|
77
|
+
|
78
|
+
|
79
|
+
<!-- if includes -->
|
80
|
+
|
81
|
+
<div id="section">
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
<!-- if method_list -->
|
91
|
+
|
92
|
+
|
93
|
+
</div>
|
94
|
+
|
95
|
+
|
96
|
+
<div id="validator-badges">
|
97
|
+
<p><small><a href="http://validator.w3.org/check/referer">[Validate]</a></small></p>
|
98
|
+
</div>
|
99
|
+
|
100
|
+
</body>
|
101
|
+
</html>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
3
|
+
<!DOCTYPE html
|
4
|
+
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
5
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
6
|
+
|
7
|
+
<!--
|
8
|
+
|
9
|
+
Classes
|
10
|
+
|
11
|
+
-->
|
12
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
13
|
+
<head>
|
14
|
+
<title>Classes</title>
|
15
|
+
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
16
|
+
<link rel="stylesheet" href="rdoc-style.css" type="text/css" />
|
17
|
+
<base target="docwin" />
|
18
|
+
</head>
|
19
|
+
<body>
|
20
|
+
<div id="index">
|
21
|
+
<h1 class="section-bar">Classes</h1>
|
22
|
+
<div id="index-entries">
|
23
|
+
<a href="classes/ActiveRecord/Acts/EavModel.html">ActiveRecord::Acts::EavModel</a><br />
|
24
|
+
<a href="classes/ActiveRecord/Acts/EavModel/ClassMethods.html">ActiveRecord::Acts::EavModel::ClassMethods</a><br />
|
25
|
+
<a href="classes/ActiveRecord/Acts/EavModel/InstanceMethods.html">ActiveRecord::Acts::EavModel::InstanceMethods</a><br />
|
26
|
+
<a href="classes/ActiveRecord/Acts/EavModel/InstanceMethods/ClassMethods.html">ActiveRecord::Acts::EavModel::InstanceMethods::ClassMethods</a><br />
|
27
|
+
<a href="classes/ActiveRecord/Base.html">ActiveRecord::Base</a><br />
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</body>
|
31
|
+
</html>
|
@@ -0,0 +1,31 @@
|
|
1
|
+
|
2
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
3
|
+
<!DOCTYPE html
|
4
|
+
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
5
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
6
|
+
|
7
|
+
<!--
|
8
|
+
|
9
|
+
Files
|
10
|
+
|
11
|
+
-->
|
12
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
13
|
+
<head>
|
14
|
+
<title>Files</title>
|
15
|
+
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
16
|
+
<link rel="stylesheet" href="rdoc-style.css" type="text/css" />
|
17
|
+
<base target="docwin" />
|
18
|
+
</head>
|
19
|
+
<body>
|
20
|
+
<div id="index">
|
21
|
+
<h1 class="section-bar">Files</h1>
|
22
|
+
<div id="index-entries">
|
23
|
+
<a href="files/CHANGELOG.html">CHANGELOG</a><br />
|
24
|
+
<a href="files/MIT-LICENSE.html">MIT-LICENSE</a><br />
|
25
|
+
<a href="files/README_rdoc.html">README.rdoc</a><br />
|
26
|
+
<a href="files/SPECDOC.html">SPECDOC</a><br />
|
27
|
+
<a href="files/lib/acts_as_eav_model_rb.html">lib/acts_as_eav_model.rb</a><br />
|
28
|
+
</div>
|
29
|
+
</div>
|
30
|
+
</body>
|
31
|
+
</html>
|
@@ -0,0 +1,40 @@
|
|
1
|
+
|
2
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
3
|
+
<!DOCTYPE html
|
4
|
+
PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
|
5
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
|
6
|
+
|
7
|
+
<!--
|
8
|
+
|
9
|
+
Methods
|
10
|
+
|
11
|
+
-->
|
12
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
13
|
+
<head>
|
14
|
+
<title>Methods</title>
|
15
|
+
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
16
|
+
<link rel="stylesheet" href="rdoc-style.css" type="text/css" />
|
17
|
+
<base target="docwin" />
|
18
|
+
</head>
|
19
|
+
<body>
|
20
|
+
<div id="index">
|
21
|
+
<h1 class="section-bar">Methods</h1>
|
22
|
+
<div id="index-entries">
|
23
|
+
<a href="classes/ActiveRecord/Base.html#M000014">attributes= (ActiveRecord::Base)</a><br />
|
24
|
+
<a href="classes/ActiveRecord/Acts/EavModel/InstanceMethods/ClassMethods.html#M000012">create_attribute_table (ActiveRecord::Acts::EavModel::InstanceMethods::ClassMethods)</a><br />
|
25
|
+
<a href="classes/ActiveRecord/Acts/EavModel/InstanceMethods/ClassMethods.html#M000013">drop_attribute_table (ActiveRecord::Acts::EavModel::InstanceMethods::ClassMethods)</a><br />
|
26
|
+
<a href="classes/ActiveRecord/Acts/EavModel/InstanceMethods.html#M000011">each_eav_relation (ActiveRecord::Acts::EavModel::InstanceMethods)</a><br />
|
27
|
+
<a href="classes/ActiveRecord/Acts/EavModel/InstanceMethods.html#M000003">eav_attributes (ActiveRecord::Acts::EavModel::InstanceMethods)</a><br />
|
28
|
+
<a href="classes/ActiveRecord/Acts/EavModel/InstanceMethods.html#M000009">eav_related (ActiveRecord::Acts::EavModel::InstanceMethods)</a><br />
|
29
|
+
<a href="classes/ActiveRecord/Acts/EavModel/InstanceMethods.html#M000010">exec_if_related (ActiveRecord::Acts::EavModel::InstanceMethods)</a><br />
|
30
|
+
<a href="classes/ActiveRecord/Acts/EavModel/InstanceMethods.html#M000008">find_related_eav_attribute (ActiveRecord::Acts::EavModel::InstanceMethods)</a><br />
|
31
|
+
<a href="classes/ActiveRecord/Acts/EavModel/ClassMethods.html#M000001">has_eav_behavior (ActiveRecord::Acts::EavModel::ClassMethods)</a><br />
|
32
|
+
<a href="classes/ActiveRecord/Acts/EavModel/InstanceMethods.html#M000002">is_eav_attribute? (ActiveRecord::Acts::EavModel::InstanceMethods)</a><br />
|
33
|
+
<a href="classes/ActiveRecord/Acts/EavModel/InstanceMethods.html#M000007">method_missing_with_eav_behavior (ActiveRecord::Acts::EavModel::InstanceMethods)</a><br />
|
34
|
+
<a href="classes/ActiveRecord/Acts/EavModel/InstanceMethods.html#M000005">read_attribute_with_eav_behavior (ActiveRecord::Acts::EavModel::InstanceMethods)</a><br />
|
35
|
+
<a href="classes/ActiveRecord/Acts/EavModel/InstanceMethods.html#M000004">save_modified_eav_attributes (ActiveRecord::Acts::EavModel::InstanceMethods)</a><br />
|
36
|
+
<a href="classes/ActiveRecord/Acts/EavModel/InstanceMethods.html#M000006">write_attribute_with_eav_behavior (ActiveRecord::Acts::EavModel::InstanceMethods)</a><br />
|
37
|
+
</div>
|
38
|
+
</div>
|
39
|
+
</body>
|
40
|
+
</html>
|
data/doc/index.html
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
<?xml version="1.0" encoding="iso-8859-1"?>
|
2
|
+
<!DOCTYPE html
|
3
|
+
PUBLIC "-//W3C//DTD XHTML 1.0 Frameset//EN"
|
4
|
+
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-frameset.dtd">
|
5
|
+
|
6
|
+
<!--
|
7
|
+
|
8
|
+
acts_as_eav_model
|
9
|
+
|
10
|
+
-->
|
11
|
+
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
|
12
|
+
<head>
|
13
|
+
<title>acts_as_eav_model</title>
|
14
|
+
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" />
|
15
|
+
</head>
|
16
|
+
<frameset rows="20%, 80%">
|
17
|
+
<frameset cols="25%,35%,45%">
|
18
|
+
<frame src="fr_file_index.html" title="Files" name="Files" />
|
19
|
+
<frame src="fr_class_index.html" name="Classes" />
|
20
|
+
<frame src="fr_method_index.html" name="Methods" />
|
21
|
+
</frameset>
|
22
|
+
<frame src="files/README_rdoc.html" name="docwin" />
|
23
|
+
</frameset>
|
24
|
+
</html>
|
data/doc/rdoc-style.css
ADDED
@@ -0,0 +1,208 @@
|
|
1
|
+
|
2
|
+
body {
|
3
|
+
font-family: Verdana,Arial,Helvetica,sans-serif;
|
4
|
+
font-size: 90%;
|
5
|
+
margin: 0;
|
6
|
+
margin-left: 40px;
|
7
|
+
padding: 0;
|
8
|
+
background: white;
|
9
|
+
}
|
10
|
+
|
11
|
+
h1,h2,h3,h4 { margin: 0; color: #efefef; background: transparent; }
|
12
|
+
h1 { font-size: 150%; }
|
13
|
+
h2,h3,h4 { margin-top: 1em; }
|
14
|
+
|
15
|
+
a { background: #eef; color: #039; text-decoration: none; }
|
16
|
+
a:hover { background: #039; color: #eef; }
|
17
|
+
|
18
|
+
/* Override the base stylesheet's Anchor inside a table cell */
|
19
|
+
td > a {
|
20
|
+
background: transparent;
|
21
|
+
color: #039;
|
22
|
+
text-decoration: none;
|
23
|
+
}
|
24
|
+
|
25
|
+
/* and inside a section title */
|
26
|
+
.section-title > a {
|
27
|
+
background: transparent;
|
28
|
+
color: #eee;
|
29
|
+
text-decoration: none;
|
30
|
+
}
|
31
|
+
|
32
|
+
/* === Structural elements =================================== */
|
33
|
+
|
34
|
+
div#index {
|
35
|
+
margin: 0;
|
36
|
+
margin-left: -40px;
|
37
|
+
padding: 0;
|
38
|
+
font-size: 90%;
|
39
|
+
}
|
40
|
+
|
41
|
+
|
42
|
+
div#index a {
|
43
|
+
margin-left: 0.7em;
|
44
|
+
}
|
45
|
+
|
46
|
+
div#index .section-bar {
|
47
|
+
margin-left: 0px;
|
48
|
+
padding-left: 0.7em;
|
49
|
+
background: #ccc;
|
50
|
+
font-size: small;
|
51
|
+
}
|
52
|
+
|
53
|
+
|
54
|
+
div#classHeader, div#fileHeader {
|
55
|
+
width: auto;
|
56
|
+
color: white;
|
57
|
+
padding: 0.5em 1.5em 0.5em 1.5em;
|
58
|
+
margin: 0;
|
59
|
+
margin-left: -40px;
|
60
|
+
border-bottom: 3px solid #006;
|
61
|
+
}
|
62
|
+
|
63
|
+
div#classHeader a, div#fileHeader a {
|
64
|
+
background: inherit;
|
65
|
+
color: white;
|
66
|
+
}
|
67
|
+
|
68
|
+
div#classHeader td, div#fileHeader td {
|
69
|
+
background: inherit;
|
70
|
+
color: white;
|
71
|
+
}
|
72
|
+
|
73
|
+
|
74
|
+
div#fileHeader {
|
75
|
+
background: #057;
|
76
|
+
}
|
77
|
+
|
78
|
+
div#classHeader {
|
79
|
+
background: #048;
|
80
|
+
}
|
81
|
+
|
82
|
+
|
83
|
+
.class-name-in-header {
|
84
|
+
font-size: 180%;
|
85
|
+
font-weight: bold;
|
86
|
+
}
|
87
|
+
|
88
|
+
|
89
|
+
div#bodyContent {
|
90
|
+
padding: 0 1.5em 0 1.5em;
|
91
|
+
}
|
92
|
+
|
93
|
+
div#description {
|
94
|
+
padding: 0.5em 1.5em;
|
95
|
+
background: #efefef;
|
96
|
+
border: 1px dotted #999;
|
97
|
+
}
|
98
|
+
|
99
|
+
div#description h1,h2,h3,h4,h5,h6 {
|
100
|
+
color: #125;;
|
101
|
+
background: transparent;
|
102
|
+
}
|
103
|
+
|
104
|
+
div#validator-badges {
|
105
|
+
text-align: center;
|
106
|
+
}
|
107
|
+
div#validator-badges img { border: 0; }
|
108
|
+
|
109
|
+
div#copyright {
|
110
|
+
color: #333;
|
111
|
+
background: #efefef;
|
112
|
+
font: 0.75em sans-serif;
|
113
|
+
margin-top: 5em;
|
114
|
+
margin-bottom: 0;
|
115
|
+
padding: 0.5em 2em;
|
116
|
+
}
|
117
|
+
|
118
|
+
|
119
|
+
/* === Classes =================================== */
|
120
|
+
|
121
|
+
table.header-table {
|
122
|
+
color: white;
|
123
|
+
font-size: small;
|
124
|
+
}
|
125
|
+
|
126
|
+
.type-note {
|
127
|
+
font-size: small;
|
128
|
+
color: #DEDEDE;
|
129
|
+
}
|
130
|
+
|
131
|
+
.xxsection-bar {
|
132
|
+
background: #eee;
|
133
|
+
color: #333;
|
134
|
+
padding: 3px;
|
135
|
+
}
|
136
|
+
|
137
|
+
.section-bar {
|
138
|
+
color: #333;
|
139
|
+
border-bottom: 1px solid #999;
|
140
|
+
margin-left: -20px;
|
141
|
+
}
|
142
|
+
|
143
|
+
|
144
|
+
.section-title {
|
145
|
+
background: #79a;
|
146
|
+
color: #eee;
|
147
|
+
padding: 3px;
|
148
|
+
margin-top: 2em;
|
149
|
+
margin-left: -30px;
|
150
|
+
border: 1px solid #999;
|
151
|
+
}
|
152
|
+
|
153
|
+
.top-aligned-row { vertical-align: top }
|
154
|
+
.bottom-aligned-row { vertical-align: bottom }
|
155
|
+
|
156
|
+
/* --- Context section classes ----------------------- */
|
157
|
+
|
158
|
+
.context-row { }
|
159
|
+
.context-item-name { font-family: monospace; font-weight: bold; color: black; }
|
160
|
+
.context-item-value { font-size: small; color: #448; }
|
161
|
+
.context-item-desc { color: #333; padding-left: 2em; }
|
162
|
+
|
163
|
+
/* --- Method classes -------------------------- */
|
164
|
+
.method-detail {
|
165
|
+
background: #efefef;
|
166
|
+
padding: 0;
|
167
|
+
margin-top: 0.5em;
|
168
|
+
margin-bottom: 1em;
|
169
|
+
border: 1px dotted #ccc;
|
170
|
+
}
|
171
|
+
.method-heading {
|
172
|
+
color: black;
|
173
|
+
background: #ccc;
|
174
|
+
border-bottom: 1px solid #666;
|
175
|
+
padding: 0.2em 0.5em 0 0.5em;
|
176
|
+
}
|
177
|
+
.method-signature { color: black; background: inherit; }
|
178
|
+
.method-name { font-weight: bold; }
|
179
|
+
.method-args { font-style: italic; }
|
180
|
+
.method-description { padding: 0 0.5em 0 0.5em; }
|
181
|
+
|
182
|
+
/* --- Source code sections -------------------- */
|
183
|
+
|
184
|
+
a.source-toggle { font-size: 90%; }
|
185
|
+
div.method-source-code {
|
186
|
+
background: #262626;
|
187
|
+
color: #ffdead;
|
188
|
+
margin: 1em;
|
189
|
+
padding: 0.5em;
|
190
|
+
border: 1px dashed #999;
|
191
|
+
overflow: hidden;
|
192
|
+
}
|
193
|
+
|
194
|
+
div.method-source-code pre { color: #ffdead; overflow: hidden; }
|
195
|
+
|
196
|
+
/* --- Ruby keyword styles --------------------- */
|
197
|
+
|
198
|
+
.standalone-code { background: #221111; color: #ffdead; overflow: hidden; }
|
199
|
+
|
200
|
+
.ruby-constant { color: #7fffd4; background: transparent; }
|
201
|
+
.ruby-keyword { color: #00ffff; background: transparent; }
|
202
|
+
.ruby-ivar { color: #eedd82; background: transparent; }
|
203
|
+
.ruby-operator { color: #00ffee; background: transparent; }
|
204
|
+
.ruby-identifier { color: #ffdead; background: transparent; }
|
205
|
+
.ruby-node { color: #ffa07a; background: transparent; }
|
206
|
+
.ruby-comment { color: #b22222; font-weight: bold; background: transparent; }
|
207
|
+
.ruby-regexp { color: #ffa07a; background: transparent; }
|
208
|
+
.ruby-value { color: #7fffd4; background: transparent; }
|
data/init.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require 'acts_as_eav_model'
|
data/install.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
# Install hook code here
|
@@ -0,0 +1,542 @@
|
|
1
|
+
module ActiveRecord # :nodoc:
|
2
|
+
module Acts # :nodoc:
|
3
|
+
##
|
4
|
+
# ActsAsEavModel allow for the Entity-attribute-value model (EAV), also
|
5
|
+
# known as object-attribute-value model and open schema on any of your ActiveRecord
|
6
|
+
# models.
|
7
|
+
#
|
8
|
+
# = What is Entity-attribute-value model?
|
9
|
+
# Entity-attribute-value model (EAV) is a data model that is used in circumstances
|
10
|
+
# where the number of attributes (properties, parameters) that can be used to describe
|
11
|
+
# a thing (an "entity" or "object") is potentially very vast, but the number that will
|
12
|
+
# actually apply to a given entity is relatively modest.
|
13
|
+
#
|
14
|
+
# = Typical Problem
|
15
|
+
# A good example of this is where you need to store
|
16
|
+
# lots (possible hundreds) of optional attributes on an object. My typical
|
17
|
+
# reference example is when you have a User object. You want to store the
|
18
|
+
# user's preferences between sessions. Every search, sort, etc in your
|
19
|
+
# application you want to keep track of so when the user visits that section
|
20
|
+
# of the application again you can simply restore the display to how it was.
|
21
|
+
#
|
22
|
+
# So your controller might have:
|
23
|
+
#
|
24
|
+
# Project.find :all, :conditions => current_user.project_search,
|
25
|
+
# :order => current_user.project_order
|
26
|
+
#
|
27
|
+
# But there could be hundreds of these little attributes that you really don't
|
28
|
+
# want to store directly on the user object. It would make your table have too
|
29
|
+
# many columns so it would be too much of a pain to deal with. Also there might
|
30
|
+
# be performance problems. So instead you might do something like
|
31
|
+
# this:
|
32
|
+
#
|
33
|
+
# class User < ActiveRecord::Base
|
34
|
+
# has_many :preferences
|
35
|
+
# end
|
36
|
+
#
|
37
|
+
# class Preferences < ActiveRecord::Base
|
38
|
+
# belongs_to :user
|
39
|
+
# end
|
40
|
+
#
|
41
|
+
# Now simply give the Preference model a "name" and "value" column and you are
|
42
|
+
# set..... except this is now too complicated. To retrieve a attribute you will
|
43
|
+
# need to do something like:
|
44
|
+
#
|
45
|
+
# Project.find :all,
|
46
|
+
# :conditions => current_user.preferences.find_by_name('project_search').value,
|
47
|
+
# :order => current_user.preferences.find_by_name('project_order').value
|
48
|
+
#
|
49
|
+
# Sure you could fix this through a few methods on your model. But what about
|
50
|
+
# saving?
|
51
|
+
#
|
52
|
+
# current_user.preferences.create :name => 'project_search',
|
53
|
+
# :value => "lastname LIKE 'jones%'"
|
54
|
+
# current_user.preferences.create :name => 'project_order',
|
55
|
+
# :value => "name"
|
56
|
+
#
|
57
|
+
# Again this seems to much. Again we could add some methods to our model to
|
58
|
+
# make this simpler but do we want to do this on every model. NO! So instead
|
59
|
+
# we use this plugin which does everything for us.
|
60
|
+
#
|
61
|
+
# = Capabilities
|
62
|
+
#
|
63
|
+
# The ActsAsEavModel plugin is capable of modeling this problem in a intuitive
|
64
|
+
# way. Instead of having to deal with a related model you treat all attributes
|
65
|
+
# (both on the model and related) as if they are all on the model. The plugin
|
66
|
+
# will try to save all attributes to the model (normal ActiveRecord behaviour)
|
67
|
+
# but if there is no column for an attribute it will try to save it to a
|
68
|
+
# related model whose purpose is to store these many sparsly populated
|
69
|
+
# attributes.
|
70
|
+
#
|
71
|
+
# The main design goals are:
|
72
|
+
#
|
73
|
+
# * Have the eav attributes feel like normal attributes. Simple gets and sets
|
74
|
+
# will add and remove records from the related model.
|
75
|
+
# * Allow for more than one related model. So for example on my User model I might
|
76
|
+
# have some eav behavior going into a contact_info table while others are
|
77
|
+
# going in a user_preferences table.
|
78
|
+
# * Allow a model to determine what a valid eav attribute is for a given
|
79
|
+
# related model so our model still can generate a NoMethodError.
|
80
|
+
#
|
81
|
+
module EavModel
|
82
|
+
|
83
|
+
MAGIC_FIELD_NAMES = [:created_at, :created_on, :updated_at, :updated_on, :created_by, :updated_by,
|
84
|
+
:lock_version, :type, :id, :position, :parent_id, :lft, :rgt, :quote_value, :template, :to_ary,
|
85
|
+
:marshal_dump, :marshal_load, :_dump, :_load, :to_yaml_type, :to_yaml, :yaml_initialize, :to_xml, :to_json, :as_json,
|
86
|
+
:from_json, :from_xml,:validate,:validate_on_create,:validate_on_update].freeze
|
87
|
+
|
88
|
+
def self.included(base) # :nodoc:
|
89
|
+
base.extend ClassMethods
|
90
|
+
end
|
91
|
+
|
92
|
+
module ClassMethods
|
93
|
+
|
94
|
+
##
|
95
|
+
# Will make the current class have eav behaviour.
|
96
|
+
#
|
97
|
+
# class Post < ActiveRecord::Base
|
98
|
+
# has_eav_behavior
|
99
|
+
# end
|
100
|
+
# post = Post.find_by_title 'hello world'
|
101
|
+
# puts "My post intro is: #{post.intro}"
|
102
|
+
# post.teaser = 'An awesome introduction to the blog'
|
103
|
+
# post.save
|
104
|
+
#
|
105
|
+
# The above example should work even though "intro" and "teaser" are not
|
106
|
+
# attributes on the Post model.
|
107
|
+
#
|
108
|
+
# The following options are available on for has_eav_behavior to modify
|
109
|
+
# the behavior. Reasonable defaults are provided:
|
110
|
+
#
|
111
|
+
# * <tt>class_name</tt>:
|
112
|
+
# The class for the related model. This defaults to the
|
113
|
+
# model name prepended to "Attribute". So for a "User" model the class
|
114
|
+
# name would be "UserAttribute". The class can actually exist (in that
|
115
|
+
# case the model file will be loaded through Rails dependency system) or
|
116
|
+
# if it does not exist a basic model will be dynamically defined for you.
|
117
|
+
# This allows you to implement custom methods on the related class by
|
118
|
+
# simply defining the class manually.
|
119
|
+
# * <tt>table_name</tt>:
|
120
|
+
# The table for the related model. This defaults to the
|
121
|
+
# attribute model's table name.
|
122
|
+
# * <tt>relationship_name</tt>:
|
123
|
+
# This is the name of the actual has_many
|
124
|
+
# relationship. Most of the type this relationship will only be used
|
125
|
+
# indirectly but it is there if the user wants more raw access. This
|
126
|
+
# defaults to the class name underscored then pluralized finally turned
|
127
|
+
# into a symbol.
|
128
|
+
# * <tt>foreign_key</tt>:
|
129
|
+
# The key in the attribute table to relate back to the
|
130
|
+
# model. This defaults to the model name underscored prepended to "_id"
|
131
|
+
# * <tt>name_field</tt>:
|
132
|
+
# The field which stores the name of the attribute in the related object
|
133
|
+
# * <tt>value_field</tt>:
|
134
|
+
# The field that stores the value in the related object
|
135
|
+
# * <tt>fields</tt>:
|
136
|
+
# A list of fields that are valid eav attributes. By default
|
137
|
+
# this is "nil" which means that all field are valid. Use this option if
|
138
|
+
# you want some fields to go to one flex attribute model while other
|
139
|
+
# fields will go to another. As an alternative you can override the
|
140
|
+
# #eav_attributes method which will return a list of all valid flex
|
141
|
+
# attributes. This is useful if you want to read the list of attributes
|
142
|
+
# from another source to keep your code DRY. This method is given a
|
143
|
+
# single argument which is the class for the related model. The following
|
144
|
+
# provide an example:
|
145
|
+
#
|
146
|
+
# class User < ActiveRecord::Base
|
147
|
+
# has_eav_behavior :class_name => 'UserContactInfo'
|
148
|
+
# has_eav_behavior :class_name => 'Preferences'
|
149
|
+
#
|
150
|
+
# def eav_attributes(model)
|
151
|
+
# case model
|
152
|
+
# when UserContactInfo
|
153
|
+
# %w(email phone aim yahoo msn)
|
154
|
+
# when Preference
|
155
|
+
# %w(project_search project_order user_search user_order)
|
156
|
+
# else Array.new
|
157
|
+
# end
|
158
|
+
# end
|
159
|
+
# end
|
160
|
+
#
|
161
|
+
# marcus = User.find_by_login 'marcus'
|
162
|
+
# marcus.email = 'marcus@example.com' # Will save to UserContactInfo model
|
163
|
+
# marcus.project_order = 'name' # Will save to Preference
|
164
|
+
# marcus.save # Carries out save so now values are in database
|
165
|
+
#
|
166
|
+
# Note the else clause in our case statement. Since an empty array is
|
167
|
+
# returned for all other models (perhaps added later) then we can be
|
168
|
+
# certain that only the above eav attributes are allowed.
|
169
|
+
#
|
170
|
+
# If both a :fields option and #eav_attributes method is defined the
|
171
|
+
# <tt>fields</tt> option take precidence. This allows you to easily define the
|
172
|
+
# field list inline for one model while implementing #eav_attributes
|
173
|
+
# for another model and not having #eav_attributes need to determine
|
174
|
+
# what model it is answering for. In both cases the list of flex
|
175
|
+
# attributes can be a list of string or symbols
|
176
|
+
#
|
177
|
+
# A final alternative to :fields and #eav_attributes is the
|
178
|
+
# #is_eav_attribute? method. This method is given two arguments. The
|
179
|
+
# first is the attribute being retrieved/saved the second is the Model we
|
180
|
+
# are testing for. If you override this method then the #eav_attributes
|
181
|
+
# method or the :fields option will have no affect. Use of this method
|
182
|
+
# is ideal when you want to retrict the attributes but do so in a
|
183
|
+
# algorithmic way. The following is an example:
|
184
|
+
# class User < ActiveRecord::Base
|
185
|
+
# has_eav_behavior :class_name => 'UserContactInfo'
|
186
|
+
# has_eav_behavior :class_name => 'Preferences'
|
187
|
+
#
|
188
|
+
# def is_eav_attribute?(attr, model)
|
189
|
+
# case attr.to_s
|
190
|
+
# when /^contact_/ then true
|
191
|
+
# when /^preference_/ then true
|
192
|
+
# else
|
193
|
+
# false
|
194
|
+
# end
|
195
|
+
# end
|
196
|
+
# end
|
197
|
+
#
|
198
|
+
# marcus = User.find_by_login 'marcus'
|
199
|
+
# marcus.contact_phone = '021 654 9876'
|
200
|
+
# marcus.contact_email = 'marcus@example.com'
|
201
|
+
# marcus.preference_project_order = 'name'
|
202
|
+
# marcus.some_attribute = 'blah' # If some_attribute is not defined on
|
203
|
+
# # the model then method not found is thrown
|
204
|
+
#
|
205
|
+
def has_eav_behavior(options = {})
|
206
|
+
Rails.logger.debug("HERE OPTIONS=#{options.inspect}")
|
207
|
+
# Provide default options
|
208
|
+
options[:class_name] ||= self.name + 'Attribute'
|
209
|
+
options[:table_name] ||= options[:class_name].tableize
|
210
|
+
options[:relationship_name] ||= options[:class_name].tableize.to_sym
|
211
|
+
options[:foreign_key] ||= "#{self.table_name.singularize.downcase}_id" #self.name.foreign_key
|
212
|
+
options[:base_foreign_key] ||= self.name.underscore.foreign_key
|
213
|
+
options[:name_field] ||= 'name'
|
214
|
+
options[:value_field] ||= 'value'
|
215
|
+
options[:fields].collect! {|f| f.to_s} unless options[:fields].nil?
|
216
|
+
options[:parent] = self.name
|
217
|
+
|
218
|
+
# Init option storage if necessary
|
219
|
+
cattr_accessor :eav_options
|
220
|
+
self.eav_options ||= Hash.new
|
221
|
+
|
222
|
+
# Return if already processed.
|
223
|
+
return if self.eav_options.keys.include? options[:class_name]
|
224
|
+
|
225
|
+
# Attempt to load related class. If not create it
|
226
|
+
begin
|
227
|
+
options[:class_name].constantize
|
228
|
+
rescue
|
229
|
+
Object.const_set(options[:class_name],
|
230
|
+
Class.new(ActiveRecord::Base)).class_eval do
|
231
|
+
def self.reloadable? #:nodoc:
|
232
|
+
false
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Store options
|
238
|
+
self.eav_options[options[:class_name]] = options
|
239
|
+
|
240
|
+
# Only mix instance methods once
|
241
|
+
unless self.included_modules.include?(ActiveRecord::Acts::EavModel::InstanceMethods)
|
242
|
+
send :include, ActiveRecord::Acts::EavModel::InstanceMethods
|
243
|
+
end
|
244
|
+
|
245
|
+
# Modify attribute class
|
246
|
+
attribute_class = options[:class_name].constantize
|
247
|
+
base_class = self.name.underscore.to_sym
|
248
|
+
|
249
|
+
attribute_class.class_eval do
|
250
|
+
belongs_to base_class, :foreign_key => options[:base_foreign_key]
|
251
|
+
alias_method :base, base_class # For generic access
|
252
|
+
end
|
253
|
+
|
254
|
+
# Modify main class
|
255
|
+
class_eval do
|
256
|
+
has_many options[:relationship_name],
|
257
|
+
:class_name => options[:class_name],
|
258
|
+
:table_name => options[:table_name],
|
259
|
+
:foreign_key => options[:foreign_key],
|
260
|
+
:dependent => :destroy
|
261
|
+
|
262
|
+
# The following is only setup once
|
263
|
+
unless method_defined? :method_missing_without_eav_behavior
|
264
|
+
|
265
|
+
# Carry out delayed actions before save
|
266
|
+
after_validation :save_modified_eav_attributes,:on=>:update
|
267
|
+
|
268
|
+
# Make attributes seem real
|
269
|
+
alias_method_chain :respond_to?, :eav_behavior
|
270
|
+
alias_method_chain :method_missing, :eav_behavior
|
271
|
+
|
272
|
+
private
|
273
|
+
|
274
|
+
alias_method_chain :read_attribute, :eav_behavior
|
275
|
+
alias_method_chain :write_attribute, :eav_behavior
|
276
|
+
|
277
|
+
end
|
278
|
+
end
|
279
|
+
|
280
|
+
create_attribute_table
|
281
|
+
|
282
|
+
end
|
283
|
+
|
284
|
+
end
|
285
|
+
|
286
|
+
module InstanceMethods
|
287
|
+
|
288
|
+
def self.included(base) # :nodoc:
|
289
|
+
base.extend ClassMethods
|
290
|
+
end
|
291
|
+
|
292
|
+
module ClassMethods
|
293
|
+
|
294
|
+
##
|
295
|
+
# Rake migration task to create the versioned table using options passed to has_eav_behavior
|
296
|
+
#
|
297
|
+
def create_attribute_table(options = {})
|
298
|
+
eav_options.keys.each do |key|
|
299
|
+
continue if eav_options[key][:parent] != self.name
|
300
|
+
model = eav_options[key][:class_name]
|
301
|
+
|
302
|
+
return if connection.tables.include?(eav_options[model][:table_name])
|
303
|
+
|
304
|
+
self.connection.create_table(eav_options[model][:table_name], options) do |t|
|
305
|
+
t.integer eav_options[model][:foreign_key], :null => false
|
306
|
+
t.string eav_options[model][:name_field], :null => false
|
307
|
+
t.string eav_options[model][:value_field], :null => false
|
308
|
+
|
309
|
+
t.timestamps
|
310
|
+
end
|
311
|
+
|
312
|
+
self.connection.add_index eav_options[model][:table_name], eav_options[model][:foreign_key]
|
313
|
+
end
|
314
|
+
|
315
|
+
end
|
316
|
+
|
317
|
+
##
|
318
|
+
# Rake migration task to drop the attribute table
|
319
|
+
#
|
320
|
+
def drop_attribute_table(options = {})
|
321
|
+
eav_options.keys.each do |key|
|
322
|
+
continue if eav_options[key][:parent] != self.name
|
323
|
+
model = eav_options[key][:class_name]
|
324
|
+
self.connection.drop_table eav_options[model][:table_name]
|
325
|
+
end
|
326
|
+
|
327
|
+
end
|
328
|
+
|
329
|
+
end
|
330
|
+
|
331
|
+
##
|
332
|
+
# Will determine if the given attribute is a eav attribute on the
|
333
|
+
# given model. Override this in your class to provide custom logic if
|
334
|
+
# the #eav_attributes method or the :fields option are not flexible
|
335
|
+
# enough. If you override this method :fields and #eav_attributes will
|
336
|
+
# not apply at all unless you implement them yourself.
|
337
|
+
#
|
338
|
+
def is_eav_attribute?(attribute_name, model)
|
339
|
+
attribute_name = attribute_name.to_s
|
340
|
+
model_options = eav_options[model.name]
|
341
|
+
return model_options[:fields].include?(attribute_name) unless model_options[:fields].nil?
|
342
|
+
return eav_attributes(model).collect {|field| field.to_s}.include?(attribute_name) unless
|
343
|
+
eav_attributes(model).nil?
|
344
|
+
true
|
345
|
+
end
|
346
|
+
|
347
|
+
##
|
348
|
+
# Return a list of valid eav attributes for the given model. Return
|
349
|
+
# nil if any field is allowed. If you want to say no field is allowed
|
350
|
+
# then return an empty array. If you just have a static list the :fields
|
351
|
+
# option is most likely easier.
|
352
|
+
#
|
353
|
+
def eav_attributes(model); nil end
|
354
|
+
|
355
|
+
##
|
356
|
+
# CLK added a respond_to? implementation so that ActiveRecord AssociationProxy (polymorphic relationships)
|
357
|
+
# does not mask the method_missing implementation here. See:
|
358
|
+
# https://rails.lighthouseapp.com/projects/8994/tickets/2378-associationproxymethod_missing-masks-method_missing-in-models
|
359
|
+
# Updated when certain magic fields (like timestamp columns) were interfering with saves, etc.
|
360
|
+
# http://oldwiki.rubyonrails.org/rails/pages/MagicFieldNames
|
361
|
+
#
|
362
|
+
def respond_to_with_eav_behavior?(method_id, include_private = false)
|
363
|
+
if MAGIC_FIELD_NAMES.include?(method_id.to_sym)
|
364
|
+
respond_to_without_eav_behavior?(method_id, include_private)
|
365
|
+
else
|
366
|
+
true
|
367
|
+
end
|
368
|
+
end
|
369
|
+
|
370
|
+
private
|
371
|
+
|
372
|
+
##
|
373
|
+
# Called after validation on update so that eav attributes behave
|
374
|
+
# like normal attributes in the fact that the database is not touched
|
375
|
+
# until save is called.
|
376
|
+
#
|
377
|
+
def save_modified_eav_attributes
|
378
|
+
return if @save_eav_attr.nil?
|
379
|
+
@save_eav_attr.each do |s|
|
380
|
+
model, attribute_name = s
|
381
|
+
related_attribute = find_related_eav_attribute(model, attribute_name)
|
382
|
+
unless related_attribute.nil?
|
383
|
+
if related_attribute.value.nil?
|
384
|
+
eav_related(model).delete(related_attribute)
|
385
|
+
else
|
386
|
+
related_attribute.save
|
387
|
+
end
|
388
|
+
end
|
389
|
+
end
|
390
|
+
@save_eav_attr = []
|
391
|
+
end
|
392
|
+
|
393
|
+
##
|
394
|
+
# Overrides ActiveRecord::Base#read_attribute
|
395
|
+
#
|
396
|
+
def read_attribute_with_eav_behavior(attribute_name)
|
397
|
+
attribute_name = attribute_name.to_s
|
398
|
+
exec_if_related(attribute_name) do |model|
|
399
|
+
return nil if !@remove_eav_attr.nil? && @remove_eav_attr.any? do |r|
|
400
|
+
r[0] == model && r[1] == attribute_name
|
401
|
+
end
|
402
|
+
|
403
|
+
value_field = eav_options[model.name][:value_field]
|
404
|
+
related_attribute = find_related_eav_attribute(model, attribute_name)
|
405
|
+
|
406
|
+
return nil if related_attribute.nil?
|
407
|
+
return related_attribute.send(value_field)
|
408
|
+
end
|
409
|
+
read_attribute_without_eav_behavior(attribute_name)
|
410
|
+
end
|
411
|
+
|
412
|
+
##
|
413
|
+
# Overrides ActiveRecord::Base#write_attribute
|
414
|
+
#
|
415
|
+
def write_attribute_with_eav_behavior(attribute_name, value)
|
416
|
+
attribute_name = attribute_name.to_s
|
417
|
+
exec_if_related(attribute_name) do |model|
|
418
|
+
value_field = eav_options[model.name][:value_field]
|
419
|
+
@save_eav_attr ||= []
|
420
|
+
@save_eav_attr << [model, attribute_name]
|
421
|
+
related_attribute = find_related_eav_attribute(model, attribute_name)
|
422
|
+
if related_attribute.nil?
|
423
|
+
# Used to check for nil? but this caused validation
|
424
|
+
# problems that are harder to solve. blank? is probably
|
425
|
+
# not correct but it works well for now.
|
426
|
+
unless value.blank?
|
427
|
+
name_field = eav_options[model.name][:name_field]
|
428
|
+
foreign_key = eav_options[model.name][:foreign_key]
|
429
|
+
eav_related(model).build name_field => attribute_name,
|
430
|
+
value_field => value, foreign_key => self.id
|
431
|
+
end
|
432
|
+
return value
|
433
|
+
else
|
434
|
+
value_field = (value_field.to_s + '=').to_sym
|
435
|
+
return related_attribute.send(value_field, value)
|
436
|
+
end
|
437
|
+
end
|
438
|
+
write_attribute_without_eav_behavior(attribute_name, value)
|
439
|
+
end
|
440
|
+
|
441
|
+
##
|
442
|
+
# Custom method added by ckraybill!
|
443
|
+
#
|
444
|
+
def attribute_empty_with_eav_behavior(attribute_name)
|
445
|
+
!read_attribute_with_eav_behavior(attribute_name).blank?
|
446
|
+
end
|
447
|
+
|
448
|
+
##
|
449
|
+
# Implements eav-attributes as if real getter/setter methods
|
450
|
+
# were defined.
|
451
|
+
# Method modified to support '?' behavior
|
452
|
+
def method_missing_with_eav_behavior(method_id, *args, &block)
|
453
|
+
begin
|
454
|
+
method_missing_without_eav_behavior(method_id, *args, &block)
|
455
|
+
rescue NoMethodError => e
|
456
|
+
m = method_id.to_s
|
457
|
+
attribute_name = m.sub(/\=|\?$/, '')
|
458
|
+
exec_if_related(attribute_name) do |model|
|
459
|
+
if m =~ /\=$/
|
460
|
+
return write_attribute_with_eav_behavior(attribute_name, args[0])
|
461
|
+
elsif m =~ /\?$/
|
462
|
+
return attribute_empty_with_eav_behavior(attribute_name)
|
463
|
+
else
|
464
|
+
return read_attribute_with_eav_behavior(attribute_name)
|
465
|
+
end
|
466
|
+
end
|
467
|
+
raise e
|
468
|
+
end
|
469
|
+
end
|
470
|
+
|
471
|
+
##
|
472
|
+
# Retrieve the related eav attribute object
|
473
|
+
#
|
474
|
+
def find_related_eav_attribute(model, attribute_name)
|
475
|
+
name_field = eav_options[model.name][:name_field]
|
476
|
+
eav_related(model).to_a.find do |relation|
|
477
|
+
relation.send(name_field) == attribute_name
|
478
|
+
end
|
479
|
+
end
|
480
|
+
|
481
|
+
##
|
482
|
+
# Retrieve the collection of related eav attributes
|
483
|
+
#
|
484
|
+
def eav_related(model)
|
485
|
+
relationship = eav_options[model.name][:relationship_name]
|
486
|
+
send relationship
|
487
|
+
end
|
488
|
+
|
489
|
+
##
|
490
|
+
# yield only if attribute_name is a eav_attribute
|
491
|
+
#
|
492
|
+
def exec_if_related(attribute_name)
|
493
|
+
return false if self.class.column_names.include?(attribute_name) || MAGIC_FIELD_NAMES.include?(attribute_name.to_sym)
|
494
|
+
each_eav_relation do |model|
|
495
|
+
if is_eav_attribute?(attribute_name, model)
|
496
|
+
yield model
|
497
|
+
end
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
501
|
+
##
|
502
|
+
# yields for each eav relation.
|
503
|
+
#
|
504
|
+
def each_eav_relation
|
505
|
+
eav_options.keys.each {|kls| yield kls.constantize}
|
506
|
+
end
|
507
|
+
|
508
|
+
end
|
509
|
+
|
510
|
+
end
|
511
|
+
end
|
512
|
+
|
513
|
+
class Base
|
514
|
+
|
515
|
+
##
|
516
|
+
# Overrides ActiveRecord::Base#attributes=
|
517
|
+
#
|
518
|
+
# Because in version >=2.2.2 of ActiveRecord the behaviour in the attributes
|
519
|
+
# have been changed to throw a NoMethodError if the AR object don't respond
|
520
|
+
# to an attribute setter, we could not use method missing to allow for our
|
521
|
+
# entity-attribute-value behaviour.
|
522
|
+
#
|
523
|
+
def attributes=(new_attributes, guard_protected_attributes = true)
|
524
|
+
return if new_attributes.nil?
|
525
|
+
attributes = new_attributes.dup
|
526
|
+
attributes.stringify_keys!
|
527
|
+
multi_parameter_attributes = []
|
528
|
+
attributes = sanitize_for_mass_assignment(attributes) if guard_protected_attributes
|
529
|
+
attributes.each do |k, v|
|
530
|
+
if k.include?("(")
|
531
|
+
multi_parameter_attributes << [ k, v ]
|
532
|
+
else
|
533
|
+
send(:"#{k}=", v)
|
534
|
+
end
|
535
|
+
end
|
536
|
+
|
537
|
+
assign_multiparameter_attributes(multi_parameter_attributes)
|
538
|
+
end
|
539
|
+
end
|
540
|
+
end
|
541
|
+
|
542
|
+
ActiveRecord::Base.send :include, ActiveRecord::Acts::EavModel
|