pjb3-flex-attributes 0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/MIT-LICENSE +20 -0
- data/Manifest +5 -0
- data/README +25 -0
- data/flex-attributes.gemspec +29 -0
- data/lib/flex-attributes.rb +475 -0
- metadata +64 -0
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2005 Eric Anderson
|
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/Manifest
ADDED
data/README
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
= Flex Attributes
|
2
|
+
|
3
|
+
See ActiveRecord::FlexAttributes for usage information.
|
4
|
+
|
5
|
+
= Installation
|
6
|
+
|
7
|
+
Edit config/environment.rb:
|
8
|
+
|
9
|
+
config.gem 'pjb3-flex-attributes', :version => '~> 0.1', :lib => 'flex-attributes', :source => 'http://gems.github.com'
|
10
|
+
|
11
|
+
= RUNNING UNIT TESTS
|
12
|
+
|
13
|
+
== Creating the test database
|
14
|
+
|
15
|
+
The test databases will be created from the info specified in test/database.yml.
|
16
|
+
Either change that file to match your database or change your database to
|
17
|
+
match that file.
|
18
|
+
|
19
|
+
== Running with Rake
|
20
|
+
|
21
|
+
The easiest way to run the unit tests is through Rake. By default sqlite3
|
22
|
+
will be the database run. Just change your env variable DB to be the database
|
23
|
+
adaptor (specified in database.yml) that you want to use. The database and
|
24
|
+
permissions must already be setup but the tables will be created for you
|
25
|
+
from schema.rb.
|
@@ -0,0 +1,29 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = %q{flex-attributes}
|
3
|
+
s.version = "0.1"
|
4
|
+
|
5
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
6
|
+
s.authors = ["Eric Anderson", "Paul Barry"]
|
7
|
+
s.date = %q{2008-08-26}
|
8
|
+
s.description = %q{Flex attributes allow for the common but questionable database design of storing attributes in a thin key/value table related to some model.}
|
9
|
+
s.email = %q{erci (at) afaik (dot) us}
|
10
|
+
s.extra_rdoc_files = ["lib/flex-attributes.rb", "README"]
|
11
|
+
s.files = ["flex-attributes.gemspec", "lib/flex-attributes.rb", "Manifest", "README", "MIT-LICENSE"]
|
12
|
+
s.has_rdoc = true
|
13
|
+
s.homepage = %q{http://github.com/pjb3/flex-attributes}
|
14
|
+
s.rdoc_options = ["--line-numbers", "--inline-source", "--title", "Flex-Attributes", "--main", "README"]
|
15
|
+
s.require_paths = ["lib"]
|
16
|
+
s.rubyforge_project = %q{flex-attributes}
|
17
|
+
s.rubygems_version = %q{1.2.0}
|
18
|
+
s.summary = %q{Gem version of flex-attributes Rails plugin.}
|
19
|
+
|
20
|
+
if s.respond_to? :specification_version then
|
21
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
22
|
+
s.specification_version = 2
|
23
|
+
|
24
|
+
if current_version >= 3 then
|
25
|
+
else
|
26
|
+
end
|
27
|
+
else
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,475 @@
|
|
1
|
+
module FlexAttributes
|
2
|
+
|
3
|
+
def self.included(base) # :nodoc:
|
4
|
+
base.extend ClassMethods
|
5
|
+
end
|
6
|
+
|
7
|
+
# Flex attributes allow for the common but questionable database design of
|
8
|
+
# storing attributes in a thin key/value table related to some model.
|
9
|
+
#
|
10
|
+
# = Rational
|
11
|
+
#
|
12
|
+
# A good example of this is where you need to store
|
13
|
+
# lots (possible hundreds) of optional attributes on an object. My typical
|
14
|
+
# reference example is when you have a User object. You want to store the
|
15
|
+
# user's preferences between sessions. Every search, sort, etc in your
|
16
|
+
# application you want to keep track of so when the user visits that section
|
17
|
+
# of the application again you can simply restore the display to how it was.
|
18
|
+
#
|
19
|
+
# So your controller might have:
|
20
|
+
#
|
21
|
+
# Project.find :all, :conditions => current_user.project_search,
|
22
|
+
# :order => current_user.project_order
|
23
|
+
#
|
24
|
+
# But there could be hundreds of these little attributes that you really don't
|
25
|
+
# want to store directly on the user object. It would make your table have too
|
26
|
+
# many columns so it would be too much of a pain to deal with. Also there might
|
27
|
+
# be performance problems. So instead you might do something like
|
28
|
+
# this:
|
29
|
+
#
|
30
|
+
# class User < ActiveRecord::Base
|
31
|
+
# has_many :preferences
|
32
|
+
# end
|
33
|
+
#
|
34
|
+
# class Preferences < ActiveRecord::Base
|
35
|
+
# belongs_to :user
|
36
|
+
# end
|
37
|
+
#
|
38
|
+
# Now simply give the Preference model a "name" and "value" column and you are
|
39
|
+
# set..... except this is now too complicated. To retrieve a attribute you will
|
40
|
+
# need to do something like:
|
41
|
+
#
|
42
|
+
# Project.find :all,
|
43
|
+
# :conditions => current_user.preferences.find_by_name('project_search').value,
|
44
|
+
# :order => current_user.preferences.find_by_name('project_order').value
|
45
|
+
#
|
46
|
+
# Sure you could fix this through a few methods on your model. But what about
|
47
|
+
# saving?
|
48
|
+
#
|
49
|
+
# current_user.preferences.create :name => 'project_search',
|
50
|
+
# :value => "lastname LIKE 'jones%'"
|
51
|
+
# current_user.preferences.create :name => 'project_order',
|
52
|
+
# :value => "name"
|
53
|
+
#
|
54
|
+
# Again this seems to much. Again we could add some methods to our model to
|
55
|
+
# make this simpler but do we want to do this on every model. NO! So instead
|
56
|
+
# we use this plugin which does everything for us.
|
57
|
+
#
|
58
|
+
# = Capabilities
|
59
|
+
#
|
60
|
+
# The FlexAttributes plugin is capable of modeling this problem in a intuitive
|
61
|
+
# way. Instead of having to deal with a related model you treat all attributes
|
62
|
+
# (both on the model and related) as if they are all on the model. The plugin
|
63
|
+
# will try to save all attributes to the model (normal ActiveRecord behaviour)
|
64
|
+
# but if there is no column for an attribute it will try to save it to a
|
65
|
+
# related model whose purpose is to store these many sparsly populated
|
66
|
+
# attributes.
|
67
|
+
#
|
68
|
+
# The main design goals are:
|
69
|
+
#
|
70
|
+
# * Have the flex attributes feel like normal attributes. Simple gets and sets
|
71
|
+
# will add and remove records from the related model.
|
72
|
+
# * Allow for more than one related model. So for example on my User model I might
|
73
|
+
# have some flex attributes going into a contact_info table while others are
|
74
|
+
# going in a user_preferences table.
|
75
|
+
# * Allow a model to determine what a valid flex attribute is for a given
|
76
|
+
# related model so our model still can generate a NoMethodError.
|
77
|
+
# * Have flex attributes work with ActsAsVersioned. We want the versioned
|
78
|
+
# models to be able to be related with the correct attributes. If a flex
|
79
|
+
# attribute changes it should generate a new version of the model and the old
|
80
|
+
# version should still have the old value for the flex attribute.
|
81
|
+
|
82
|
+
|
83
|
+
module ClassMethods
|
84
|
+
|
85
|
+
# Will make the current class have flex attributes.
|
86
|
+
#
|
87
|
+
# class User < ActiveRecord::Base
|
88
|
+
# has_flex_attributes
|
89
|
+
# end
|
90
|
+
# eric = User.find_by_login 'eric'
|
91
|
+
# puts "My AOL instant message name is: #{eric.aim}"
|
92
|
+
# eric.phone = '555-123-4567'
|
93
|
+
# eric.save
|
94
|
+
#
|
95
|
+
# The above example should work even though "aim" and "phone" are not
|
96
|
+
# attributes on the User model.
|
97
|
+
#
|
98
|
+
# The following options are available on for has_flex_attributes to modify
|
99
|
+
# the behavior. Reasonable defaults are provided:
|
100
|
+
#
|
101
|
+
# class_name::
|
102
|
+
# The class for the related model. This defaults to the
|
103
|
+
# model name prepended to "Attribute". So for a "User" model the class
|
104
|
+
# name would be "UserAttribute". The class can actually exist (in that
|
105
|
+
# case the model file will be loaded through Rails dependency system) or
|
106
|
+
# if it does not exist a basic model will be dynamically defined for you.
|
107
|
+
# This allows you to implement custom methods on the related class by
|
108
|
+
# simply defining the class manually.
|
109
|
+
# table_name::
|
110
|
+
# The table for the related model. This defaults to the
|
111
|
+
# attribute model's table name.
|
112
|
+
# relationship_name::
|
113
|
+
# This is the name of the actual has_many
|
114
|
+
# relationship. Most of the type this relationship will only be used
|
115
|
+
# indirectly but it is there if the user wants more raw access. This
|
116
|
+
# defaults to the class name underscored then pluralized finally turned
|
117
|
+
# into a symbol.
|
118
|
+
# foreign_key::
|
119
|
+
# The key in the attribute table to relate back to the
|
120
|
+
# model. This defaults to the model name underscored prepended to "_id"
|
121
|
+
# name_field::
|
122
|
+
# The field which stores the name of the attribute in the related object
|
123
|
+
# value_field::
|
124
|
+
# The field that stores the value in the related object
|
125
|
+
# fields::
|
126
|
+
# A list of fields that are valid flex attributes. By default
|
127
|
+
# this is "nil" which means that all field are valid. Use this option if
|
128
|
+
# you want some fields to go to one flex attribute model while other
|
129
|
+
# fields will go to another. As an alternative you can override the
|
130
|
+
# #flex_attributes method which will return a list of all valid flex
|
131
|
+
# attributes. This is useful if you want to read the list of attributes
|
132
|
+
# from another source to keep your code DRY. This method is given a
|
133
|
+
# single argument which is the class for the related model. The following
|
134
|
+
# provide an example:
|
135
|
+
#
|
136
|
+
# class User < ActiveRecord::Base
|
137
|
+
# has_flex_attributes :class_name => 'UserContactInfo'
|
138
|
+
# has_flex_attributes :class_name => 'Preferences'
|
139
|
+
#
|
140
|
+
# def flex_attributes(model)
|
141
|
+
# case model
|
142
|
+
# when UserContactInfo
|
143
|
+
# %w(email phone aim yahoo msn)
|
144
|
+
# when Preference
|
145
|
+
# %w(project_search project_order user_search user_order)
|
146
|
+
# else Array.new
|
147
|
+
# end
|
148
|
+
# end
|
149
|
+
# end
|
150
|
+
#
|
151
|
+
# eric = User.find_by_login 'eric'
|
152
|
+
# eric.email = 'eric@example.com' # Will save to UserContactInfo model
|
153
|
+
# eric.project_order = 'name' # Will save to Preference
|
154
|
+
# eric.save # Carries out save so now values are in database
|
155
|
+
#
|
156
|
+
# Note the else clause in our case statement. Since an empty array is
|
157
|
+
# returned for all other models (perhaps added later) then we can be
|
158
|
+
# certain that only the above flex attributes are allowed.
|
159
|
+
#
|
160
|
+
# If both a :fields option and #flex_attributes method is defined the
|
161
|
+
# :fields option take precidence. This allows you to easily define the
|
162
|
+
# field list inline for one model while implementing #flex_attributes
|
163
|
+
# for another model and not having #flex_attributes need to determine
|
164
|
+
# what model it is answering for. In both cases the list of flex
|
165
|
+
# attributes can be a list of string or symbols
|
166
|
+
#
|
167
|
+
# A final alternative to :fields and #flex_attributes is the
|
168
|
+
# #is_flex_attribute? method. This method is given two arguments. The
|
169
|
+
# first is the attribute being retrieved/saved the second is the Model we
|
170
|
+
# are testing for. If you override this method then the #flex_attributes
|
171
|
+
# method or the :fields option will have no affect. Use of this method
|
172
|
+
# is ideal when you want to retrict the attributes but do so in a
|
173
|
+
# algorithmic way. The following is an example:
|
174
|
+
# class User < ActiveRecord::Base
|
175
|
+
# has_flex_attributes :class_name => 'UserContactInfo'
|
176
|
+
# has_flex_attributes :class_name => 'Preferences'
|
177
|
+
#
|
178
|
+
# def is_flex_attribute?(attr, model)
|
179
|
+
# case attr.to_s
|
180
|
+
# when /^contact_/ then true
|
181
|
+
# when /^preference_/ then true
|
182
|
+
# else
|
183
|
+
# false
|
184
|
+
# end
|
185
|
+
# end
|
186
|
+
# end
|
187
|
+
#
|
188
|
+
# eric = User.find_by_login 'eric'
|
189
|
+
# eric.contact_phone = '555-123-4567'
|
190
|
+
# eric.contact_email = 'eric@example.com'
|
191
|
+
# eric.preference_project_order = 'name'
|
192
|
+
# eric.some_attribute = 'blah' # If some_attribute is not defined on
|
193
|
+
# # the model then method not found is thrown
|
194
|
+
def has_flex_attributes(options={})
|
195
|
+
|
196
|
+
# Provide default options
|
197
|
+
options[:class_name] ||= self.class_name + 'Attribute'
|
198
|
+
options[:table_name] ||= options[:class_name].tableize
|
199
|
+
options[:relationship_name] ||= options[:class_name].tableize.to_sym
|
200
|
+
options[:foreign_key] ||= self.class_name.foreign_key
|
201
|
+
options[:base_foreign_key] ||= self.name.underscore.foreign_key
|
202
|
+
options[:name_field] ||= 'name'
|
203
|
+
options[:value_field] ||= 'value'
|
204
|
+
options[:fields].collect! {|f| f.to_s} unless options[:fields].nil?
|
205
|
+
options[:versioned] = options.has_key?(:versioned) ?
|
206
|
+
options[:versioned] : false
|
207
|
+
|
208
|
+
# Init option storage if necessary
|
209
|
+
cattr_accessor :flex_options
|
210
|
+
self.flex_options ||= Hash.new
|
211
|
+
|
212
|
+
# Return if already processed.
|
213
|
+
return if self.flex_options.keys.include? options[:class_name]
|
214
|
+
|
215
|
+
# Attempt to load related class. If not create it
|
216
|
+
begin
|
217
|
+
options[:class_name].constantize
|
218
|
+
rescue
|
219
|
+
Object.const_set(options[:class_name],
|
220
|
+
Class.new(ActiveRecord::Base)).class_eval do
|
221
|
+
def self.reloadable? #:nodoc:
|
222
|
+
false
|
223
|
+
end
|
224
|
+
end
|
225
|
+
end
|
226
|
+
|
227
|
+
# Store options
|
228
|
+
self.flex_options[options[:class_name]] = options
|
229
|
+
|
230
|
+
# Mix in instance methods
|
231
|
+
send :include, ActiveRecord::FlexAttributes::InstanceMethods
|
232
|
+
|
233
|
+
# Modify attribute class
|
234
|
+
attribute_class = options[:class_name].constantize
|
235
|
+
base_class = self.name.underscore.to_sym
|
236
|
+
attribute_class.class_eval do
|
237
|
+
belongs_to base_class, :foreign_key => options[:base_foreign_key]
|
238
|
+
alias_method :base, base_class # For generic access
|
239
|
+
if options[:versioned]
|
240
|
+
begin
|
241
|
+
version_column = column_names.include?('lock_version') ?
|
242
|
+
'lock_version' : 'version'
|
243
|
+
def version # :nodoc:
|
244
|
+
lock_version
|
245
|
+
end if version_column == 'lock_version'
|
246
|
+
rescue
|
247
|
+
version_column = 'version'
|
248
|
+
end
|
249
|
+
acts_as_versioned :version_column => version_column
|
250
|
+
acts_as_versioned_association base_class, :both_sides => true
|
251
|
+
def version_condition_met? # :nodoc:
|
252
|
+
base.version_condition_met?
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
# Modify main class
|
258
|
+
class_eval do
|
259
|
+
has_many options[:relationship_name],
|
260
|
+
:class_name => options[:class_name],
|
261
|
+
:table_name => options[:table_name],
|
262
|
+
:foreign_key => options[:foreign_key],
|
263
|
+
:dependent => :destroy
|
264
|
+
|
265
|
+
if options[:versioned]
|
266
|
+
begin
|
267
|
+
version_column = column_names.include?('lock_version') ?
|
268
|
+
'lock_version' : 'version'
|
269
|
+
def version # :nodoc:
|
270
|
+
lock_version
|
271
|
+
end if version_column == 'lock_version'
|
272
|
+
rescue
|
273
|
+
version_column = 'version'
|
274
|
+
end
|
275
|
+
acts_as_versioned :version_column => version_column
|
276
|
+
acts_as_versioned_association options[:relationship_name],
|
277
|
+
:both_sides => true
|
278
|
+
version_class.send :include,
|
279
|
+
ActiveRecord::FlexAttributes::InstanceMethods
|
280
|
+
end
|
281
|
+
|
282
|
+
# The following is only setup once
|
283
|
+
unless private_method_defined? :method_missing_without_flex_attributes
|
284
|
+
|
285
|
+
# Carry out delayed actions before save
|
286
|
+
after_validation_on_update :save_modified_flex_attributes
|
287
|
+
|
288
|
+
# Make attributes seem real
|
289
|
+
alias_method :method_missing_without_flex_attributes, :method_missing
|
290
|
+
alias_method :method_missing, :method_missing_with_flex_attributes
|
291
|
+
|
292
|
+
if options[:versioned]
|
293
|
+
version_class_alias_method :method_missing_without_flex_attributes, :method_missing
|
294
|
+
version_class_alias_method :method_missing, :method_missing_with_flex_attributes
|
295
|
+
end
|
296
|
+
|
297
|
+
private
|
298
|
+
|
299
|
+
alias_method :read_attribute_without_flex_attributes, :read_attribute
|
300
|
+
alias_method :read_attribute, :read_attribute_with_flex_attributes
|
301
|
+
alias_method :write_attribute_without_flex_attributes, :write_attribute
|
302
|
+
alias_method :write_attribute, :write_attribute_with_flex_attributes
|
303
|
+
|
304
|
+
if options[:versioned]
|
305
|
+
version_class_alias_method :read_attribute_without_flex_attributes, :read_attribute
|
306
|
+
version_class_alias_method :read_attribute, :read_attribute_with_flex_attributes
|
307
|
+
version_class_alias_method :write_attribute_without_flex_attributes, :write_attribute
|
308
|
+
version_class_alias_method :write_attribute, :write_attribute_with_flex_attributes
|
309
|
+
end
|
310
|
+
end
|
311
|
+
end
|
312
|
+
end
|
313
|
+
|
314
|
+
private
|
315
|
+
|
316
|
+
# Will alias a method on the versioned class
|
317
|
+
def version_class_alias_method(new, old)
|
318
|
+
version_class.send(:alias_method, new, old)
|
319
|
+
end
|
320
|
+
|
321
|
+
# Will return the version class when dealing with a versioned object
|
322
|
+
def version_class
|
323
|
+
"#{name}::Version".constantize
|
324
|
+
end
|
325
|
+
end
|
326
|
+
|
327
|
+
module InstanceMethods
|
328
|
+
|
329
|
+
# Will determine if the given attribute is a flex attribute on the
|
330
|
+
# given model. Override this in your class to provide custom logic if
|
331
|
+
# the #flex_attributes method or the :fields option are not flexible
|
332
|
+
# enough. If you override this method :fields and #flex_attributes will
|
333
|
+
# not apply at all unless you implement them yourself.
|
334
|
+
def is_flex_attribute?(attr, model)
|
335
|
+
attr = attr.to_s
|
336
|
+
return flex_options[model.name][:fields].include?(attr) unless
|
337
|
+
flex_options[model.name][:fields].nil?
|
338
|
+
return flex_attributes(model).collect {|f| f.to_s}.include?(attr) unless
|
339
|
+
flex_attributes(model).nil?
|
340
|
+
true
|
341
|
+
end
|
342
|
+
|
343
|
+
# Return a list of valid flex attributes for the given model. Return
|
344
|
+
# nil if any field is allowed. If you want to say no field is allowed
|
345
|
+
# then return an empty array. If you just have a static list the :fields
|
346
|
+
# option is most likely easier.
|
347
|
+
def flex_attributes(model); nil end
|
348
|
+
|
349
|
+
private
|
350
|
+
|
351
|
+
# Called after validation on update so that flex attributes behave
|
352
|
+
# like normal attributes in the fact that the database is not touched
|
353
|
+
# until save is called.
|
354
|
+
def save_modified_flex_attributes
|
355
|
+
return if @save_flex_attr.nil?
|
356
|
+
@save_flex_attr.each do |s|
|
357
|
+
model, attr_name = s
|
358
|
+
related_attr = flex_related_attr model, attr_name
|
359
|
+
unless related_attr.nil?
|
360
|
+
if related_attr.value.nil?
|
361
|
+
flex_related(model).delete related_attr
|
362
|
+
else
|
363
|
+
related_attr.save
|
364
|
+
end
|
365
|
+
end
|
366
|
+
end
|
367
|
+
@save_flex_attr = []
|
368
|
+
end
|
369
|
+
|
370
|
+
# Overrides ActiveRecord::Base#read_attribute
|
371
|
+
def read_attribute_with_flex_attributes(attr_name)
|
372
|
+
attr_name = attr_name.to_s
|
373
|
+
exec_if_related attr_name do |model|
|
374
|
+
return nil if !@remove_flex_attr.nil? && @remove_flex_attr.any? do |r|
|
375
|
+
r[0] == model && r[1] == attr_name
|
376
|
+
end
|
377
|
+
value_field = flex_options[model.name][:value_field]
|
378
|
+
related_attr = flex_related_attr model, attr_name
|
379
|
+
return nil if related_attr.nil?
|
380
|
+
return related_attr.send(value_field)
|
381
|
+
end
|
382
|
+
read_attribute_without_flex_attributes(attr_name)
|
383
|
+
end
|
384
|
+
|
385
|
+
# Overrides ActiveRecord::Base#write_attribute
|
386
|
+
def write_attribute_with_flex_attributes(attr_name, value)
|
387
|
+
attr_name = attr_name.to_s
|
388
|
+
exec_if_related attr_name do |model|
|
389
|
+
value_field = flex_options[model.name][:value_field]
|
390
|
+
@save_flex_attr ||= []
|
391
|
+
@save_flex_attr << [model, attr_name]
|
392
|
+
related_attr = flex_related_attr(model, attr_name)
|
393
|
+
if related_attr.nil?
|
394
|
+
# Used to check for nil? but this caused validation
|
395
|
+
# problems that are harder to solve. blank? is probably
|
396
|
+
# not correct but it works well for now.
|
397
|
+
unless value.blank?
|
398
|
+
name_field = flex_options[model.name][:name_field]
|
399
|
+
foreign_key = flex_options[model.name][:foreign_key]
|
400
|
+
flex_related(model).build name_field => attr_name,
|
401
|
+
value_field => value, foreign_key => self.id
|
402
|
+
end
|
403
|
+
return value
|
404
|
+
else
|
405
|
+
value_field = (value_field.to_s + '=').to_sym
|
406
|
+
return related_attr.send(value_field, value)
|
407
|
+
end
|
408
|
+
end
|
409
|
+
write_attribute_without_flex_attributes(attr_name, value)
|
410
|
+
end
|
411
|
+
|
412
|
+
# Implements flex-attributes as if real getter/setter methods
|
413
|
+
# were defined.
|
414
|
+
def method_missing_with_flex_attributes(method_id, *args, &block)
|
415
|
+
begin
|
416
|
+
method_missing_without_flex_attributes method_id, *args, &block
|
417
|
+
rescue NoMethodError => e
|
418
|
+
attr_name = method_id.to_s.sub(/\=$/, '')
|
419
|
+
exec_if_related attr_name do |model|
|
420
|
+
if method_id.to_s =~ /\=$/
|
421
|
+
return write_attribute_with_flex_attributes(attr_name, args[0])
|
422
|
+
else
|
423
|
+
return read_attribute_with_flex_attributes(attr_name)
|
424
|
+
end
|
425
|
+
end
|
426
|
+
raise e
|
427
|
+
end
|
428
|
+
end
|
429
|
+
|
430
|
+
# Retrieve the related flex attribute object
|
431
|
+
def flex_related_attr(model, attr)
|
432
|
+
name_field = flex_options[model.name][:name_field]
|
433
|
+
flex_related(model).to_a.find {|r| r.send(name_field) == attr}
|
434
|
+
end
|
435
|
+
|
436
|
+
# Retrieve the collection of related flex attributes
|
437
|
+
def flex_related(model)
|
438
|
+
relationship = flex_options[model.name][:relationship_name]
|
439
|
+
send relationship
|
440
|
+
end
|
441
|
+
|
442
|
+
# Yield only if attr_name is a flex_attribute
|
443
|
+
def exec_if_related(attr_name)
|
444
|
+
return false if self.class.column_names.include? attr_name
|
445
|
+
each_flex_relation do |model|
|
446
|
+
if is_flex_attribute? attr_name, model
|
447
|
+
yield model
|
448
|
+
end
|
449
|
+
end
|
450
|
+
end
|
451
|
+
|
452
|
+
# Yields for each flex relation.
|
453
|
+
def each_flex_relation
|
454
|
+
flex_options.keys.each {|kls| yield kls.constantize}
|
455
|
+
end
|
456
|
+
|
457
|
+
# Returns the options for the flex attributes
|
458
|
+
def flex_options
|
459
|
+
nonversioned_class(self.class).flex_options
|
460
|
+
end
|
461
|
+
|
462
|
+
# Will return the parent model if kls is a versioned class
|
463
|
+
def nonversioned_class(kls)
|
464
|
+
if kls.name =~ /\:\:Version$/
|
465
|
+
base_class = kls.name
|
466
|
+
base_class.sub! /\:\:Version$/, ''
|
467
|
+
return base_class.constantize
|
468
|
+
end
|
469
|
+
kls
|
470
|
+
end
|
471
|
+
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
ActiveRecord::Base.class_eval { include FlexAttributes }
|
metadata
ADDED
@@ -0,0 +1,64 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: pjb3-flex-attributes
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: "0.1"
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Eric Anderson
|
8
|
+
- Paul Barry
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2008-08-26 00:00:00 -07:00
|
14
|
+
default_executable:
|
15
|
+
dependencies: []
|
16
|
+
|
17
|
+
description: Flex attributes allow for the common but questionable database design of storing attributes in a thin key/value table related to some model.
|
18
|
+
email: erci (at) afaik (dot) us
|
19
|
+
executables: []
|
20
|
+
|
21
|
+
extensions: []
|
22
|
+
|
23
|
+
extra_rdoc_files:
|
24
|
+
- lib/flex-attributes.rb
|
25
|
+
- README
|
26
|
+
files:
|
27
|
+
- flex-attributes.gemspec
|
28
|
+
- lib/flex-attributes.rb
|
29
|
+
- Manifest
|
30
|
+
- README
|
31
|
+
- MIT-LICENSE
|
32
|
+
has_rdoc: true
|
33
|
+
homepage: http://github.com/pjb3/flex-attributes
|
34
|
+
post_install_message:
|
35
|
+
rdoc_options:
|
36
|
+
- --line-numbers
|
37
|
+
- --inline-source
|
38
|
+
- --title
|
39
|
+
- Flex-Attributes
|
40
|
+
- --main
|
41
|
+
- README
|
42
|
+
require_paths:
|
43
|
+
- lib
|
44
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
45
|
+
requirements:
|
46
|
+
- - ">="
|
47
|
+
- !ruby/object:Gem::Version
|
48
|
+
version: "0"
|
49
|
+
version:
|
50
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: "0"
|
55
|
+
version:
|
56
|
+
requirements: []
|
57
|
+
|
58
|
+
rubyforge_project: flex-attributes
|
59
|
+
rubygems_version: 1.2.0
|
60
|
+
signing_key:
|
61
|
+
specification_version: 2
|
62
|
+
summary: Gem version of flex-attributes Rails plugin.
|
63
|
+
test_files: []
|
64
|
+
|