tableless_model 0.0.2 → 0.0.3
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/README.rdoc +39 -1
- data/lib/activerecord/base/class_methods.rb +1 -1
- data/lib/tableless_model.rb +1 -0
- data/lib/tableless_model/class_methods.rb +2 -4
- data/lib/tableless_model/instance_methods.rb +3 -4
- data/lib/tableless_model/version.rb +1 -1
- data/spec/tableless_model_spec.rb +128 -62
- data/spec/test_helper.rb +154 -0
- data/tableless_model.gemspec +2 -3
- metadata +8 -6
data/README.rdoc
CHANGED
@@ -15,7 +15,8 @@ Tableless Model is available as a Rubygem:
|
|
15
15
|
|
16
16
|
gem install tableless_model
|
17
17
|
|
18
|
-
|
18
|
+
(current version: 0.0.3)
|
19
|
+
|
19
20
|
|
20
21
|
== Usage
|
21
22
|
|
@@ -119,6 +120,43 @@ And this is how the content of the serialized column would look like in the data
|
|
119
120
|
noindex: false
|
120
121
|
|
121
122
|
|
123
|
+
== Validations
|
124
|
+
|
125
|
+
Tableless Model uses the gem Validatable to support validations methods and callbacks (such as "after_validation").
|
126
|
+
Note: it currently uses the Rails 2.x syntax only.
|
127
|
+
|
128
|
+
Example:
|
129
|
+
|
130
|
+
|
131
|
+
class SeoOptions < ActiveRecord::TablelessModel
|
132
|
+
|
133
|
+
attribute :title_tag, :type => :string, :default => ""
|
134
|
+
attribute :meta_description, :type => :string, :default => ""
|
135
|
+
attribute :meta_keywords, :type => :string, :default => ""
|
136
|
+
attribute :noindex, :type => :boolean, :default => false
|
137
|
+
attribute :nofollow, :type => :boolean, :default => false
|
138
|
+
attribute :noarchive, :type => :boolean, :default => false
|
139
|
+
|
140
|
+
validates_presence_of :meta_keywords
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
|
145
|
+
Testing:
|
146
|
+
|
147
|
+
x = SeoOptionsSettings.new
|
148
|
+
=> <#SeoOptions meta_description="" meta_keywords="" noarchive=false nofollow=false noindex=false
|
149
|
+
title_tag="">
|
150
|
+
|
151
|
+
x.valid?
|
152
|
+
=> false
|
153
|
+
|
154
|
+
x.meta_keywords = "test"
|
155
|
+
=> "test"
|
156
|
+
|
157
|
+
x.valid?
|
158
|
+
=> true
|
159
|
+
|
122
160
|
|
123
161
|
== TODO
|
124
162
|
|
@@ -35,7 +35,7 @@ module Base
|
|
35
35
|
|
36
36
|
# Making sure the serialized column contains a new instance of the tableless model
|
37
37
|
# if it hasn't been set yet
|
38
|
-
default_value_for column_name
|
38
|
+
default_value_for column_name, class_type.new
|
39
39
|
|
40
40
|
# Telling AR that the column has to store an instance of the given tableless model in
|
41
41
|
# YAML serialized format
|
data/lib/tableless_model.rb
CHANGED
@@ -53,11 +53,9 @@ module Tableless
|
|
53
53
|
#
|
54
54
|
#
|
55
55
|
def cast(attribute_name, value)
|
56
|
-
|
56
|
+
type = self.attributes[attribute_name.to_s][:type] || :string
|
57
57
|
|
58
|
-
|
59
|
-
|
60
|
-
return value if type.nil?
|
58
|
+
return nil if value.nil? && ![:string, :integer, :boolean, :float, :decimal].include?(type)
|
61
59
|
|
62
60
|
begin
|
63
61
|
case type
|
@@ -68,12 +68,11 @@ module Tableless
|
|
68
68
|
|
69
69
|
#
|
70
70
|
#
|
71
|
-
#
|
72
|
-
#
|
73
|
-
# as strings in the tableless model
|
71
|
+
# Since the object should only allow the defined attributes
|
72
|
+
# merging is forbidden
|
74
73
|
#
|
75
74
|
def merge(hash)
|
76
|
-
|
75
|
+
raise NoMethodError
|
77
76
|
end
|
78
77
|
|
79
78
|
end
|
@@ -1,93 +1,159 @@
|
|
1
|
-
require "
|
1
|
+
require File.join(File.dirname(__FILE__), "test_helper")
|
2
|
+
|
2
3
|
require "active_record"
|
3
4
|
require "lib/tableless_model"
|
4
5
|
require 'minitest/autorun'
|
5
6
|
|
6
|
-
class TestTablelessModel < ActiveRecord::TablelessModel
|
7
|
-
end
|
8
|
-
|
9
7
|
|
10
|
-
describe
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
TestTablelessModel.attributes.must_be_empty
|
8
|
+
describe "A class inheriting from ActiveRecord::TablelessModel " do
|
9
|
+
before do
|
10
|
+
class TestClass1 < ActiveRecord::TablelessModel
|
11
|
+
end
|
15
12
|
end
|
16
13
|
|
17
|
-
it "
|
18
|
-
|
14
|
+
it "has the accessor 'attributes', which is originally an empty hash" do
|
15
|
+
TestClass1.must_respond_to("attributes")
|
16
|
+
TestClass1.attributes.must_be_kind_of Hash
|
17
|
+
TestClass1.attributes.must_be_empty
|
19
18
|
end
|
20
19
|
|
21
20
|
it "responds to 'attribute', 'cast'" do
|
22
|
-
|
23
|
-
TestTablelessModel.must_respond_to "cast"
|
21
|
+
["attribute", "cast"].each {|method_name| TestClass1.must_respond_to method_name }
|
24
22
|
end
|
23
|
+
end
|
25
24
|
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
25
|
+
describe "The 'attribute' macro" do
|
26
|
+
before do
|
27
|
+
class TestClass < ActiveRecord::TablelessModel
|
28
|
+
attribute :test_attribute
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
it "adds a key-value pair to the attributes accessor" do
|
33
|
+
TestClass.attributes.wont_be_empty
|
34
|
+
TestClass.attributes.key?("test_attribute").must_equal true, "An attribute named 'test_attribute' should have been defined"
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
describe "An instance of TablelessModel" do
|
39
|
+
before do
|
40
|
+
class TestClass2 < ActiveRecord::TablelessModel
|
41
|
+
attribute :test_attribute
|
42
|
+
attribute :test_attribute_with_default_value, :default => "default value"
|
43
|
+
attribute :test_attribute_with_type_and_default_value, :default => "003xxx", :type => :integer
|
31
44
|
end
|
32
45
|
|
33
|
-
|
34
|
-
|
35
|
-
|
46
|
+
@instance = TestClass2.new
|
47
|
+
end
|
48
|
+
|
49
|
+
it "has a getter and a setter for each defined attribute" do
|
50
|
+
[:test_attribute, :test_attribute_with_default_value, :test_attribute_with_type_and_default_value].each do |attribute_name|
|
51
|
+
@instance.must_respond_to attribute_name, "Getter for #{attribute_name} should have been defined"
|
52
|
+
@instance.must_respond_to "#{attribute_name}=", "Setter for #{attribute_name} should have been defined"
|
36
53
|
end
|
37
54
|
end
|
55
|
+
|
56
|
+
it "assigns the default value to an attribute that has not yet been set, if a default value has been specified" do
|
57
|
+
@instance.test_attribute_with_default_value.must_equal "default value"
|
58
|
+
@instance.test_attribute_with_type_and_default_value.must_equal 3
|
59
|
+
end
|
60
|
+
|
61
|
+
it "should allow overriding the default values" do
|
62
|
+
instance = TestClass2.new( :test_attribute_with_default_value => "changed value" )
|
63
|
+
instance.test_attribute_with_default_value.must_equal "changed value"
|
64
|
+
end
|
65
|
+
|
66
|
+
it "assumes an attribute's data type is string if the type has not been specified" do
|
67
|
+
@instance.test_attribute.must_be_kind_of String
|
68
|
+
end
|
38
69
|
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
70
|
+
it "assigns the expected not-nil value to an attribute if a default value hasn't been specified" do
|
71
|
+
@instance.test_attribute.must_equal ""
|
72
|
+
end
|
73
|
+
|
74
|
+
it "does not allow access to undefined attributes" do
|
75
|
+
@instance.wont_respond_to "unknown_attribute"
|
76
|
+
@instance.wont_respond_to "unknown_attribute="
|
77
|
+
|
78
|
+
proc { @instance["unknown_attribute"] }.must_raise(NoMethodError)
|
79
|
+
proc { @instance["unknown_attribute="] }.must_raise(NoMethodError)
|
80
|
+
end
|
81
|
+
|
82
|
+
it "shows the expected output on inspect" do
|
83
|
+
@instance.inspect.must_equal "<#TestClass2 test_attribute=\"\" test_attribute_with_default_value=\"default value\" test_attribute_with_type_and_default_value=3>"
|
84
|
+
end
|
45
85
|
|
46
|
-
it "tries to enforce type casting if a type has been specified for an attribute" do
|
47
86
|
|
48
|
-
|
49
|
-
|
50
|
-
|
87
|
+
it "should not allow merging" do
|
88
|
+
proc { @instance.merge(:new_symbol_key => "new_symbol_key") }.must_raise NoMethodError
|
89
|
+
end
|
90
|
+
|
91
|
+
end
|
51
92
|
|
52
|
-
# temporarily changing type
|
53
|
-
TestTablelessModel.attributes["test_attribute"][:type] = type
|
54
93
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
94
|
+
describe "An instance of TablelessModel" do
|
95
|
+
before do
|
96
|
+
class TestClass3 < ActiveRecord::TablelessModel
|
97
|
+
attribute :typed_test_attribute, :type => :integer
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
it "tries to enforce type casting if a type has been specified for an attribute" do
|
59
102
|
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
else type
|
64
|
-
end
|
103
|
+
test_values = [ "test", 1234, true, "1234.12", "2011-01-02 15:23" ]
|
104
|
+
|
105
|
+
[ :string, :integer, :float, :decimal, :time, :date, :datetime, :boolean ].each do |type|
|
65
106
|
|
107
|
+
# temporarily changing type
|
108
|
+
TestClass3.attributes["typed_test_attribute"][:type] = type
|
66
109
|
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
110
|
+
instance = TestClass3.new
|
111
|
+
|
112
|
+
type_name = case type
|
113
|
+
when :datetime then :date_time
|
114
|
+
when :decimal then :big_decimal
|
115
|
+
else type
|
116
|
+
end
|
117
|
+
|
118
|
+
|
119
|
+
# excluding some test values that would always fail depending on the type
|
120
|
+
exclude_test_values = case type
|
121
|
+
when :decimal then [ "test", true ]
|
122
|
+
when :time then [ 1234, true ]
|
123
|
+
when :date then [ "test", 1234, true, "1234.12" ]
|
124
|
+
when :datetime then [ "test", 1234, true ]
|
125
|
+
else []
|
126
|
+
end
|
127
|
+
|
128
|
+
(test_values - exclude_test_values).each do |value|
|
129
|
+
instance.typed_test_attribute = value
|
130
|
+
|
131
|
+
if type == :boolean
|
132
|
+
[true, false].include?(instance.typed_test_attribute).must_equal true, "Expected #{instance.typed_test_attribute.inspect} to be boolean, not #{type.class}"
|
133
|
+
else
|
134
|
+
instance.typed_test_attribute.must_be_kind_of type_name.to_s.classify.constantize
|
74
135
|
end
|
136
|
+
end
|
75
137
|
|
138
|
+
end
|
139
|
+
end
|
140
|
+
end
|
76
141
|
|
77
|
-
(test_values - exclude_test_values).each do |value|
|
78
|
-
|
79
|
-
instance.test_attribute = value
|
80
|
-
|
81
|
-
if type == :boolean
|
82
|
-
[true, false].include?(instance.test_attribute).must_equal true, "Expected #{instance.test_attribute.inspect} to be boolean, not #{type.class}"
|
83
|
-
else
|
84
|
-
instance.test_attribute.must_be_kind_of type_name.to_s.classify.constantize
|
85
|
-
end
|
86
|
-
|
87
|
-
end
|
88
142
|
|
89
|
-
|
143
|
+
describe "An ActiveRecord::Base model" do
|
144
|
+
before do
|
145
|
+
class ModelOptions < ActiveRecord::TablelessModel
|
146
|
+
attribute :aaa, :default => 111
|
147
|
+
attribute :bbb, :default => "bbb"
|
148
|
+
end
|
149
|
+
|
150
|
+
class Model < ActiveRecord::Base
|
151
|
+
# has_tableless :options => ModelOptions
|
90
152
|
end
|
91
153
|
end
|
92
|
-
|
154
|
+
|
155
|
+
it "responds to default_value_for, has_tableless" do
|
156
|
+
[:default_value_for, :has_tableless].each {|method| Model.must_respond_to(method)}
|
157
|
+
end
|
158
|
+
|
93
159
|
end
|
data/spec/test_helper.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'minitest/unit'
|
3
|
+
require 'minitest/spec'
|
4
|
+
require 'ansi'
|
5
|
+
|
6
|
+
class MiniTest::Unit
|
7
|
+
include ANSI::Code
|
8
|
+
|
9
|
+
PADDING_SIZE = 4
|
10
|
+
|
11
|
+
def run(args = [])
|
12
|
+
@verbose = true
|
13
|
+
|
14
|
+
filter = if args.first =~ /^(-n|--name)$/ then
|
15
|
+
args.shift
|
16
|
+
arg = args.shift
|
17
|
+
arg =~ /\/(.*)\// ? Regexp.new($1) : arg
|
18
|
+
else
|
19
|
+
/./ # anything - ^test_ already filtered by #tests
|
20
|
+
end
|
21
|
+
|
22
|
+
@@out.puts "Loaded suite #{$0.sub(/\.rb$/, '')}\nStarted"
|
23
|
+
|
24
|
+
start = Time.now
|
25
|
+
run_test_suites filter
|
26
|
+
|
27
|
+
@@out.puts
|
28
|
+
@@out.puts "Finished in #{'%.6f' % (Time.now - start)} seconds."
|
29
|
+
|
30
|
+
@@out.puts
|
31
|
+
|
32
|
+
@@out.print "%d tests, " % test_count
|
33
|
+
@@out.print "%d assertions, " % assertion_count
|
34
|
+
@@out.print red { "%d failures, " % failures }
|
35
|
+
@@out.print yellow { "%d errors, " % errors }
|
36
|
+
@@out.puts cyan { "%d skips" % skips}
|
37
|
+
|
38
|
+
return failures + errors if @test_count > 0 # or return nil...
|
39
|
+
end
|
40
|
+
|
41
|
+
# Overwrite #run_test_suites so that it prints out reports
|
42
|
+
# as errors are generated.
|
43
|
+
def run_test_suites(filter = /./)
|
44
|
+
@test_count, @assertion_count = 0, 0
|
45
|
+
old_sync, @@out.sync = @@out.sync, true if @@out.respond_to? :sync=
|
46
|
+
TestCase.test_suites.each do |suite|
|
47
|
+
test_cases = suite.test_methods.grep(filter)
|
48
|
+
if test_cases.size > 0
|
49
|
+
@@out.print "\n#{suite}:\n"
|
50
|
+
end
|
51
|
+
|
52
|
+
test_cases.each do |test|
|
53
|
+
inst = suite.new test
|
54
|
+
inst._assertions = 0
|
55
|
+
|
56
|
+
t = Time.now
|
57
|
+
|
58
|
+
@broken = nil
|
59
|
+
|
60
|
+
@@out.print(case inst.run(self)
|
61
|
+
when :pass
|
62
|
+
@broken = false
|
63
|
+
green { pad_with_size "PASS" }
|
64
|
+
when :error
|
65
|
+
@broken = true
|
66
|
+
yellow { pad_with_size "ERROR" }
|
67
|
+
when :fail
|
68
|
+
@broken = true
|
69
|
+
red { pad_with_size "FAIL" }
|
70
|
+
when :skip
|
71
|
+
@broken = false
|
72
|
+
cyan { pad_with_size "SKIP" }
|
73
|
+
end)
|
74
|
+
|
75
|
+
|
76
|
+
@@out.print " #{test.humanize.gsub(/Test\s\d+\s(.*)/,"\\1")} "
|
77
|
+
@@out.print " (%.2fs) " % (Time.now - t)
|
78
|
+
|
79
|
+
if @broken
|
80
|
+
@@out.puts
|
81
|
+
|
82
|
+
report = @report.last
|
83
|
+
@@out.puts pad(report[:message], 10)
|
84
|
+
trace = MiniTest::filter_backtrace(report[:exception].backtrace).first
|
85
|
+
@@out.print pad(trace, 10)
|
86
|
+
|
87
|
+
@@out.puts
|
88
|
+
end
|
89
|
+
|
90
|
+
@@out.puts
|
91
|
+
@test_count += 1
|
92
|
+
@assertion_count += inst._assertions
|
93
|
+
end
|
94
|
+
end
|
95
|
+
@@out.sync = old_sync if @@out.respond_to? :sync=
|
96
|
+
[@test_count, @assertion_count]
|
97
|
+
end
|
98
|
+
|
99
|
+
def pad(str, size=PADDING_SIZE)
|
100
|
+
" " * size + str
|
101
|
+
end
|
102
|
+
|
103
|
+
def pad_with_size(str)
|
104
|
+
pad("%5s" % str)
|
105
|
+
end
|
106
|
+
|
107
|
+
# Overwrite #puke method so that is stores a hash
|
108
|
+
# with :message and :exception keys.
|
109
|
+
def puke(klass, meth, e)
|
110
|
+
result = nil
|
111
|
+
msg = case e
|
112
|
+
when MiniTest::Skip
|
113
|
+
@skips += 1
|
114
|
+
result = :skip
|
115
|
+
e.message
|
116
|
+
when MiniTest::Assertion
|
117
|
+
@failures += 1
|
118
|
+
result = :fail
|
119
|
+
e.message
|
120
|
+
else
|
121
|
+
@errors += 1
|
122
|
+
result = :error
|
123
|
+
"#{e.class}: #{e.message}\n"
|
124
|
+
end
|
125
|
+
|
126
|
+
@report << {:message => msg, :exception => e}
|
127
|
+
result
|
128
|
+
end
|
129
|
+
|
130
|
+
|
131
|
+
class TestCase
|
132
|
+
# Overwrite #run method so that is uses symbols
|
133
|
+
# as return values rather than characters.
|
134
|
+
def run(runner)
|
135
|
+
result = :pass
|
136
|
+
begin
|
137
|
+
@passed = nil
|
138
|
+
self.setup
|
139
|
+
self.send self.__name__
|
140
|
+
@passed = true
|
141
|
+
rescue Exception => e
|
142
|
+
@passed = false
|
143
|
+
result = runner.puke(self.class, self.__name__, e)
|
144
|
+
ensure
|
145
|
+
begin
|
146
|
+
self.teardown
|
147
|
+
rescue Exception => e
|
148
|
+
result = runner.puke(self.class, self.__name__, e)
|
149
|
+
end
|
150
|
+
end
|
151
|
+
result
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
data/tableless_model.gemspec
CHANGED
@@ -9,14 +9,13 @@ Gem::Specification.new do |s|
|
|
9
9
|
s.platform = Gem::Platform::RUBY
|
10
10
|
s.authors = ["Vito Botta"]
|
11
11
|
s.email = ["vito@botta.name"]
|
12
|
-
s.homepage = "
|
12
|
+
s.homepage = "https://github.com/vitobotta/tableless_model"
|
13
13
|
s.summary = %q{A serialisable and validatable table-less model with support for associations, useful to store settings, options, etc in a serialized form in a parent object}
|
14
14
|
s.description = %q{A serialisable and validatable table-less model with support for associations, useful to store settings, options, etc in a serialized form in a parent object}
|
15
15
|
|
16
16
|
s.add_dependency "validatable"
|
17
|
-
|
18
|
-
s.add_development_dependency "activerecord"
|
19
17
|
s.add_development_dependency "minitest"
|
18
|
+
s.add_development_dependency "ansi"
|
20
19
|
|
21
20
|
s.rubyforge_project = "tableless_model"
|
22
21
|
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tableless_model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 25
|
5
5
|
prerelease:
|
6
6
|
segments:
|
7
7
|
- 0
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version: 0.0.
|
9
|
+
- 3
|
10
|
+
version: 0.0.3
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Vito Botta
|
@@ -33,7 +33,7 @@ dependencies:
|
|
33
33
|
type: :runtime
|
34
34
|
version_requirements: *id001
|
35
35
|
- !ruby/object:Gem::Dependency
|
36
|
-
name:
|
36
|
+
name: minitest
|
37
37
|
prerelease: false
|
38
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
@@ -47,7 +47,7 @@ dependencies:
|
|
47
47
|
type: :development
|
48
48
|
version_requirements: *id002
|
49
49
|
- !ruby/object:Gem::Dependency
|
50
|
-
name:
|
50
|
+
name: ansi
|
51
51
|
prerelease: false
|
52
52
|
requirement: &id003 !ruby/object:Gem::Requirement
|
53
53
|
none: false
|
@@ -81,9 +81,10 @@ files:
|
|
81
81
|
- lib/tableless_model/instance_methods.rb
|
82
82
|
- lib/tableless_model/version.rb
|
83
83
|
- spec/tableless_model_spec.rb
|
84
|
+
- spec/test_helper.rb
|
84
85
|
- tableless_model.gemspec
|
85
86
|
has_rdoc: true
|
86
|
-
homepage:
|
87
|
+
homepage: https://github.com/vitobotta/tableless_model
|
87
88
|
licenses: []
|
88
89
|
|
89
90
|
post_install_message:
|
@@ -118,3 +119,4 @@ specification_version: 3
|
|
118
119
|
summary: A serialisable and validatable table-less model with support for associations, useful to store settings, options, etc in a serialized form in a parent object
|
119
120
|
test_files:
|
120
121
|
- spec/tableless_model_spec.rb
|
122
|
+
- spec/test_helper.rb
|