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 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 => class_type.new
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
@@ -13,6 +13,7 @@ module ActiveRecord
13
13
 
14
14
  extend Tableless::ClassMethods
15
15
  include Tableless::InstanceMethods
16
+ include Validatable
16
17
 
17
18
  #
18
19
  #
@@ -53,11 +53,9 @@ module Tableless
53
53
  #
54
54
  #
55
55
  def cast(attribute_name, value)
56
- return nil if value.nil?
56
+ type = self.attributes[attribute_name.to_s][:type] || :string
57
57
 
58
- type = self.attributes[attribute_name.to_s][:type]
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
- # Ensures that when merging with a given hash
72
- # all the keys are stringified as the keys are always handled
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
- super hash.stringify_keys
75
+ raise NoMethodError
77
76
  end
78
77
 
79
78
  end
@@ -1,3 +1,3 @@
1
1
  module TablelessModel
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.3"
3
3
  end
@@ -1,93 +1,159 @@
1
- require "rubygems"
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 TestTablelessModel do
11
- it "has the accessor 'attributes', originally an empty hash" do
12
- TestTablelessModel.must_respond_to("attributes")
13
- TestTablelessModel.attributes.must_be_kind_of Hash
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 "responds to 'attribute'" do
18
- TestTablelessModel.must_respond_to "attribute"
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
- TestTablelessModel.must_respond_to "attribute"
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
- describe "'attribute' macro" do
27
- before do
28
- TestTablelessModel.class_eval do
29
- attribute :test_attribute
30
- end
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
- it "adds a key-value pair to the attributes accessor" do
34
- TestTablelessModel.attributes.wont_be_empty
35
- TestTablelessModel.attributes.key?("test_attribute").must_equal true, "An attribute named 'test_attribute' should have been defined"
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
- describe "An instance of TablelessModel" do
40
- before do
41
- TestTablelessModel.class_eval do
42
- attribute :test_attribute
43
- end
44
- end
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
- test_values = [ "test", 1234, true, "1234.12", "2011-01-02 15:23" ]
49
-
50
- [ :string, :integer, :float, :decimal, :time, :date, :datetime, :boolean ].each do |type|
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
- instance = TestTablelessModel.new
56
-
57
- # instance.must_respond_to "test_attribute", "Getter for test_attribute should have been defined"
58
- # instance.must_respond_to "test_attribute=", "Setter for test_attribute should have been defined"
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
- type_name = case type
61
- when :datetime then :date_time
62
- when :decimal then :big_decimal
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
- # excluding some test values that would always faild depending on the type
68
- exclude_test_values = case type
69
- when :decimal then [ "test", true ]
70
- when :time then [ 1234, true ]
71
- when :date then [ "test", 1234, true, "1234.12" ]
72
- when :datetime then [ "test", 1234, true ]
73
- else []
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
- end
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
@@ -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
@@ -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 = "http://rubygems.org/gems/tableless_model"
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: 27
4
+ hash: 25
5
5
  prerelease:
6
6
  segments:
7
7
  - 0
8
8
  - 0
9
- - 2
10
- version: 0.0.2
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: activerecord
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: minitest
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: http://rubygems.org/gems/tableless_model
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