pjb3-flex-attributes 0.1
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/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
|
+
|