tableless_model 0.0.7 → 0.0.8
Sign up to get free protection for your applications and to get access to all the features.
- data/.rspec +2 -0
- data/Gemfile.lock +29 -11
- data/README.md +274 -0
- data/lib/activerecord/base/class_methods.rb +8 -1
- data/lib/activerecord/base/instance_methods.rb +15 -0
- data/lib/tableless_model.rb +7 -2
- data/lib/tableless_model/class_methods.rb +4 -5
- data/lib/tableless_model/instance_methods.rb +29 -7
- data/lib/tableless_model/version.rb +1 -1
- data/spec/spec_helper.rb +7 -0
- data/spec/tableless_model_spec.rb +203 -193
- data/tableless_model.gemspec +3 -2
- metadata +34 -11
- data/README.rdoc +0 -170
- data/spec/test_helper.rb +0 -157
data/tableless_model.gemspec
CHANGED
@@ -14,9 +14,10 @@ Gem::Specification.new do |s|
|
|
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
|
-
s.add_development_dependency "minitest"
|
18
|
-
s.add_development_dependency "ansi"
|
19
17
|
s.add_development_dependency "sqlite3"
|
18
|
+
s.add_development_dependency "activerecord"
|
19
|
+
s.add_development_dependency "rspec"
|
20
|
+
s.add_development_dependency "timecop"
|
20
21
|
|
21
22
|
s.rubyforge_project = "tableless_model"
|
22
23
|
|
metadata
CHANGED
@@ -1,12 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: tableless_model
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
|
4
|
+
hash: 15
|
5
|
+
prerelease:
|
5
6
|
segments:
|
6
7
|
- 0
|
7
8
|
- 0
|
8
|
-
-
|
9
|
-
version: 0.0.
|
9
|
+
- 8
|
10
|
+
version: 0.0.8
|
10
11
|
platform: ruby
|
11
12
|
authors:
|
12
13
|
- Vito Botta
|
@@ -14,7 +15,7 @@ autorequire:
|
|
14
15
|
bindir: bin
|
15
16
|
cert_chain: []
|
16
17
|
|
17
|
-
date: 2011-
|
18
|
+
date: 2011-07-24 00:00:00 +01:00
|
18
19
|
default_executable:
|
19
20
|
dependencies:
|
20
21
|
- !ruby/object:Gem::Dependency
|
@@ -25,50 +26,68 @@ dependencies:
|
|
25
26
|
requirements:
|
26
27
|
- - ">="
|
27
28
|
- !ruby/object:Gem::Version
|
29
|
+
hash: 3
|
28
30
|
segments:
|
29
31
|
- 0
|
30
32
|
version: "0"
|
31
33
|
type: :runtime
|
32
34
|
version_requirements: *id001
|
33
35
|
- !ruby/object:Gem::Dependency
|
34
|
-
name:
|
36
|
+
name: sqlite3
|
35
37
|
prerelease: false
|
36
38
|
requirement: &id002 !ruby/object:Gem::Requirement
|
37
39
|
none: false
|
38
40
|
requirements:
|
39
41
|
- - ">="
|
40
42
|
- !ruby/object:Gem::Version
|
43
|
+
hash: 3
|
41
44
|
segments:
|
42
45
|
- 0
|
43
46
|
version: "0"
|
44
47
|
type: :development
|
45
48
|
version_requirements: *id002
|
46
49
|
- !ruby/object:Gem::Dependency
|
47
|
-
name:
|
50
|
+
name: activerecord
|
48
51
|
prerelease: false
|
49
52
|
requirement: &id003 !ruby/object:Gem::Requirement
|
50
53
|
none: false
|
51
54
|
requirements:
|
52
55
|
- - ">="
|
53
56
|
- !ruby/object:Gem::Version
|
57
|
+
hash: 3
|
54
58
|
segments:
|
55
59
|
- 0
|
56
60
|
version: "0"
|
57
61
|
type: :development
|
58
62
|
version_requirements: *id003
|
59
63
|
- !ruby/object:Gem::Dependency
|
60
|
-
name:
|
64
|
+
name: rspec
|
61
65
|
prerelease: false
|
62
66
|
requirement: &id004 !ruby/object:Gem::Requirement
|
63
67
|
none: false
|
64
68
|
requirements:
|
65
69
|
- - ">="
|
66
70
|
- !ruby/object:Gem::Version
|
71
|
+
hash: 3
|
67
72
|
segments:
|
68
73
|
- 0
|
69
74
|
version: "0"
|
70
75
|
type: :development
|
71
76
|
version_requirements: *id004
|
77
|
+
- !ruby/object:Gem::Dependency
|
78
|
+
name: timecop
|
79
|
+
prerelease: false
|
80
|
+
requirement: &id005 !ruby/object:Gem::Requirement
|
81
|
+
none: false
|
82
|
+
requirements:
|
83
|
+
- - ">="
|
84
|
+
- !ruby/object:Gem::Version
|
85
|
+
hash: 3
|
86
|
+
segments:
|
87
|
+
- 0
|
88
|
+
version: "0"
|
89
|
+
type: :development
|
90
|
+
version_requirements: *id005
|
72
91
|
description: 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
|
73
92
|
email:
|
74
93
|
- vito@botta.name
|
@@ -80,17 +99,19 @@ extra_rdoc_files: []
|
|
80
99
|
|
81
100
|
files:
|
82
101
|
- .gitignore
|
102
|
+
- .rspec
|
83
103
|
- Gemfile
|
84
104
|
- Gemfile.lock
|
85
|
-
- README.
|
105
|
+
- README.md
|
86
106
|
- Rakefile
|
87
107
|
- lib/activerecord/base/class_methods.rb
|
108
|
+
- lib/activerecord/base/instance_methods.rb
|
88
109
|
- lib/tableless_model.rb
|
89
110
|
- lib/tableless_model/class_methods.rb
|
90
111
|
- lib/tableless_model/instance_methods.rb
|
91
112
|
- lib/tableless_model/version.rb
|
113
|
+
- spec/spec_helper.rb
|
92
114
|
- spec/tableless_model_spec.rb
|
93
|
-
- spec/test_helper.rb
|
94
115
|
- tableless_model.gemspec
|
95
116
|
has_rdoc: true
|
96
117
|
homepage: https://github.com/vitobotta/tableless_model
|
@@ -106,6 +127,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
106
127
|
requirements:
|
107
128
|
- - ">="
|
108
129
|
- !ruby/object:Gem::Version
|
130
|
+
hash: 3
|
109
131
|
segments:
|
110
132
|
- 0
|
111
133
|
version: "0"
|
@@ -114,16 +136,17 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
114
136
|
requirements:
|
115
137
|
- - ">="
|
116
138
|
- !ruby/object:Gem::Version
|
139
|
+
hash: 3
|
117
140
|
segments:
|
118
141
|
- 0
|
119
142
|
version: "0"
|
120
143
|
requirements: []
|
121
144
|
|
122
145
|
rubyforge_project: tableless_model
|
123
|
-
rubygems_version: 1.
|
146
|
+
rubygems_version: 1.6.2
|
124
147
|
signing_key:
|
125
148
|
specification_version: 3
|
126
149
|
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
|
127
150
|
test_files:
|
151
|
+
- spec/spec_helper.rb
|
128
152
|
- spec/tableless_model_spec.rb
|
129
|
-
- spec/test_helper.rb
|
data/README.rdoc
DELETED
@@ -1,170 +0,0 @@
|
|
1
|
-
= Tableless Model
|
2
|
-
|
3
|
-
This is an extended Hash that has a defined collection of method-like attributes, and only these attributes can be set or read from the hash.
|
4
|
-
Optionally, you can also set default values and enforce data types for these attributes.
|
5
|
-
|
6
|
-
Tableless Model behaves in a similar way to normal ActiveRecord models in that it also supports validations and can be useful, for example, to reduce database complexity in some cases, by removing associations and therefore tables.
|
7
|
-
|
8
|
-
In particular, by using Tableless Model, you could save tables whenever you have one to one associations between a parent model and a child model containing options, settings, debugging information or any other collection of attributes that belongs uniquely to a single parent object.
|
9
|
-
|
10
|
-
Removing database tables also means reducing the number of queries to fetch associations, therefore it can also help a little bit with performance.
|
11
|
-
|
12
|
-
|
13
|
-
== Installation
|
14
|
-
|
15
|
-
Tableless Model is available as a Rubygem:
|
16
|
-
|
17
|
-
gem install tableless_model
|
18
|
-
|
19
|
-
(current version: 0.0.7)
|
20
|
-
|
21
|
-
|
22
|
-
== Usage
|
23
|
-
|
24
|
-
For example's sake, say we have these two models:
|
25
|
-
|
26
|
-
1)
|
27
|
-
|
28
|
-
class Page < ActiveRecord::Base
|
29
|
-
|
30
|
-
# having columns such as id, title, etc
|
31
|
-
|
32
|
-
has_one :seo_options
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
|
-
2)
|
37
|
-
|
38
|
-
class SeoOptions < ActiveRecord::Base
|
39
|
-
|
40
|
-
set_table_name "seo_options"
|
41
|
-
|
42
|
-
# having columns such as id, title_tag, meta_description, meta_keywords,
|
43
|
-
# noindex, nofollow, noarchive, page_id
|
44
|
-
|
45
|
-
belongs_to :page
|
46
|
-
|
47
|
-
end
|
48
|
-
|
49
|
-
|
50
|
-
So that each instance of Page has its own SEO options, and these options/settings only belong to a page, so we have a one to one association, and our database will have the tables "pages", and "seo_options".
|
51
|
-
|
52
|
-
Using Tableless Model, we could remove the association and the table seo_options altogether, by storing those options in a column of the pages table, in a YAML-serialized form. So the models become:
|
53
|
-
|
54
|
-
|
55
|
-
1)
|
56
|
-
|
57
|
-
class Page < ActiveRecord::Base
|
58
|
-
|
59
|
-
# having columns such as id, title, seo, etc
|
60
|
-
|
61
|
-
has_tableless :seo => SeoOptions
|
62
|
-
|
63
|
-
end
|
64
|
-
|
65
|
-
2)
|
66
|
-
|
67
|
-
class SeoOptions < ActiveRecord::TablelessModel
|
68
|
-
|
69
|
-
attribute :title_tag, :type => :string, :default => "default title tag"
|
70
|
-
attribute :meta_description, :type => :string, :default => ""
|
71
|
-
attribute :meta_keywords, :type => :string, :default => ""
|
72
|
-
attribute :noindex, :type => :boolean, :default => false
|
73
|
-
attribute :nofollow, :type => :boolean, :default => false
|
74
|
-
attribute :noarchive, :type => :boolean, :default => false
|
75
|
-
|
76
|
-
end
|
77
|
-
|
78
|
-
|
79
|
-
That's it.
|
80
|
-
|
81
|
-
When you now create an instance of SeoOptions, you can get and set its attributes as you would do with a normal model:
|
82
|
-
|
83
|
-
seo_options = SeoOptions.new
|
84
|
-
=> <#SeoOptions meta_description="" meta_keywords="" noarchive=false nofollow=false noindex=false
|
85
|
-
title_tag="default title tag">
|
86
|
-
|
87
|
-
seo_options.title_tag
|
88
|
-
=> "default title tag"
|
89
|
-
|
90
|
-
seo_options.title_tag = "new title tag"
|
91
|
-
=> "new title tag"
|
92
|
-
|
93
|
-
Note that inspect shows the properties of the Tableless Model in the same way it does for ActiveRecord models.
|
94
|
-
Of course, you can also override the default values for the attributes when creating a new instance:
|
95
|
-
|
96
|
-
seo_options = SeoOptions.new( :title_tag => "a different title tag" )
|
97
|
-
=> <#SeoOptions meta_description="" meta_keywords="" noarchive=false nofollow=false noindex=false
|
98
|
-
title_tag="a different title tag">
|
99
|
-
|
100
|
-
Now, if you have used the has_tabless macro in the parent class, Page, each instance of Page will store directly its YAML-serialized SEO settings in the column named "seo".
|
101
|
-
|
102
|
-
page = Page.new
|
103
|
-
|
104
|
-
page.seo
|
105
|
-
=> <#SeoOptions meta_description="" meta_keywords="" noarchive=false nofollow=false noindex=false
|
106
|
-
title_tag="default title tag">
|
107
|
-
|
108
|
-
page.seo.title_tag = "changed title tag"
|
109
|
-
=> <#SeoOptions meta_description="" meta_keywords="" noarchive=false nofollow=false noindex=false
|
110
|
-
title_tag="changed title tag">
|
111
|
-
|
112
|
-
|
113
|
-
And this is how the content of the serialized column would look like in the database if you saved the changes as in the example
|
114
|
-
|
115
|
-
--- !map:SeoOptions
|
116
|
-
noarchive: false
|
117
|
-
meta_description:
|
118
|
-
meta_keywords:
|
119
|
-
nofollow: false
|
120
|
-
title_tag: "changed title tag"
|
121
|
-
noindex: false
|
122
|
-
|
123
|
-
|
124
|
-
== Validations
|
125
|
-
|
126
|
-
Tableless Model uses the Validatable gem to support validations methods and callbacks (such as "after_validation").
|
127
|
-
Note: it currently uses the Rails 2.x syntax only.
|
128
|
-
|
129
|
-
Example:
|
130
|
-
|
131
|
-
|
132
|
-
class SeoOptions < ActiveRecord::TablelessModel
|
133
|
-
|
134
|
-
attribute :title_tag, :type => :string, :default => ""
|
135
|
-
attribute :meta_description, :type => :string, :default => ""
|
136
|
-
attribute :meta_keywords, :type => :string, :default => ""
|
137
|
-
attribute :noindex, :type => :boolean, :default => false
|
138
|
-
attribute :nofollow, :type => :boolean, :default => false
|
139
|
-
attribute :noarchive, :type => :boolean, :default => false
|
140
|
-
|
141
|
-
validates_presence_of :meta_keywords
|
142
|
-
|
143
|
-
end
|
144
|
-
|
145
|
-
|
146
|
-
Testing:
|
147
|
-
|
148
|
-
x = SeoOptionsSettings.new
|
149
|
-
=> <#SeoOptions meta_description="" meta_keywords="" noarchive=false nofollow=false noindex=false
|
150
|
-
title_tag="">
|
151
|
-
|
152
|
-
x.valid?
|
153
|
-
=> false
|
154
|
-
|
155
|
-
x.meta_keywords = "test"
|
156
|
-
=> "test"
|
157
|
-
|
158
|
-
x.valid?
|
159
|
-
=> true
|
160
|
-
|
161
|
-
|
162
|
-
== TODO
|
163
|
-
|
164
|
-
* Support for associations
|
165
|
-
|
166
|
-
|
167
|
-
== Authors
|
168
|
-
|
169
|
-
* Vito Botta ( http://vitobotta.com )
|
170
|
-
|
data/spec/test_helper.rb
DELETED
@@ -1,157 +0,0 @@
|
|
1
|
-
require 'rubygems'
|
2
|
-
gem "minitest"
|
3
|
-
require 'minitest/autorun'
|
4
|
-
|
5
|
-
require 'ansi'
|
6
|
-
|
7
|
-
class MiniTest::Unit
|
8
|
-
include ANSI::Code
|
9
|
-
|
10
|
-
PADDING_SIZE = 4
|
11
|
-
|
12
|
-
def run(args = [])
|
13
|
-
@verbose = true
|
14
|
-
|
15
|
-
filter = if args.first =~ /^(-n|--name)$/ then
|
16
|
-
args.shift
|
17
|
-
arg = args.shift
|
18
|
-
arg =~ /\/(.*)\// ? Regexp.new($1) : arg
|
19
|
-
else
|
20
|
-
/./ # anything - ^test_ already filtered by #tests
|
21
|
-
end
|
22
|
-
|
23
|
-
@@out.puts "Loaded suite #{$0.sub(/\.rb$/, '')}\nStarted"
|
24
|
-
|
25
|
-
start = Time.now
|
26
|
-
run_test_suites filter
|
27
|
-
|
28
|
-
@@out.puts
|
29
|
-
@@out.puts "Finished in #{'%.6f' % (Time.now - start)} seconds."
|
30
|
-
|
31
|
-
@@out.puts
|
32
|
-
|
33
|
-
@@out.print "%d tests, " % test_count
|
34
|
-
@@out.print "%d assertions, " % assertion_count
|
35
|
-
@@out.print red { "%d failures, " % failures }
|
36
|
-
@@out.print yellow { "%d errors, " % errors }
|
37
|
-
@@out.puts cyan { "%d skips" % skips}
|
38
|
-
|
39
|
-
return failures + errors if @test_count > 0 # or return nil...
|
40
|
-
end
|
41
|
-
|
42
|
-
# Overwrite #run_test_suites so that it prints out reports
|
43
|
-
# as errors are generated.
|
44
|
-
def run_test_suites(filter = /./)
|
45
|
-
@test_count, @assertion_count = 0, 0
|
46
|
-
old_sync, @@out.sync = @@out.sync, true if @@out.respond_to? :sync=
|
47
|
-
|
48
|
-
TestCase.test_suites.each do |suite|
|
49
|
-
test_cases = suite.test_methods.grep(filter)
|
50
|
-
if test_cases.size > 0
|
51
|
-
@@out.print "\n#{suite}:\n"
|
52
|
-
end
|
53
|
-
|
54
|
-
test_cases.each do |test|
|
55
|
-
inst = suite.new test
|
56
|
-
inst._assertions = 0
|
57
|
-
|
58
|
-
t = Time.now
|
59
|
-
|
60
|
-
@broken = nil
|
61
|
-
|
62
|
-
@@out.print(case inst.run(self)
|
63
|
-
when :pass
|
64
|
-
@broken = false
|
65
|
-
green { pad_with_size "PASS" }
|
66
|
-
when :error
|
67
|
-
@broken = true
|
68
|
-
yellow { pad_with_size "ERROR" }
|
69
|
-
when :fail
|
70
|
-
@broken = true
|
71
|
-
red { pad_with_size "FAIL" }
|
72
|
-
when :skip
|
73
|
-
@broken = false
|
74
|
-
cyan { pad_with_size "SKIP" }
|
75
|
-
end)
|
76
|
-
|
77
|
-
|
78
|
-
# @@out.print " #{test.humanize.gsub(/Test\s\d+\s(.*)/,"\\1")} "
|
79
|
-
@@out.print " #{test} "
|
80
|
-
@@out.print " (%.2fs) " % (Time.now - t)
|
81
|
-
|
82
|
-
if @broken
|
83
|
-
@@out.puts
|
84
|
-
|
85
|
-
report = @report.last
|
86
|
-
@@out.puts pad(report[:message], 10)
|
87
|
-
trace = MiniTest::filter_backtrace(report[:exception].backtrace).first
|
88
|
-
@@out.print pad(trace, 10)
|
89
|
-
|
90
|
-
@@out.puts
|
91
|
-
end
|
92
|
-
|
93
|
-
@@out.puts
|
94
|
-
@test_count += 1
|
95
|
-
@assertion_count += inst._assertions
|
96
|
-
end
|
97
|
-
end
|
98
|
-
@@out.sync = old_sync if @@out.respond_to? :sync=
|
99
|
-
[@test_count, @assertion_count]
|
100
|
-
end
|
101
|
-
|
102
|
-
def pad(str, size=PADDING_SIZE)
|
103
|
-
" " * size + str
|
104
|
-
end
|
105
|
-
|
106
|
-
def pad_with_size(str)
|
107
|
-
pad("%5s" % str)
|
108
|
-
end
|
109
|
-
|
110
|
-
# Overwrite #puke method so that is stores a hash
|
111
|
-
# with :message and :exception keys.
|
112
|
-
def puke(klass, meth, e)
|
113
|
-
result = nil
|
114
|
-
msg = case e
|
115
|
-
when MiniTest::Skip
|
116
|
-
@skips += 1
|
117
|
-
result = :skip
|
118
|
-
e.message
|
119
|
-
when MiniTest::Assertion
|
120
|
-
@failures += 1
|
121
|
-
result = :fail
|
122
|
-
e.message
|
123
|
-
else
|
124
|
-
@errors += 1
|
125
|
-
result = :error
|
126
|
-
"#{e.class}: #{e.message}\n"
|
127
|
-
end
|
128
|
-
|
129
|
-
@report << {:message => msg, :exception => e}
|
130
|
-
result
|
131
|
-
end
|
132
|
-
|
133
|
-
|
134
|
-
class TestCase
|
135
|
-
# Overwrite #run method so that is uses symbols
|
136
|
-
# as return values rather than characters.
|
137
|
-
def run(runner)
|
138
|
-
result = :pass
|
139
|
-
begin
|
140
|
-
@passed = nil
|
141
|
-
self.setup
|
142
|
-
self.send self.__name__
|
143
|
-
@passed = true
|
144
|
-
rescue Exception => e
|
145
|
-
@passed = false
|
146
|
-
result = runner.puke(self.class, self.__name__, e)
|
147
|
-
ensure
|
148
|
-
begin
|
149
|
-
self.teardown
|
150
|
-
rescue Exception => e
|
151
|
-
result = runner.puke(self.class, self.__name__, e)
|
152
|
-
end
|
153
|
-
end
|
154
|
-
result
|
155
|
-
end
|
156
|
-
end
|
157
|
-
end
|