koujou 0.1.0
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/History.txt +4 -0
- data/Manifest.txt +29 -0
- data/PostInstall.txt +7 -0
- data/README.rdoc +43 -0
- data/Rakefile +26 -0
- data/koujou.gemspec +37 -0
- data/lib/koujou.rb +23 -0
- data/lib/koujou/builder.rb +214 -0
- data/lib/koujou/custom_validation.rb +15 -0
- data/lib/koujou/data_generator.rb +106 -0
- data/lib/koujou/sequence.rb +11 -0
- data/lib/koujou/validation_reflection.rb +143 -0
- data/script/console +10 -0
- data/script/destroy +14 -0
- data/script/generate +14 -0
- data/test/lib/active_record_test_connector.rb +79 -0
- data/test/lib/models/car.rb +7 -0
- data/test/lib/models/comment.rb +7 -0
- data/test/lib/models/message.rb +5 -0
- data/test/lib/models/photo.rb +3 -0
- data/test/lib/models/post.rb +15 -0
- data/test/lib/models/profile.rb +9 -0
- data/test/lib/models/user.rb +38 -0
- data/test/test_builder.rb +150 -0
- data/test/test_custom_validation.rb +19 -0
- data/test/test_data_generator.rb +120 -0
- data/test/test_helper.rb +12 -0
- data/test/test_kojo.rb +11 -0
- data/test/test_sequence.rb +20 -0
- metadata +109 -0
@@ -0,0 +1,143 @@
|
|
1
|
+
#--
|
2
|
+
# Copyright (c) 2006-2008, Michael Schuerig, michael@schuerig.de
|
3
|
+
#
|
4
|
+
# Permission is hereby granted, free of charge, to any person obtaining
|
5
|
+
# a copy of this software and associated documentation files (the
|
6
|
+
# "Software"), to deal in the Software without restriction, including
|
7
|
+
# without limitation the rights to use, copy, modify, merge, publish,
|
8
|
+
# distribute, sublicense, and/or sell copies of the Software, and to
|
9
|
+
# permit persons to whom the Software is furnished to do so, subject to
|
10
|
+
# the following conditions:
|
11
|
+
#
|
12
|
+
# The above copyright notice and this permission notice shall be
|
13
|
+
# included in all copies or substantial portions of the Software.
|
14
|
+
#
|
15
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
17
|
+
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
19
|
+
# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
20
|
+
# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
21
|
+
# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
22
|
+
#++
|
23
|
+
|
24
|
+
|
25
|
+
require 'active_record/reflection'
|
26
|
+
require 'ostruct'
|
27
|
+
|
28
|
+
# Based on code by Sebastian Kanthak
|
29
|
+
# See http://dev.rubyonrails.org/ticket/861
|
30
|
+
module Koujou # :nodoc:
|
31
|
+
module ActiveRecordExtensions # :nodoc:
|
32
|
+
module ValidationReflection # :nodoc:
|
33
|
+
|
34
|
+
mattr_accessor :reflected_validations
|
35
|
+
Koujou::ActiveRecordExtensions::ValidationReflection.reflected_validations = %w(
|
36
|
+
validates_acceptance_of
|
37
|
+
validates_associated
|
38
|
+
validates_confirmation_of
|
39
|
+
validates_exclusion_of
|
40
|
+
validates_format_of
|
41
|
+
validates_inclusion_of
|
42
|
+
validates_length_of
|
43
|
+
validates_size_of
|
44
|
+
validates_numericality_of
|
45
|
+
validates_presence_of
|
46
|
+
validates_uniqueness_of
|
47
|
+
validate
|
48
|
+
)
|
49
|
+
|
50
|
+
mattr_accessor :in_ignored_subvalidation
|
51
|
+
Koujou::ActiveRecordExtensions::ValidationReflection.in_ignored_subvalidation = false
|
52
|
+
|
53
|
+
def self.included(base)
|
54
|
+
return if base.kind_of?(Koujou::ActiveRecordExtensions::ValidationReflection::ClassMethods)
|
55
|
+
base.extend(ClassMethods)
|
56
|
+
end
|
57
|
+
|
58
|
+
def self.install(base)
|
59
|
+
reflected_validations.freeze
|
60
|
+
reflected_validations.each do |validation_type|
|
61
|
+
next if base.respond_to?("#{validation_type}_with_reflection")
|
62
|
+
ignore_subvalidations = false
|
63
|
+
base.class_eval <<-"end_eval"
|
64
|
+
class << self
|
65
|
+
def #{validation_type}_with_reflection(*attr_names, &block)
|
66
|
+
ignoring_subvalidations(#{ignore_subvalidations}) do
|
67
|
+
#{validation_type}_without_reflection(*attr_names, &block)
|
68
|
+
remember_validation_metadata(:#{validation_type}, *attr_names)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
alias_method_chain :#{validation_type}, :reflection
|
73
|
+
end
|
74
|
+
end_eval
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
module ClassMethods
|
79
|
+
|
80
|
+
# Returns an array of MacroReflection objects for all validations in the class
|
81
|
+
def reflect_on_all_validations
|
82
|
+
read_inheritable_attribute(:validations) || []
|
83
|
+
end
|
84
|
+
|
85
|
+
# Returns an array of MacroReflection objects for all validations defined for the field +attr_name+.
|
86
|
+
def reflect_on_validations_for(attr_name)
|
87
|
+
attr_name = attr_name.to_sym
|
88
|
+
reflect_on_all_validations.select do |reflection|
|
89
|
+
reflection.name == attr_name
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# A few convenience methods added in by Mike. Why not loop and define_method these?
|
94
|
+
|
95
|
+
def unique_validations
|
96
|
+
reflect_on_all_validations.select{|v| v.macro == :validates_uniqueness_of }
|
97
|
+
end
|
98
|
+
|
99
|
+
def required_validations
|
100
|
+
reflect_on_all_validations.select{|v| v.macro == :validates_presence_of }
|
101
|
+
end
|
102
|
+
|
103
|
+
def confirmation_validations
|
104
|
+
reflect_on_all_validations.select{|v| v.macro == :validates_confirmation_of }
|
105
|
+
end
|
106
|
+
|
107
|
+
def length_validations
|
108
|
+
reflect_on_all_validations.select{|v| v.macro == :validates_length_of || v.macro == :validates_size_of }
|
109
|
+
end
|
110
|
+
|
111
|
+
def inclusion_validations
|
112
|
+
reflect_on_all_validations.select{|v| v.macro == :validates_inclusion_of }
|
113
|
+
end
|
114
|
+
|
115
|
+
def custom_validations
|
116
|
+
# We don't want to get anything like: validate_associated_records_for_posts.
|
117
|
+
reflect_on_all_validations.select{|v| v.macro == :validate && v.name.to_s.match("_associated_").nil? }
|
118
|
+
end
|
119
|
+
|
120
|
+
private
|
121
|
+
|
122
|
+
def remember_validation_metadata(validation_type, *attr_names)
|
123
|
+
configuration = attr_names.last.is_a?(Hash) ? attr_names.pop : {}
|
124
|
+
attr_names.each do |attr_name|
|
125
|
+
write_inheritable_array :validations,
|
126
|
+
[ ActiveRecord::Reflection::MacroReflection.new(validation_type, attr_name.to_sym, configuration, self) ]
|
127
|
+
end
|
128
|
+
end
|
129
|
+
|
130
|
+
def ignoring_subvalidations(ignore)
|
131
|
+
save_ignore = Koujou::ActiveRecordExtensions::ValidationReflection.in_ignored_subvalidation
|
132
|
+
unless Koujou::ActiveRecordExtensions::ValidationReflection.in_ignored_subvalidation
|
133
|
+
Koujou::ActiveRecordExtensions::ValidationReflection.in_ignored_subvalidation = ignore
|
134
|
+
yield
|
135
|
+
end
|
136
|
+
ensure
|
137
|
+
Koujou::ActiveRecordExtensions::ValidationReflection.in_ignored_subvalidation = save_ignore
|
138
|
+
end
|
139
|
+
end
|
140
|
+
|
141
|
+
end
|
142
|
+
end
|
143
|
+
end
|
data/script/console
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# File: script/console
|
3
|
+
irb = RUBY_PLATFORM =~ /(:?mswin|mingw)/ ? 'irb.bat' : 'irb'
|
4
|
+
|
5
|
+
libs = " -r irb/completion"
|
6
|
+
# Perhaps use a console_lib to store any extra methods I may want available in the cosole
|
7
|
+
# libs << " -r #{File.dirname(__FILE__) + '/../lib/console_lib/console_logger.rb'}"
|
8
|
+
libs << " -r #{File.dirname(__FILE__) + '/../lib/koujou.rb'}"
|
9
|
+
puts "Loading koujou gem"
|
10
|
+
exec "#{irb} #{libs} --simple-prompt"
|
data/script/destroy
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/destroy'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Destroy.new.run(ARGV)
|
data/script/generate
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
APP_ROOT = File.expand_path(File.join(File.dirname(__FILE__), '..'))
|
3
|
+
|
4
|
+
begin
|
5
|
+
require 'rubigen'
|
6
|
+
rescue LoadError
|
7
|
+
require 'rubygems'
|
8
|
+
require 'rubigen'
|
9
|
+
end
|
10
|
+
require 'rubigen/scripts/generate'
|
11
|
+
|
12
|
+
ARGV.shift if ['--help', '-h'].include?(ARGV[0])
|
13
|
+
RubiGen::Base.use_component_sources! [:rubygems, :newgem, :newgem_theme, :test_unit]
|
14
|
+
RubiGen::Scripts::Generate.new.run(ARGV)
|
@@ -0,0 +1,79 @@
|
|
1
|
+
# This let's us happily test AR.
|
2
|
+
require 'active_record'
|
3
|
+
# Require all our models.
|
4
|
+
Dir.glob(File.join(File.dirname(__FILE__), "..", "lib", "models", "*.rb")).each { |f| require f }
|
5
|
+
|
6
|
+
|
7
|
+
class ActiveRecordTestConnector
|
8
|
+
cattr_accessor :connected
|
9
|
+
cattr_accessor :able_to_connect
|
10
|
+
|
11
|
+
self.connected = false
|
12
|
+
self.able_to_connect = true
|
13
|
+
|
14
|
+
class << self
|
15
|
+
|
16
|
+
def setup
|
17
|
+
unless self.connected || !self.able_to_connect
|
18
|
+
setup_connection
|
19
|
+
load_schema
|
20
|
+
self.connected = true
|
21
|
+
end
|
22
|
+
rescue Exception => e # errors from ActiveRecord setup
|
23
|
+
$stderr.puts "\nSkipping ActiveRecord tests: #{e}\n\n"
|
24
|
+
self.able_to_connect = false
|
25
|
+
end
|
26
|
+
|
27
|
+
def setup_connection
|
28
|
+
ActiveRecord::Base.establish_connection({
|
29
|
+
:adapter => 'sqlite3',
|
30
|
+
:dbfile => 'test.sqlite3'
|
31
|
+
})
|
32
|
+
end
|
33
|
+
|
34
|
+
def load_schema
|
35
|
+
ActiveRecord::Schema.define do
|
36
|
+
create_table "users", :force => true do |t|
|
37
|
+
t.string "name", "email", "first_name", "last_name", "hashed_password"
|
38
|
+
t.text "about"
|
39
|
+
t.integer "age"
|
40
|
+
t.float "salary"
|
41
|
+
t.datetime "hired_on"
|
42
|
+
t.boolean "terms_of_service"
|
43
|
+
end
|
44
|
+
create_table "posts", :force => true do |t|
|
45
|
+
t.string "name"
|
46
|
+
t.text "body"
|
47
|
+
t.integer "user_id"
|
48
|
+
end
|
49
|
+
create_table "comments", :force => true do |t|
|
50
|
+
t.text "bod"
|
51
|
+
t.integer "post_id"
|
52
|
+
end
|
53
|
+
create_table "profiles", :force => true do |t|
|
54
|
+
t.integer "user_id"
|
55
|
+
t.boolean "likes_cheese"
|
56
|
+
end
|
57
|
+
create_table "photos", :force => true do |t|
|
58
|
+
t.integer "profile_id"
|
59
|
+
end
|
60
|
+
create_table "messages", :force => true do |t|
|
61
|
+
t.string "subject"
|
62
|
+
t.text "body"
|
63
|
+
t.datetime "created_at"
|
64
|
+
t.datetime "updated_at"
|
65
|
+
t.integer "sender_id"
|
66
|
+
t.integer "receiver_id"
|
67
|
+
t.boolean "read"
|
68
|
+
end
|
69
|
+
create_table "cars", :force => true do |t|
|
70
|
+
t.integer "user_id", "year"
|
71
|
+
t.string "make", "model"
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
|
@@ -0,0 +1,15 @@
|
|
1
|
+
class Post < ActiveRecord::Base
|
2
|
+
validates_presence_of :name
|
3
|
+
validates_length_of :body, :is => 20
|
4
|
+
|
5
|
+
belongs_to :user
|
6
|
+
has_many :comments
|
7
|
+
|
8
|
+
validate :craziness
|
9
|
+
|
10
|
+
protected
|
11
|
+
def craziness
|
12
|
+
raise 'No way Jose' if "Matz" != "Guido van Rossum"
|
13
|
+
end
|
14
|
+
|
15
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
class User < ActiveRecord::Base
|
2
|
+
validates_presence_of :name, :age, :salary, :hired_on, :email, :first_name, :last_name
|
3
|
+
validates_uniqueness_of :name
|
4
|
+
validates_acceptance_of :terms_of_service
|
5
|
+
|
6
|
+
attr_accessor :password
|
7
|
+
|
8
|
+
|
9
|
+
# This is basically the main validation from restful_auth's user model.
|
10
|
+
validates_presence_of :password, :if => :password_required?
|
11
|
+
validates_presence_of :password_confirmation, :if => :password_required?
|
12
|
+
validates_length_of :password, :within => 4..40, :if => :password_required?
|
13
|
+
validates_confirmation_of :password, :if => :password_required?
|
14
|
+
validates_length_of :email, :within => 3..100
|
15
|
+
|
16
|
+
has_many :posts
|
17
|
+
has_one :profile
|
18
|
+
|
19
|
+
attr_accessible :email, :password, :password_confirmation
|
20
|
+
|
21
|
+
validate :custom_validation_method
|
22
|
+
validate :custom_private_method
|
23
|
+
|
24
|
+
def password_required?
|
25
|
+
true
|
26
|
+
end
|
27
|
+
|
28
|
+
def custom_validation_method
|
29
|
+
false
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
def custom_private_method
|
34
|
+
false
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,150 @@
|
|
1
|
+
require File.dirname(__FILE__) + '/test_helper.rb'
|
2
|
+
|
3
|
+
class TestBuilder < Test::Unit::TestCase
|
4
|
+
|
5
|
+
# Gives us DB access.
|
6
|
+
ActiveRecordTestConnector.setup
|
7
|
+
|
8
|
+
context 'ActiveRecord' do
|
9
|
+
|
10
|
+
should 'have the koujou method' do
|
11
|
+
assert User.respond_to?(:koujou)
|
12
|
+
end
|
13
|
+
|
14
|
+
should 'return an instance of User, with ActiveRecord::Base as an ancestor' do
|
15
|
+
u = User.koujou
|
16
|
+
assert_equal User, u.class
|
17
|
+
assert u.class.ancestors.include?(ActiveRecord::Base)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'on sending the koujou message' do
|
22
|
+
|
23
|
+
should 'persist the record to the db without any arguments' do
|
24
|
+
u = User.koujou
|
25
|
+
assert !u.new_record?
|
26
|
+
end
|
27
|
+
|
28
|
+
should 'return a new record when passing in false' do
|
29
|
+
u = User.koujou(false)
|
30
|
+
assert u.new_record?
|
31
|
+
end
|
32
|
+
|
33
|
+
should 'have unique values for multiple instances where validates_uniqueness_of is defined for a column' do
|
34
|
+
u1 = User.koujou
|
35
|
+
u2 = User.koujou
|
36
|
+
assert_not_equal u1.name, u2.name
|
37
|
+
end
|
38
|
+
|
39
|
+
should 'have a password confirmation automatically set' do
|
40
|
+
u = User.koujou
|
41
|
+
assert_not_nil u.password_confirmation
|
42
|
+
end
|
43
|
+
|
44
|
+
should 'allow me to override the model attributes' do
|
45
|
+
namae = 'One Factory to Rule them all'
|
46
|
+
p = Post.koujou(true, :name => namae)
|
47
|
+
assert_equal namae, p.name
|
48
|
+
end
|
49
|
+
|
50
|
+
should 'allow me to override select attributes, yet still generate data for the rest' do
|
51
|
+
# We have a validates_length_of :bod, :is => 20 set.
|
52
|
+
bod = "T" * 20
|
53
|
+
p = Post.koujou_create(:body => bod)
|
54
|
+
assert_not_nil p.name
|
55
|
+
assert_equal bod, p.body
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
context 'on sending the koujou_create message' do
|
61
|
+
|
62
|
+
should 'persist the record to the db' do
|
63
|
+
u = User.koujou_create
|
64
|
+
assert !u.new_record?
|
65
|
+
end
|
66
|
+
|
67
|
+
should 'allow me to override the model attributes' do
|
68
|
+
comment = 'your post is epic fail'
|
69
|
+
c = Comment.koujou_create(:bod => comment)
|
70
|
+
assert_equal comment, c.bod
|
71
|
+
end
|
72
|
+
|
73
|
+
should 'not be sequenced unless I say so' do
|
74
|
+
u = User.koujou
|
75
|
+
# The first digit should not be an integer.
|
76
|
+
assert_equal 0, u.first_name[0,1].to_i
|
77
|
+
end
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
context 'on sending the koujou_build message' do
|
82
|
+
|
83
|
+
should 'return a new record' do
|
84
|
+
u = User.koujou_build
|
85
|
+
assert u.new_record?
|
86
|
+
end
|
87
|
+
|
88
|
+
should 'allow me to override the model attributes' do
|
89
|
+
clever = 'Whatever\'s clever'
|
90
|
+
p = Post.koujou_build(:name => clever)
|
91
|
+
assert_equal clever, p.name
|
92
|
+
end
|
93
|
+
|
94
|
+
should 'cause a model to be invalid if you override a field that has validates_presence_of with a nil value' do
|
95
|
+
u = User.koujou_build(:email => nil)
|
96
|
+
assert !u.valid?
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
context 'associations' do
|
101
|
+
|
102
|
+
should 'not automatically create any assoications unless there\'s a validation for the id' do
|
103
|
+
u = User.koujou
|
104
|
+
assert_equal 0, u.posts.size
|
105
|
+
end
|
106
|
+
|
107
|
+
should 'automatically create a user for a profile when the profile has a required user_id validation' do
|
108
|
+
p = Profile.koujou
|
109
|
+
assert_not_nil p.user
|
110
|
+
end
|
111
|
+
|
112
|
+
should 'find custom validations' do
|
113
|
+
assert_equal 2, User.custom_validations.size
|
114
|
+
end
|
115
|
+
|
116
|
+
should 'car should have an owner association to user via class_name' do
|
117
|
+
c = Car.koujou
|
118
|
+
assert_not_nil c.owner
|
119
|
+
end
|
120
|
+
|
121
|
+
end
|
122
|
+
|
123
|
+
context 'custom validations' do
|
124
|
+
|
125
|
+
should 'totally override any custom validations, and thus not fail when we call koujou' do
|
126
|
+
p = Post.koujou
|
127
|
+
assert true
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'inclusion_of_validation' do
|
133
|
+
|
134
|
+
should 'create the correct value for an attribute marked with validates_inclusion_of' do
|
135
|
+
c = Car.koujou
|
136
|
+
assert_equal 'Nissan', c.make
|
137
|
+
end
|
138
|
+
|
139
|
+
should 'create the correct values for inclusion when it\'s not also a required attribute' do
|
140
|
+
c = Car.koujou
|
141
|
+
assert_equal 1900, c.year
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
145
|
+
|
146
|
+
|
147
|
+
end
|
148
|
+
|
149
|
+
|
150
|
+
|