tableless_model 0.0.2 → 0.0.3

Sign up to get free protection for your applications and to get access to all the features.
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