serialized_attributes 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source "http://rubygems.org"
2
+ gem 'rails', "~> 3.0.0"
3
+
4
+ # Add dependencies to develop your gem here.
5
+ # Include everything needed to run rake, tests, features, etc.
6
+ group :development do
7
+ gem "bundler", "~> 1.0.7"
8
+ gem "jeweler", "~> 1.5.2"
9
+ gem "sqlite3"
10
+ end
data/Gemfile.lock ADDED
@@ -0,0 +1,80 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ abstract (1.0.0)
5
+ actionmailer (3.0.5)
6
+ actionpack (= 3.0.5)
7
+ mail (~> 2.2.15)
8
+ actionpack (3.0.5)
9
+ activemodel (= 3.0.5)
10
+ activesupport (= 3.0.5)
11
+ builder (~> 2.1.2)
12
+ erubis (~> 2.6.6)
13
+ i18n (~> 0.4)
14
+ rack (~> 1.2.1)
15
+ rack-mount (~> 0.6.13)
16
+ rack-test (~> 0.5.7)
17
+ tzinfo (~> 0.3.23)
18
+ activemodel (3.0.5)
19
+ activesupport (= 3.0.5)
20
+ builder (~> 2.1.2)
21
+ i18n (~> 0.4)
22
+ activerecord (3.0.5)
23
+ activemodel (= 3.0.5)
24
+ activesupport (= 3.0.5)
25
+ arel (~> 2.0.2)
26
+ tzinfo (~> 0.3.23)
27
+ activeresource (3.0.5)
28
+ activemodel (= 3.0.5)
29
+ activesupport (= 3.0.5)
30
+ activesupport (3.0.5)
31
+ arel (2.0.9)
32
+ builder (2.1.2)
33
+ erubis (2.6.6)
34
+ abstract (>= 1.0.0)
35
+ git (1.2.5)
36
+ i18n (0.5.0)
37
+ jeweler (1.5.2)
38
+ bundler (~> 1.0.0)
39
+ git (>= 1.2.5)
40
+ rake
41
+ mail (2.2.15)
42
+ activesupport (>= 2.3.6)
43
+ i18n (>= 0.4.0)
44
+ mime-types (~> 1.16)
45
+ treetop (~> 1.4.8)
46
+ mime-types (1.16)
47
+ polyglot (0.3.1)
48
+ rack (1.2.1)
49
+ rack-mount (0.6.13)
50
+ rack (>= 1.0.0)
51
+ rack-test (0.5.7)
52
+ rack (>= 1.0)
53
+ rails (3.0.5)
54
+ actionmailer (= 3.0.5)
55
+ actionpack (= 3.0.5)
56
+ activerecord (= 3.0.5)
57
+ activeresource (= 3.0.5)
58
+ activesupport (= 3.0.5)
59
+ bundler (~> 1.0)
60
+ railties (= 3.0.5)
61
+ railties (3.0.5)
62
+ actionpack (= 3.0.5)
63
+ activesupport (= 3.0.5)
64
+ rake (>= 0.8.7)
65
+ thor (~> 0.14.4)
66
+ rake (0.8.7)
67
+ sqlite3 (1.3.3)
68
+ thor (0.14.6)
69
+ treetop (1.4.9)
70
+ polyglot (>= 0.3.1)
71
+ tzinfo (0.3.24)
72
+
73
+ PLATFORMS
74
+ ruby
75
+
76
+ DEPENDENCIES
77
+ bundler (~> 1.0.7)
78
+ jeweler (~> 1.5.2)
79
+ rails (~> 3.0.0)
80
+ sqlite3
data/LICENSE.txt ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2011 Emma Persky
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,72 @@
1
+ serialized_attributes
2
+ =====================
3
+
4
+ This is a very cool lib, allows to define dynamic serialized fields in your AR model, which acts as normal rails db columns.
5
+ It give you full power of rails to your virtual columns: validations, rails forms, error messages, type casting
6
+ (integer, date/time), type safety, :dirty? s, etc.. STI is supported! Basic associations is working too!
7
+
8
+ Now updated for Rails 3
9
+
10
+ Example of usage
11
+ ----------------
12
+
13
+ ### STI table def
14
+
15
+ create_table :documents do |t|
16
+ t.string :type
17
+ t.text :serialized_attributes # <--- here all your dynamic fields will be saved
18
+ t.integer :reference_id # <--- you can also define any sql columns for your indexes
19
+ t.timestamps
20
+ end
21
+
22
+ ### STI base model
23
+
24
+ class Document < ActiveRecord::Base
25
+ include SerializedAttributes
26
+ end
27
+
28
+ ### Other models..
29
+
30
+ class Post < Document
31
+ attribute :title, String
32
+ attribute :body, String
33
+ attribute :is_published, Boolean, :default => false
34
+
35
+ attribute :comment_ids, Array # <--- serialized Array of ids of associated comments
36
+ has_references_to :comments
37
+
38
+ validates_presence_of :title, :body
39
+ end
40
+
41
+ class Comment < Document
42
+ attribute :body, String, :requied => true # <--- validates_presence_of :body
43
+ attribute :post_id, Integer
44
+ belongs_to :post
45
+ end
46
+
47
+ IRB fun
48
+ -------
49
+
50
+ post = Post.create(:title => "First Post", :body => "Lorem ...")
51
+ assert !post.new_record?
52
+ post.comments = [Comment.create(:body => "this is a comment")]
53
+ post.comments << Comment.create(:body => "this is second comment")
54
+ assert_equal Comment.all.map(&:id), post.comment_ids
55
+ post.save
56
+ assert post.reload.comments.length == 2
57
+
58
+ # Mass Assignment and Protect Attributes
59
+ class Widget < ActiveRecord::Base
60
+ include SerializedAttributes
61
+
62
+ #protect other attributes from mass assignment
63
+ attr_accessible :name
64
+
65
+ #specifically permit a given serialized attribute to be mass assigned
66
+ accessible_attribute :creator, String
67
+ end
68
+
69
+
70
+ # limitations
71
+ - has-references-to association dont update reverse association
72
+ - serialized-attributes column saved every time you call :save without depending on what is actually changed
data/Rakefile ADDED
@@ -0,0 +1,53 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "serialized_attributes"
16
+ gem.homepage = "http://github.com/emmapersky/serialized_attributes"
17
+ gem.license = "MIT"
18
+ gem.summary = %Q{Simple serialization of row level attributes}
19
+ gem.description = %Q{Serialize model attributes to a single database column instead}
20
+ gem.email = "emma.persky@gmail.com"
21
+ gem.authors = ["Emma Persky"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rake/testtask'
30
+ Rake::TestTask.new(:test) do |test|
31
+ test.libs << 'lib' << 'test'
32
+ test.pattern = 'test/**/*.rb'
33
+ test.verbose = true
34
+ end
35
+
36
+ # require 'rcov/rcovtask'
37
+ # Rcov::RcovTask.new do |test|
38
+ # test.libs << 'test'
39
+ # test.pattern = 'test/**/*.rb'
40
+ # test.verbose = true
41
+ # end
42
+
43
+ task :default => :test
44
+
45
+ require 'rake/rdoctask'
46
+ Rake::RDocTask.new do |rdoc|
47
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
48
+
49
+ rdoc.rdoc_dir = 'rdoc'
50
+ rdoc.title = "foo #{version}"
51
+ rdoc.rdoc_files.include('README*')
52
+ rdoc.rdoc_files.include('lib/**/*.rb')
53
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.0
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+ require File.dirname(__FILE__) + '/lib/has_references_to'
2
+ require File.dirname(__FILE__) + '/lib/serialized_attributes'
@@ -0,0 +1,70 @@
1
+ module ActiveRecord
2
+ module Associations
3
+
4
+ class HasReferencesToAssociation < HasManyAssociation
5
+ def ids
6
+ (@owner["#{@reflection.name.to_s.singularize}_ids"] ||= []).map(&:to_i)
7
+ end
8
+
9
+ def ids=(array = [])
10
+ @owner["#{@reflection.name.to_s.singularize}_ids"] = array.map(&:to_i)
11
+ end
12
+
13
+ def construct_sql
14
+ @finder_sql = "#{@reflection.quoted_table_name}.id IN (#{ids * ', '})"
15
+ @finder_sql << " AND (#{conditions})" if conditions
16
+ @counter_sql = @finder_sql
17
+ end
18
+
19
+ def insert_record(record, force = false, validate = true)
20
+ load_target
21
+ set_belongs_to_association_for(record)
22
+ result = !record.new_record? || (force ? record.save! : record.save(:validate => validate))
23
+ self.ids = (ids + [record.id]) if result
24
+ result
25
+ end
26
+
27
+ def delete_records(records)
28
+ self.ids = ids - records.map(&:id)
29
+ end
30
+
31
+ def create(attrs = {})
32
+ if attrs.is_a?(Array)
33
+ attrs.collect { |attr| create(attr) }
34
+ else
35
+ create_record(attrs) do |record|
36
+ yield(record) if block_given?
37
+ self.ids = (ids << record.id) if record.save
38
+ end
39
+ end
40
+ end
41
+
42
+ def create!(attrs = {})
43
+ create_record(attrs) do |record|
44
+ yield(record) if block_given?
45
+ record.save!
46
+ self.ids = (ids << record.id)
47
+ end
48
+ end
49
+ end
50
+
51
+ module ClassMethods
52
+ def create_has_references_to_reflection(association_id, options, &extension)
53
+ #options.assert_valid_keys(valid_keys_for_has_many_association)
54
+ options[:extend] = create_extension_modules(association_id, extension, options[:extend])
55
+ reflection = ActiveRecord::Reflection::AssociationReflection.new(:has_many, association_id, options, self)
56
+ write_inheritable_hash :reflections, name => reflection
57
+ reflection
58
+ end
59
+
60
+ def has_references_to(association_id, options = {}, &extension)
61
+ reflection = create_has_references_to_reflection(association_id, options, &extension)
62
+ configure_dependency_for_has_many(reflection)
63
+ add_association_callbacks(reflection.name, reflection.options)
64
+
65
+ collection_accessor_methods(reflection, HasReferencesToAssociation)
66
+ end
67
+ end
68
+
69
+ end
70
+ end
@@ -0,0 +1,89 @@
1
+ module SerializedAttributes
2
+ def self.included(base)
3
+ return if base.respond_to?(:serialized_attributes_definition)
4
+
5
+ base.class_eval do
6
+ class_inheritable_hash :serialized_attributes_definition
7
+ write_inheritable_attribute(:serialized_attributes_definition, {})
8
+ cattr_accessor :serialized_attributes_column
9
+ self.serialized_attributes_column = :serialized_attributes
10
+
11
+ serialize serialized_attributes_column, Hash
12
+
13
+
14
+ base.extend ClassMethods
15
+ include InstanceMethods
16
+ end
17
+ end
18
+
19
+ module ClassMethods
20
+ def serialized_attributes_definition
21
+ read_inheritable_attribute(:serialized_attributes_definition)
22
+ end
23
+
24
+ def instantiate(record)
25
+ object = super(record)
26
+ object.unpack_serialized_attributes!
27
+ object
28
+ end
29
+
30
+ def accessible_attribute(name, type, opts = {})
31
+ attribute(name, type, opts.merge({:attr_accessible => true}))
32
+ end
33
+
34
+ def attribute(name, type, opts = {})
35
+ name = name.to_s
36
+ type = SerializedAttributes.type_to_sqltype(type)
37
+ serialized_attributes_definition[name] = ActiveRecord::ConnectionAdapters::Column.new(name.to_s, opts[:default], type.to_s, nil)
38
+
39
+ define_method("#{name.to_s}=".to_sym) { |value| @attributes[name] = value }
40
+ define_method(name) { self.class.serialized_attributes_definition[name].type_cast(@attributes[name]) }
41
+
42
+ attr_accessible name if opts[:attr_accessible]
43
+ end
44
+ end
45
+
46
+ module InstanceMethods
47
+ def create_or_update
48
+ pack_serialized_attributes!
49
+ super
50
+ end
51
+
52
+ def unpack_serialized_attributes!
53
+ if @attributes.has_key?(serialized_attributes_column.to_s) && attributes = (self[serialized_attributes_column] || {})
54
+ serialized_attributes_definition.each do |key, column|
55
+ loaded_value = attributes.has_key?(key) ? attributes[key] : column.default
56
+ @attributes[key] = attributes.has_key?(key) ? attributes[key] : column.default
57
+ end
58
+ attributes.slice!(*serialized_attributes_definition.keys)
59
+ end
60
+ end
61
+
62
+ def pack_serialized_attributes!
63
+ if @attributes.has_key?(serialized_attributes_column.to_s)
64
+ attributes = self[serialized_attributes_column] ||= {}
65
+ serialized_attributes_definition.each do |key, column|
66
+ attributes[key] = self.send key
67
+ end
68
+ end
69
+ attributes.slice!(*serialized_attributes_definition.keys)
70
+ end
71
+ end
72
+
73
+ def to_variable(sym)
74
+ "@#{sym.to_s}".to_sym
75
+ end
76
+
77
+ def self.type_to_sqltype(type)
78
+ return type if type.is_a?(Symbol)
79
+ {
80
+ String => :string, Boolean => :boolean,
81
+ Fixnum => :integer, Integer => :integer, BigDecimal => :decimal, Float => :float,
82
+ Date => :date, Time => :time, DateTime => :time
83
+ }[type] || type
84
+ end
85
+
86
+ module Boolean
87
+ end
88
+
89
+ end
@@ -0,0 +1,130 @@
1
+ require 'test/unit'
2
+ require 'rubygems'
3
+ require 'active_record'
4
+ require 'logger'
5
+
6
+ ActiveRecord::Base.establish_connection :adapter => 'sqlite3', :database => ':memory:'
7
+
8
+ require File.dirname(__FILE__) + "/../init" # load plugin
9
+
10
+ class DocumentsSchema < ActiveRecord::Migration
11
+ def self.up
12
+ create_table :documents do |t|
13
+ t.text :serialized_attributes # <--- here all your dynamic fields will be saved
14
+ t.string :type
15
+ t.integer :reference_id # <--- you can also define any sql columns for your indexes
16
+ t.timestamps
17
+ end
18
+
19
+ create_table :widgets do |t|
20
+ t.string :name
21
+ t.boolean :active
22
+ t.text :serialized_attributes
23
+ t.timestamps
24
+ end
25
+ end
26
+ end
27
+
28
+ class Document < ActiveRecord::Base
29
+ include SerializedAttributes
30
+ end
31
+
32
+ class Post < Document
33
+ attribute :title, String
34
+ attribute :body, String
35
+ attribute :is_published, Boolean, :default => false
36
+
37
+ attribute :comment_ids, Array # <--- serialized Array of ids of associated comments
38
+ has_references_to :comments
39
+
40
+ validates_presence_of :title, :body
41
+
42
+ end
43
+
44
+ class Comment < Document
45
+ attribute :body, String
46
+ attribute :post_id, Integer
47
+ belongs_to :post
48
+
49
+ validates_presence_of :body
50
+
51
+ end
52
+
53
+ class ModelBefore < ActiveRecord::Base
54
+ set_table_name :documents
55
+ end
56
+
57
+ class ModelAfter < ActiveRecord::Base
58
+ set_table_name :documents
59
+ include SerializedAttributes
60
+ attribute :custom_field, String, :default => 'default value'
61
+ end
62
+
63
+ class ModelSecond < ActiveRecord::Base
64
+ set_table_name :documents
65
+ include SerializedAttributes
66
+ attribute :custom_field_renamed, String, :default => 'new default value'
67
+ end
68
+
69
+ class Widget < ActiveRecord::Base
70
+ include SerializedAttributes
71
+
72
+ #white list the name attribute, others may not be mass assigned
73
+ attr_accessible :name, String
74
+ end
75
+
76
+ class Sprocket < Widget
77
+ #we want the attribute in_motion, but it may not be mass assigned
78
+ attribute :in_motion, Boolean
79
+
80
+ #we want to allow the size attribute to be mass assigned
81
+ accessible_attribute :size, Integer
82
+ end
83
+
84
+
85
+ class SimpleTest < Test::Unit::TestCase
86
+ #ActiveRecord::Base.logger = Logger.new(STDOUT)
87
+ DocumentsSchema.suppress_messages{ DocumentsSchema.migrate(:up) }
88
+
89
+ def test_simple
90
+ post = Post.create(:title => "First Post", :body => "Lorem ...")
91
+ assert !post.new_record?
92
+ post.comments << Comment.new(:body => "this is a comment")
93
+ post.comments << Comment.create(:body => "this is second comment")
94
+ post.comments.create(:body => "one more")
95
+
96
+ assert_equal Comment.all.map(&:id), post.comment_ids
97
+ post.save
98
+
99
+ assert_equal 3, post.reload.comments.size
100
+ end
101
+
102
+
103
+ # => it should initialize attributes on objects even if they were serialized before that attribute existed
104
+ def test_null_serialized_attributes_column_on_already_exists_records
105
+ # => to test this, we create a model (ModelBefore) that has no attributes (but has an attributes column)
106
+ # => then we create second model (ModelAfter) which we force to use the same table as ModelBefore (set_table_name)
107
+ # => We create an object using ModelBefore and then try to load it using ModelAfter.
108
+ model_before = ModelBefore.create
109
+ model_after = ModelAfter.find(model_before.id)
110
+
111
+ assert_equal model_after.custom_field, 'default value'
112
+ end
113
+
114
+ # => it should not unpack custom attributes on objects if they have been removed
115
+ def test_removed_custom_field
116
+ # => to test this, we use a similar method to the prior test, but change (or remove) an attribute
117
+ model1 = ModelAfter.create
118
+ model2 = ModelSecond.find(model1.id)
119
+ model2.save!
120
+ model2.reload
121
+
122
+ assert_equal model2.serialized_attributes.keys.include?('custom_field'), false
123
+ end
124
+
125
+ # => it should create attributes as whitelisted and allow their mass assignment
126
+ def test_accessible_attributes_are_created
127
+ sprocket = Sprocket.create(:name => "Spacely's Space Sprocket", :size => 99)
128
+ assert sprocket.size == 99
129
+ end
130
+ end
metadata ADDED
@@ -0,0 +1,138 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: serialized_attributes
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Emma Persky
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-03 00:00:00 -05:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ type: :runtime
23
+ prerelease: false
24
+ name: rails
25
+ version_requirements: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ~>
29
+ - !ruby/object:Gem::Version
30
+ hash: 7
31
+ segments:
32
+ - 3
33
+ - 0
34
+ - 0
35
+ version: 3.0.0
36
+ requirement: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ type: :development
39
+ prerelease: false
40
+ name: bundler
41
+ version_requirements: &id002 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ hash: 25
47
+ segments:
48
+ - 1
49
+ - 0
50
+ - 7
51
+ version: 1.0.7
52
+ requirement: *id002
53
+ - !ruby/object:Gem::Dependency
54
+ type: :development
55
+ prerelease: false
56
+ name: jeweler
57
+ version_requirements: &id003 !ruby/object:Gem::Requirement
58
+ none: false
59
+ requirements:
60
+ - - ~>
61
+ - !ruby/object:Gem::Version
62
+ hash: 7
63
+ segments:
64
+ - 1
65
+ - 5
66
+ - 2
67
+ version: 1.5.2
68
+ requirement: *id003
69
+ - !ruby/object:Gem::Dependency
70
+ type: :development
71
+ prerelease: false
72
+ name: sqlite3
73
+ version_requirements: &id004 !ruby/object:Gem::Requirement
74
+ none: false
75
+ requirements:
76
+ - - ">="
77
+ - !ruby/object:Gem::Version
78
+ hash: 3
79
+ segments:
80
+ - 0
81
+ version: "0"
82
+ requirement: *id004
83
+ description: Serialize model attributes to a single database column instead
84
+ email: emma.persky@gmail.com
85
+ executables: []
86
+
87
+ extensions: []
88
+
89
+ extra_rdoc_files:
90
+ - LICENSE.txt
91
+ - README.md
92
+ files:
93
+ - Gemfile
94
+ - Gemfile.lock
95
+ - LICENSE.txt
96
+ - README.md
97
+ - Rakefile
98
+ - VERSION
99
+ - init.rb
100
+ - lib/has_references_to.rb
101
+ - lib/serialized_attributes.rb
102
+ - test/simple_test.rb
103
+ has_rdoc: true
104
+ homepage: http://github.com/emmapersky/serialized_attributes
105
+ licenses:
106
+ - MIT
107
+ post_install_message:
108
+ rdoc_options: []
109
+
110
+ require_paths:
111
+ - lib
112
+ required_ruby_version: !ruby/object:Gem::Requirement
113
+ none: false
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ hash: 3
118
+ segments:
119
+ - 0
120
+ version: "0"
121
+ required_rubygems_version: !ruby/object:Gem::Requirement
122
+ none: false
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ hash: 3
127
+ segments:
128
+ - 0
129
+ version: "0"
130
+ requirements: []
131
+
132
+ rubyforge_project:
133
+ rubygems_version: 1.3.7
134
+ signing_key:
135
+ specification_version: 3
136
+ summary: Simple serialization of row level attributes
137
+ test_files:
138
+ - test/simple_test.rb