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 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
@@ -0,0 +1,5 @@
1
+ flex-attributes.gemspec
2
+ lib/flex-attributes.rb
3
+ Manifest
4
+ README
5
+ MIT-LICENSE
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
+