data-attributes 0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in data-attributes.gemspec
4
+ gemspec
@@ -0,0 +1,73 @@
1
+ = Data Attributes
2
+
3
+ == Description
4
+
5
+ A gem for easy access to attributes stored in a serialized data hash.
6
+
7
+ == Summary
8
+
9
+ ActiveRecord attributes are mapped to columns in your database; however, many attributes that needed to be persisted for an object don't need their own column because they will never be searched or filtered on. One solution is to save these with a serialized hash into a text field in the database and provide similar access to the values as other attribute accessors.
10
+
11
+ == Usage
12
+
13
+ Consider a +User+ class that has a text field that is serialized, +:data+.
14
+
15
+ class User < ActiveRecord::Base
16
+
17
+ serialize :data
18
+
19
+ end
20
+
21
+ We can set an accessor to an attribute to be saved in the +:data+ field with the following:
22
+
23
+ data_attributes :details
24
+
25
+ This generates a read and write accessor called +details+.
26
+
27
+ u = User.new
28
+ u.details = "I don't need to query on this."
29
+ puts u.details
30
+ => I don't need to query on this.
31
+ puts u.data.inspect
32
+ => { "details" => "I don't need to query on this." }
33
+
34
+ The default serialized attribute used is +data+. There are two ways to change the serialized attribute used for storage. The first is set a different default attribute for the class.
35
+
36
+ data_attribute_column :more_data
37
+
38
+ This makes all of the accessors created with +data_attributes+ be saved in +more_data+ instead of +data+. If a class has two (or more) serialized attributes that will share the storage responsibilities, each attribute defined with +data_attributes+ can be individually assigned to a serialized attribute by using a hash.
39
+
40
+ data_attribute :details, { :serialized_column => :more_data }
41
+
42
+ This creates an accessor that saves +details+ to +more_data+.
43
+
44
+ You can set a default to be returned, in the case the value hasn't be set yet.
45
+
46
+ data_attribute :details, { :default => "default value" }
47
+
48
+ == Under The Hood
49
+
50
+ Like column accessors that make use of +read_attribute+ and +write_attribute+, +data-attributes+ uses +read_data_attribute+ and +write_data_attribute+ to access the serialized attribute. This allows the ability to overwrite an accessor so any validation or other data manipulation needed before the value is returned or saved.
51
+
52
+ == License
53
+
54
+ Copyright (c) 2011 Les Fletcher
55
+
56
+ Permission is hereby granted, free of charge, to any person obtaining
57
+ a copy of this software and associated documentation files (the
58
+ "Software"), to deal in the Software without restriction, including
59
+ without limitation the rights to use, copy, modify, merge, publish,
60
+ distribute, sublicense, and/or sell copies of the Software, and to
61
+ permit persons to whom the Software is furnished to do so, subject to
62
+ the following conditions:
63
+
64
+ The above copyright notice and this permission notice shall be
65
+ included in all copies or substantial portions of the Software.
66
+
67
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
68
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
69
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
70
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
71
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
72
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
73
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,7 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ desc "Run all tests"
5
+ task :test do
6
+ sh "ruby test/data_attributes_test.rb"
7
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "data-attributes/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "data-attributes"
7
+ s.version = DataAttributes::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Les Fletcher"]
10
+ s.email = ["les.fletcher@gmail.com"]
11
+ s.homepage = ""
12
+ s.summary = %q{A gem for easy access to attributes stored in a serialized data hash}
13
+ s.description = %q{A gem for easy access to attributes stored in a serialized data hash}
14
+
15
+ s.rubyforge_project = "data-attributes"
16
+
17
+ s.files = `git ls-files`.split("\n")
18
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
19
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
+ s.require_paths = ["lib"]
21
+
22
+ s.add_development_dependency('test-unit', '~> 2.2.0')
23
+ s.add_development_dependency('sqlite3', '~> 1.3.0')
24
+ s.add_dependency('activerecord', '~> 3.0.0')
25
+ end
@@ -0,0 +1,2 @@
1
+ require File.expand_path( File.join( File.dirname( __FILE__ ), 'data-attributes', 'data_attributes' ) )
2
+ require File.expand_path( File.join( File.dirname( __FILE__ ), 'data-attributes', 'errors' ) )
@@ -0,0 +1,80 @@
1
+ module DataAttributes
2
+ module DataAttributes
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ DEFAULT_DATA_COLUMN = :data
9
+
10
+ # set the default serialized attribute to use to store all the values
11
+ # default is :data
12
+ # raises +NonSerializedColumnError+ if value isn't serlialized
13
+ def data_attribute_column(value)
14
+ raise(NonSerializedColumnError) unless serialized_attributes[value.to_s]
15
+ write_inheritable_attribute(:attr_data_attribute_column, value.to_sym)
16
+ end
17
+
18
+ # set up an attribute accessor for values stored in the serialized hash
19
+ # takes an attribute name and a hash sets up a read and write accessor for it
20
+ # options:
21
+ # :serialized_column => the column used to store the attribute info
22
+ # :default => what to return if the attribute is not set
23
+ # raises +NonSerializedColumnError+ if the column specified isn't serlialized
24
+ def data_attribute(name, arg_opts={})
25
+ opts = {
26
+ :serialized_column => attr_data_attribute_column,
27
+ :default => nil
28
+ }
29
+
30
+ opts.merge!(arg_opts)
31
+
32
+ # make sure the serialized_column is actually serialized
33
+ raise(NonSerializedColumnError) unless serialized_attributes[opts[:serialized_column].to_s]
34
+
35
+ attr_data_attributes[name.to_sym] = opts
36
+
37
+ attr_name = name.to_s
38
+ class_eval("def #{attr_name}; read_data_attribute('#{attr_name}'); end")
39
+ class_eval("def #{attr_name}= (value); write_data_attribute('#{attr_name}', value); end")
40
+ end
41
+
42
+ def attr_data_attributes
43
+ read_inheritable_attribute(:attr_data_attributes) or write_inheritable_attribute(:attr_data_attributes, {})
44
+ end
45
+
46
+ def attr_data_attribute_column
47
+ read_inheritable_attribute(:attr_data_attribute_column) or write_inheritable_attribute(:attr_data_attribute_column, DEFAULT_DATA_COLUMN)
48
+ end
49
+
50
+ def data_attribute_options(name)
51
+ attr_data_attributes.has_key?(name.to_sym) ? attr_data_attributes[name.to_sym] : raise(NonDataAttributeError)
52
+ end
53
+
54
+ def data_attribute_serialized_column(name)
55
+ data_attribute_options(name)[:serialized_column]
56
+ end
57
+
58
+ def data_attribute_default(name)
59
+ data_attribute_options(name)[:default]
60
+ end
61
+ end
62
+
63
+ def read_data_attribute(name)
64
+ (self.send(data_attribute_serialized_column(name)) || {}).has_key?(name) ? self.send(data_attribute_serialized_column(name))[name] : self.data_attribute_default(name)
65
+ end
66
+
67
+ def write_data_attribute(name, value)
68
+ if self.send(data_attribute_serialized_column(name)).nil?
69
+ self.send("#{data_attribute_serialized_column(name)}=", { name => value })
70
+ else
71
+ self.send("#{data_attribute_serialized_column(name)}=", self.send(data_attribute_serialized_column(name)).merge({ name => value }))
72
+ end
73
+ end
74
+
75
+ def data_attribute_serialized_column(name); self.class.data_attribute_serialized_column(name) end
76
+ def data_attribute_default(name); self.class.data_attribute_default(name) end
77
+ end
78
+ end
79
+
80
+ ActiveRecord::Base.send(:include, DataAttributes::DataAttributes)
@@ -0,0 +1,10 @@
1
+ module DataAttributes
2
+ class DataAttributeError < StandardError
3
+ end
4
+
5
+ class NonSerializedColumnError < DataAttributeError
6
+ end
7
+
8
+ class NonDataAttributeError < DataAttributeError
9
+ end
10
+ end
@@ -0,0 +1,3 @@
1
+ module DataAttributes
2
+ VERSION = "0.2"
3
+ end
@@ -0,0 +1,132 @@
1
+ require File.expand_path( File.join( File.dirname( __FILE__ ), 'test_helper' ) )
2
+
3
+ class TestDataAttributes < Test::Unit::TestCase
4
+
5
+ def test_setup
6
+ assert_equal Basic.attr_data_attribute_column, :data
7
+ assert_equal Basic.attr_data_attributes, { :stuff => { :serialized_column => :data, :default => nil } }
8
+ assert_equal TwoAttribute.attr_data_attribute_column, :data
9
+ assert_equal TwoAttribute.attr_data_attributes, {
10
+ :stuff => { :serialized_column => :data, :default => nil },
11
+ :things => { :serialized_column => :data, :default => nil }
12
+ }
13
+ assert_equal DifferentSerializedAttribute.attr_data_attribute_column, :more_data
14
+ assert_equal DifferentSerializedAttribute.attr_data_attributes, { :stuff => { :serialized_column => :more_data, :default => nil } }
15
+ assert_equal TwoSerializedAttribute.attr_data_attribute_column, :more_data
16
+ assert_equal TwoSerializedAttribute.attr_data_attributes, {
17
+ :stuff => { :serialized_column => :data, :default => 1 },
18
+ :things => { :serialized_column => :more_data, :default => "two" }
19
+ }
20
+ end
21
+
22
+ def test_basic
23
+ b = Basic.new
24
+ assert_equal b.data, nil
25
+ assert_equal b.stuff, nil
26
+
27
+ b.stuff = "blah"
28
+ assert_equal b.stuff, "blah"
29
+ assert_equal b.data, { "stuff" => "blah" }
30
+ end
31
+
32
+ def test_primitives_methods
33
+ b = Basic.new
34
+ assert_equal b.data, nil
35
+ assert_equal b.read_data_attribute("stuff"), nil
36
+
37
+ b.write_data_attribute("stuff", "blah")
38
+ assert_equal b.data, { "stuff" => "blah" }
39
+ assert_equal b.read_data_attribute("stuff"), "blah"
40
+ end
41
+
42
+ def test_two_attributes
43
+ b = TwoAttribute.new
44
+ assert_equal b.data, nil
45
+ assert_equal b.stuff, nil
46
+
47
+ b.stuff = "blah"
48
+ assert_equal b.stuff, "blah"
49
+ assert_equal b.data, { "stuff" => "blah" }
50
+
51
+ b.things = "boom"
52
+ assert_equal b.stuff, "blah"
53
+ assert_equal b.things, "boom"
54
+ assert_equal b.data, { "stuff" => "blah", "things" => "boom" }
55
+ end
56
+
57
+ def test_primitives_methods_two
58
+ b = TwoAttribute.new
59
+ assert_equal b.data, nil
60
+ assert_equal b.read_data_attribute("stuff"), nil
61
+
62
+ b.write_data_attribute("stuff", "blah")
63
+ assert_equal b.read_data_attribute("stuff"), "blah"
64
+ assert_equal b.data, { "stuff" => "blah" }
65
+
66
+ b.write_data_attribute("things", "boom")
67
+ assert_equal b.read_data_attribute("stuff"), "blah"
68
+ assert_equal b.read_data_attribute("things"), "boom"
69
+ assert_equal b.data, { "stuff" => "blah", "things" => "boom" }
70
+ end
71
+
72
+ def test_two_serialized
73
+ t = TwoSerializedAttribute.new
74
+ assert_equal t.data, nil
75
+ assert_equal t.more_data, nil
76
+ assert_equal t.stuff, 1
77
+ assert_equal t.things, "two"
78
+
79
+ t.stuff = "stuff val"
80
+ t.things = "things val"
81
+
82
+ assert_equal t.stuff, "stuff val"
83
+ assert_equal t.things, "things val"
84
+ assert_equal t.more_data, { "things" => "things val" }
85
+ assert_equal t.data, { "stuff" => "stuff val" }
86
+ end
87
+
88
+ def test_saving
89
+ b = Basic.create
90
+ id = b.id
91
+ assert_equal b.data, nil
92
+ assert_equal b.stuff, nil
93
+
94
+ b.stuff = "blah"
95
+ assert_equal b.stuff, "blah"
96
+ assert_equal b.data, { "stuff" => "blah" }
97
+
98
+ b.save
99
+
100
+ b = Basic.find(id)
101
+ assert_equal b.stuff, "blah"
102
+ assert_equal b.data, { "stuff" => "blah" }
103
+ end
104
+
105
+ def test_non_serialized_error
106
+ klass = Class.new(ActiveRecord::Base)
107
+ ActiveRecord::Base.const_set("NonSerializedAttribute", klass)
108
+
109
+ assert_raise(DataAttributes::NonSerializedColumnError) do
110
+ klass.class_eval do
111
+ data_attribute :stuff
112
+ end
113
+ end
114
+ end
115
+
116
+ def test_default_non_serialized_error
117
+ klass = Class.new(ActiveRecord::Base)
118
+ ActiveRecord::Base.const_set("DefaultNonSerializedAttribute", klass)
119
+
120
+ assert_raise(DataAttributes::NonSerializedColumnError) do
121
+ klass.class_eval do
122
+ data_attribute_column :more_data
123
+ end
124
+ end
125
+ end
126
+
127
+ def test_data_attribute_options
128
+ assert_raise(DataAttributes::NonDataAttributeError) do
129
+ Basic.data_attribute_options(:things)
130
+ end
131
+ end
132
+ end
@@ -0,0 +1,26 @@
1
+ class Basic < ActiveRecord::Base
2
+ serialize :data
3
+ data_attribute :stuff
4
+ end
5
+
6
+ class TwoAttribute < ActiveRecord::Base
7
+ serialize :data
8
+ data_attribute :stuff
9
+ data_attribute :things
10
+ end
11
+
12
+ class DifferentSerializedAttribute < ActiveRecord::Base
13
+ serialize :more_data
14
+
15
+ data_attribute_column :more_data
16
+ data_attribute :stuff
17
+ end
18
+
19
+ class TwoSerializedAttribute < ActiveRecord::Base
20
+ serialize :data
21
+ serialize :more_data
22
+
23
+ data_attribute_column :more_data
24
+ data_attribute :stuff, { :serialized_column => :data, :default => 1 }
25
+ data_attribute :things, { :default => "two" }
26
+ end
@@ -0,0 +1,30 @@
1
+ ActiveRecord::Base.silence do
2
+ ActiveRecord::Migration.verbose = false
3
+
4
+ ActiveRecord::Schema.define do
5
+ create_table "basics" do |t|
6
+ t.text :data
7
+ end
8
+
9
+ create_table "two_attributes" do |t|
10
+ t.text :data
11
+ end
12
+
13
+ create_table "different_serialized_attribute" do |t|
14
+ t.text :more_data
15
+ end
16
+
17
+ create_table "two_serialized_attributes" do |t|
18
+ t.text :data
19
+ t.text :more_data
20
+ end
21
+
22
+ create_table "non_serialized_attributes" do |t|
23
+ t.text :data
24
+ end
25
+
26
+ create_table "default_non_serialized_attributes" do |t|
27
+ t.text :more_data
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,9 @@
1
+ require 'test/unit'
2
+ require 'active_record'
3
+
4
+ require File.expand_path( File.join( File.dirname( __FILE__ ), '..', 'lib', 'data-attributes' ) )
5
+
6
+ ActiveRecord::Base.establish_connection(:adapter => "sqlite3", :database => ":memory:")
7
+
8
+ require File.expand_path( File.join( File.dirname( __FILE__ ), 'schema' ) )
9
+ require File.expand_path( File.join( File.dirname( __FILE__ ), 'models' ) )
metadata ADDED
@@ -0,0 +1,104 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: data-attributes
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: "0.2"
6
+ platform: ruby
7
+ authors:
8
+ - Les Fletcher
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2011-04-10 00:00:00 -07:00
14
+ default_executable:
15
+ dependencies:
16
+ - !ruby/object:Gem::Dependency
17
+ name: test-unit
18
+ prerelease: false
19
+ requirement: &id001 !ruby/object:Gem::Requirement
20
+ none: false
21
+ requirements:
22
+ - - ~>
23
+ - !ruby/object:Gem::Version
24
+ version: 2.2.0
25
+ type: :development
26
+ version_requirements: *id001
27
+ - !ruby/object:Gem::Dependency
28
+ name: sqlite3
29
+ prerelease: false
30
+ requirement: &id002 !ruby/object:Gem::Requirement
31
+ none: false
32
+ requirements:
33
+ - - ~>
34
+ - !ruby/object:Gem::Version
35
+ version: 1.3.0
36
+ type: :development
37
+ version_requirements: *id002
38
+ - !ruby/object:Gem::Dependency
39
+ name: activerecord
40
+ prerelease: false
41
+ requirement: &id003 !ruby/object:Gem::Requirement
42
+ none: false
43
+ requirements:
44
+ - - ~>
45
+ - !ruby/object:Gem::Version
46
+ version: 3.0.0
47
+ type: :runtime
48
+ version_requirements: *id003
49
+ description: A gem for easy access to attributes stored in a serialized data hash
50
+ email:
51
+ - les.fletcher@gmail.com
52
+ executables: []
53
+
54
+ extensions: []
55
+
56
+ extra_rdoc_files: []
57
+
58
+ files:
59
+ - .gitignore
60
+ - Gemfile
61
+ - README.rdoc
62
+ - Rakefile
63
+ - data-attributes.gemspec
64
+ - lib/data-attributes.rb
65
+ - lib/data-attributes/data_attributes.rb
66
+ - lib/data-attributes/errors.rb
67
+ - lib/data-attributes/version.rb
68
+ - test/data_attributes_test.rb
69
+ - test/models.rb
70
+ - test/schema.rb
71
+ - test/test_helper.rb
72
+ has_rdoc: true
73
+ homepage: ""
74
+ licenses: []
75
+
76
+ post_install_message:
77
+ rdoc_options: []
78
+
79
+ require_paths:
80
+ - lib
81
+ required_ruby_version: !ruby/object:Gem::Requirement
82
+ none: false
83
+ requirements:
84
+ - - ">="
85
+ - !ruby/object:Gem::Version
86
+ version: "0"
87
+ required_rubygems_version: !ruby/object:Gem::Requirement
88
+ none: false
89
+ requirements:
90
+ - - ">="
91
+ - !ruby/object:Gem::Version
92
+ version: "0"
93
+ requirements: []
94
+
95
+ rubyforge_project: data-attributes
96
+ rubygems_version: 1.5.0
97
+ signing_key:
98
+ specification_version: 3
99
+ summary: A gem for easy access to attributes stored in a serialized data hash
100
+ test_files:
101
+ - test/data_attributes_test.rb
102
+ - test/models.rb
103
+ - test/schema.rb
104
+ - test/test_helper.rb