koujou 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,4 @@
1
+ === 0.0.1 2009-07-14
2
+
3
+ * 1 major enhancement:
4
+ * Initial release
@@ -0,0 +1,29 @@
1
+ History.txt
2
+ Manifest.txt
3
+ PostInstall.txt
4
+ README.rdoc
5
+ Rakefile
6
+ koujou.gemspec
7
+ lib/koujou.rb
8
+ lib/koujou/builder.rb
9
+ lib/koujou/custom_validation.rb
10
+ lib/koujou/data_generator.rb
11
+ lib/koujou/sequence.rb
12
+ lib/koujou/validation_reflection.rb
13
+ script/console
14
+ script/destroy
15
+ script/generate
16
+ test/lib/active_record_test_connector.rb
17
+ test/lib/models/car.rb
18
+ test/lib/models/comment.rb
19
+ test/lib/models/message.rb
20
+ test/lib/models/photo.rb
21
+ test/lib/models/post.rb
22
+ test/lib/models/profile.rb
23
+ test/lib/models/user.rb
24
+ test/test_builder.rb
25
+ test/test_custom_validation.rb
26
+ test/test_data_generator.rb
27
+ test/test_helper.rb
28
+ test/test_kojo.rb
29
+ test/test_sequence.rb
@@ -0,0 +1,7 @@
1
+
2
+ For more information on koujou, see http://koujou.rubyforge.org
3
+
4
+ NOTE: Change this information in PostInstall.txt
5
+ You can also delete it if you don't want it.
6
+
7
+
@@ -0,0 +1,43 @@
1
+ = koujou
2
+
3
+ * http://github.com/mleung/koujou
4
+
5
+ == DESCRIPTION:
6
+
7
+ Koujou is a fixture replacement that requires no effort to use. You don't have to define a single thing
8
+ in your test_helper or whatever. Just call the koujou method on your active record model, and you're
9
+ off.
10
+
11
+ Check out: http://www.michaelleung.us/koujou for all the juicy details.
12
+
13
+ Please note this is a very early release of koujou, so if you find any issues, just post
14
+ them here: http://github.com/mleung/koujou/issues
15
+
16
+ == INSTALL:
17
+
18
+ gem install mleung-koujou --source http://gems.github.com
19
+
20
+ == LICENSE:
21
+
22
+ (The MIT License)
23
+
24
+ Copyright (c) 2009 Michael Leung
25
+
26
+ Permission is hereby granted, free of charge, to any person obtaining
27
+ a copy of this software and associated documentation files (the
28
+ 'Software'), to deal in the Software without restriction, including
29
+ without limitation the rights to use, copy, modify, merge, publish,
30
+ distribute, sublicense, and/or sell copies of the Software, and to
31
+ permit persons to whom the Software is furnished to do so, subject to
32
+ the following conditions:
33
+
34
+ The above copyright notice and this permission notice shall be
35
+ included in all copies or substantial portions of the Software.
36
+
37
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
38
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
39
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
40
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
41
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
42
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
43
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,26 @@
1
+ require 'rubygems'
2
+ gem 'hoe', '>= 2.1.0'
3
+ require 'hoe'
4
+ require 'fileutils'
5
+ require './lib/koujou'
6
+
7
+ Hoe.plugin :newgem
8
+ # Hoe.plugin :website
9
+ # Hoe.plugin :cucumberfeatures
10
+
11
+ # Generate all the Rake tasks
12
+ # Run 'rake -T' to see list of generated tasks (from gem root directory)
13
+ $hoe = Hoe.spec 'koujou' do
14
+ self.developer 'Michael Leung', 'me@michaelleung.us'
15
+ self.post_install_message = 'PostInstall.txt' # TODO remove if post-install message not required
16
+ self.rubyforge_name = self.name # TODO this is default value
17
+ # self.extra_deps = [['activesupport','>= 2.0.2']]
18
+
19
+ end
20
+
21
+ require 'newgem/tasks'
22
+ Dir['tasks/**/*.rake'].each { |t| load t }
23
+
24
+ # TODO - want other tests/tasks run by default? Add them to the list
25
+ # remove_task :default
26
+ # task :default => [:spec, :features]
@@ -0,0 +1,37 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ Gem::Specification.new do |s|
4
+ s.name = %q{koujou}
5
+ s.version = "0.1.0"
6
+
7
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
+ s.authors = ["Michael Leung"]
9
+ s.date = %q{2009-07-19}
10
+ s.description = %q{Koujou is a fixture replacement that requires no effort to use. You don't have to define a single thing in your test_helper or whatever. Just call the koujou method on your active record model, and you're off. check out: http://www.michaelleung.us/koujou for all the juicy details.}
11
+ s.email = ["me@michaelleung.us"]
12
+ s.extra_rdoc_files = ["History.txt", "Manifest.txt", "PostInstall.txt"]
13
+ s.files = ["History.txt", "Manifest.txt", "PostInstall.txt", "README.rdoc", "Rakefile", "lib/koujou.rb", "lib/koujou/builder.rb", "lib/koujou/data_generator.rb", "lib/koujou/sequence.rb", "lib/koujou/validation_reflection.rb", "script/console", "script/destroy", "script/generate", "test/lib/active_record_test_connector.rb", "test/lib/models/comment.rb", "test/lib/models/post.rb", "test/lib/models/profile.rb", "test/lib/models/user.rb", "test/test_builder.rb", "test/test_data_generator.rb", "test/test_helper.rb", "test/test_kojo.rb", "test/test_sequence.rb", "lib/koujou/custom_validation.rb"]
14
+ s.has_rdoc = true
15
+ s.homepage = %q{http://github.com/mleung/koujou}
16
+ s.post_install_message = %q{PostInstall.txt}
17
+ s.rdoc_options = ["--main", "README.rdoc"]
18
+ s.require_paths = ["lib"]
19
+ s.rubyforge_project = %q{koujou}
20
+ s.rubygems_version = %q{1.3.1}
21
+ s.summary = %q{Koujou is a fixture replacement that requires no effort to use}
22
+ s.test_files = ["test/test_builder.rb", "test/test_data_generator.rb", "test/test_helper.rb", "test/test_kojo.rb", "test/test_sequence.rb"]
23
+ s.add_dependency('faker', '>= 0.3.1')
24
+
25
+ if s.respond_to? :specification_version then
26
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
27
+ s.specification_version = 3
28
+
29
+ if Gem::Version.new(Gem::RubyGemsVersion) >= Gem::Version.new('1.2.0') then
30
+ s.add_development_dependency(%q<hoe>, [">= 2.3.2"])
31
+ else
32
+ s.add_dependency(%q<hoe>, [">= 2.3.2"])
33
+ end
34
+ else
35
+ s.add_dependency(%q<hoe>, [">= 2.3.2"])
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ $:.unshift(File.dirname(__FILE__)) unless
2
+ $:.include?(File.dirname(__FILE__)) || $:.include?(File.expand_path(File.dirname(__FILE__)))
3
+
4
+ require 'rubygems'
5
+ require 'active_record'
6
+ require 'faker'
7
+ require 'koujou/sequence'
8
+ require 'koujou/builder'
9
+ require 'koujou/data_generator'
10
+ require 'koujou/validation_reflection'
11
+ require 'koujou/custom_validation'
12
+
13
+ module Koujou
14
+ VERSION = '0.1.0'
15
+ end
16
+
17
+ if ENV["RAILS_ENV"] == "test"
18
+ ActiveRecord::Base.class_eval do
19
+ include Koujou::ActiveRecordExtensions::ValidationReflection
20
+ Koujou::ActiveRecordExtensions::ValidationReflection.install(self)
21
+ include Koujou::ActiveRecordExtensions::Builder
22
+ end
23
+ end
@@ -0,0 +1,214 @@
1
+ module Koujou #:nodoc:
2
+ module ActiveRecordExtensions # :nodoc:
3
+ module Builder # :nodoc:
4
+
5
+ def self.included(base) # :nodoc:
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods #:nodoc:
10
+
11
+ def koujou(create = true, attributes = nil)
12
+ generate_instance(create, attributes)
13
+ end
14
+
15
+ def koujou_build(attributes = nil)
16
+ koujou(false, attributes)
17
+ end
18
+
19
+ def koujou_create(attributes = nil)
20
+ koujou(true, attributes)
21
+ end
22
+
23
+ protected
24
+ def generate_instance(create, attributes)
25
+ # There were a metric ton of AR warnings about instance vars
26
+ # not being initialized when running our specs. This hides those.
27
+ silence_warnings do
28
+ instance = build_model_instance(self, attributes)
29
+ instance.save! if create
30
+ instance
31
+ end
32
+ end
33
+
34
+ def build_model_instance(klass, attributes = nil, recursed_from_model = nil)
35
+ # If we pass in a string here for klass instead of a constant
36
+ # we want to convert that.
37
+ klass = Kernel.const_get(klass) unless klass.respond_to?(:new)
38
+ instance = klass.new
39
+ # Set the models attributes if the user passed them in.
40
+ # This will allow attributes to be set regardless if
41
+ # they're required or not.
42
+ instance.attributes = attributes unless attributes.nil?
43
+
44
+ set_required_attributes!(instance, attributes)
45
+ set_unique_attributes!(instance, attributes)
46
+ set_confirmation_attributes!(instance, attributes)
47
+ set_length_validated_attributes!(instance, attributes)
48
+ set_inclusion_validated_attributes!(instance, attributes)
49
+ create_associations(instance, recursed_from_model)
50
+ CustomValidation.stub_custom_validations!(instance)
51
+
52
+ instance
53
+ end
54
+
55
+ def set_required_attributes!(instance, attributes)
56
+ instance.class.required_validations.each do |v|
57
+ # We want to skip over setting any required fields if the field
58
+ # should also be unique. We handle that in the set_unique_attributes!
59
+ # method with a sequence. Also, if it's a confirmation field (e.g. password_confirmation)
60
+ # we can skip it, because that gets set below.
61
+ standard_required_attributes(instance, v) do
62
+ # We don't want to set anything if the user passed in data for this field
63
+ # or if this is a validates_presence_of :some_id. The ids will be set
64
+ # when we create the association.
65
+ next if overridden_attribute?(attributes, v.name)
66
+
67
+ generate_and_set_data(instance, v, false)
68
+ end
69
+ end
70
+ end
71
+
72
+ def set_unique_attributes!(instance, attributes)
73
+ instance.class.unique_validations.each do |v|
74
+ next if overridden_attribute?(attributes, v.name)
75
+ generate_and_set_data(instance, v, true)
76
+ end
77
+ end
78
+
79
+ # This generates set_length_validated_attributes! and set_inclusion_validated_attributes! methods.
80
+ # They used to look like this:
81
+ #
82
+ # def set_length_validated_attributes!(instance, attributes)
83
+ # instance.class.length_validations.each do |v|
84
+ # non_required_attributes(instance, v, attributes) { generate_and_set_data(instance, v, false) }
85
+ # end
86
+ # end
87
+ #
88
+ # def set_inclusion_of_validated_attributes!(instance, attributes)
89
+ # instance.class.inclusion_validations.each do |v|
90
+ # non_required_attributes(instance, v, attributes) { generate_and_set_data(instance, v, false) }
91
+ # end
92
+ # end
93
+ # I'm sure you see the similarities.
94
+ %w(length inclusion).each do |validation|
95
+ define_method("set_#{validation}_validated_attributes!") do |instance, attributes|
96
+ instance.class.send("#{validation}_validations").each do |v|
97
+ # Non required attributes are anything that doesn't have validates_presence_of,
98
+ # validates_uniqueness_of and also anything that hasn't been overriden. These
99
+ # values get set there. So there's no real point in setting them again here.
100
+ non_required_attributes(instance, v, attributes) { generate_and_set_data(instance, v, false) }
101
+ end
102
+ end
103
+ end
104
+
105
+ def set_confirmation_attributes!(instance, attributes)
106
+ instance.class.confirmation_validations.each do |v|
107
+ # This goes in and sets the models confirmation to whatever the corresponding
108
+ # fields value is. (e.g. password_confirmation= password)
109
+ instance.send("#{v.name}_confirmation=", instance.send("#{v.name}"))
110
+ end
111
+ end
112
+
113
+ def create_associations(instance, recursed_from_model = nil)
114
+ # We loop through all the has_one or belongs_to associations on the current instance
115
+ # using introspection, and build up and assign some models to each, if the user has
116
+ # required the id (e.g. requires_presence_of :user_id). So we're only going to build
117
+ # the minimum requirements for each model.
118
+ instance.class.reflect_on_all_associations.each do |a|
119
+ # We only want to create the association if the user has required the id field.
120
+ # This will build the minimum valid requirements.
121
+ next unless has_required_id_validation?(instance, a)
122
+
123
+ if a.macro == :has_one || a.macro == :belongs_to
124
+ # If there's a two way association here (user has_one profile, profile belongs_to user)
125
+ # we only want to create one of those, or it'll recurse forever. That's what the
126
+ # recursed_from_model does.
127
+ unless recursed_from_model.to_s == instance.class.to_s.downcase
128
+ instance.send("#{a.name.to_s}=", build_model_instance(get_assocation_class_name(a), nil, a.name))
129
+ end
130
+ end
131
+
132
+ end
133
+ end
134
+
135
+ def get_assocation_class_name(assocation)
136
+ # This condition is used if the class_name option
137
+ # is passed to has_many or has_one. We'll definitely
138
+ # want to use that key instead of the name of the association.
139
+ if assocation.options.has_key?(:class_name)
140
+ klass = assocation.options[:class_name]
141
+ else
142
+ klass = assocation.name.to_s.singularize.classify
143
+ end
144
+ end
145
+
146
+ def generate_and_set_data(instance, validation, sequenced)
147
+ data_generator = DataGenerator.new(sequenced, validation)
148
+ data_generator.required_length = get_required_length(instance, validation)
149
+ if has_inclusion_validation?(instance, validation)
150
+ data_generator.inclusion_values = get_inclusion_values(instance, validation)
151
+ end
152
+ instance.send("#{validation.name}=", data_generator.generate_data_for_column_type)
153
+ end
154
+
155
+ def standard_required_attributes(instance, validation)
156
+ yield unless has_unique_validation?(instance, validation)
157
+ end
158
+
159
+ def non_required_attributes(instance, validation, attributes)
160
+ yield unless has_unique_validation?(instance, validation) || has_required_validation?(instance, validation) ||
161
+ overridden_attribute?(attributes, validation.name)
162
+ end
163
+
164
+ def overridden_attribute?(attributes, key)
165
+ attributes && attributes.has_key?(key.to_sym)
166
+ end
167
+
168
+ # This creates has_unique_validation?, has_length_validation? etc.
169
+ # We could probably make one method that takes a type, but I'd rather
170
+ # get separate methods for each. Cool?
171
+ %w(unique length required inclusion).each do |v|
172
+ define_method("has_#{v}_validation?") do |instance, validation|
173
+ !instance.class.send("#{v}_validations").select{|u| u.name == validation.name }.empty?
174
+ end
175
+ end
176
+
177
+ def get_required_length(instance, validation)
178
+ return unless has_length_validation?(instance, validation)
179
+
180
+ options = instance.class.length_validations.select{|v| v.name == validation.name }.first.options
181
+
182
+ retval = nil
183
+ # If the validation is validates_length_of :name, :within => 1..20 (or in, which is an alias),
184
+ # let's just return the minimum value of the range.
185
+ %w(within in).each do |o|
186
+ retval = options[o.to_sym].entries.first if options.has_key?(o.to_sym)
187
+ break
188
+ end
189
+ if retval.nil?
190
+ # These other validations should just return the value set.
191
+ %w(is minimum maximum).each do |o|
192
+ retval = options[o.to_sym] if options.has_key?(o.to_sym)
193
+ break
194
+ end
195
+ end
196
+ retval
197
+ end
198
+
199
+ def get_inclusion_values(instance, validation)
200
+ return unless has_inclusion_validation?(instance, validation)
201
+ options = instance.class.inclusion_validations.select{|v| v.name == validation.name }.first.options
202
+ options[:in]
203
+ end
204
+
205
+ def has_required_id_validation?(instance, association)
206
+ name = association.options.has_key?(:class_name) ? association.options[:class_name].downcase : association.name
207
+ !instance.class.required_validations.select{|v| v.name.to_s == "#{name}_id" }.empty?
208
+ end
209
+
210
+ end
211
+
212
+ end
213
+ end
214
+ end
@@ -0,0 +1,15 @@
1
+ # TODO: We need to override any before_validation, or before_validation_on create callbacks as well, I think.
2
+ module Koujou #:nodoc:
3
+ class CustomValidation # :nodoc:
4
+
5
+ # This just goes in and redefines any custom validation methods. Since we can't
6
+ # really ascertain what the intent of those are when koujou runs, it seems
7
+ # like stubbing them out is the most logical choice.
8
+ def self.stub_custom_validations!(instance)
9
+ instance.class.custom_validations.each do |v|
10
+ instance.class.module_eval { define_method(v.name.to_s) { true } }
11
+ end
12
+ end
13
+
14
+ end
15
+ end
@@ -0,0 +1,106 @@
1
+ module Koujou #:nodoc:
2
+ class DataGenerator
3
+
4
+ attr_accessor :required_length, :inclusion_values
5
+
6
+ def initialize(sequenced, validation)
7
+ @sequenced = sequenced
8
+ # Validation is actually a ActiveRecord::Reflection::MacroReflection
9
+ @validation = validation
10
+ @required_length = nil
11
+ @inclusion_values = nil
12
+ end
13
+
14
+ def generate_data_for_column_type
15
+ # So if there was an inclusion passed in for validations_inclusion_of (which is just
16
+ # an enumberable object), let's just return the first element to ensure the value
17
+ # set is the correct one. Mmmkay?
18
+ return get_first_value_for_inclusion unless @inclusion_values.nil?
19
+ # Sometimes models have a validates_presence_of set, but there's no corresponding
20
+ # db column. The only example I can think of for this is a user model where the actual
21
+ # column is hashed_password but the model requires the presence of password. So we'll
22
+ # assume string. This could bite me in the ass later, but we'll see.
23
+ if @validation.active_record.columns_hash.has_key?("#{@validation.name}")
24
+ db_type = @validation.active_record.columns_hash["#{@validation.name}"].type
25
+ else
26
+ db_type = 'string'
27
+ end
28
+ # Since the method names are all based on the db types, we'll just go ahead and
29
+ # dynamically call the appropriate one.
30
+ send("generate_#{db_type}")
31
+ end
32
+
33
+ def generate_string
34
+ retval = ''
35
+ # I don't really like all these elsif's but, apparently
36
+ # it's more efficient than the numerous explicit returns
37
+ # we used to have. SEE: http://gist.github.com/160718
38
+ if @validation.name.to_s.match(/email/)
39
+ retval = format_if_sequenced(Faker::Internet.email)
40
+ elsif @validation.name.to_s == 'first_name'
41
+ retval = format_if_sequenced(Faker::Name.first_name)
42
+ elsif @validation.name.to_s == 'last_name'
43
+ retval = format_if_sequenced(Faker::Name.last_name)
44
+ elsif @validation.name.to_s.match(/login|user_name/)
45
+ retval = format_if_sequenced(Faker::Internet.user_name)
46
+ elsif @validation.name.to_s.match(/city/)
47
+ retval = format_if_sequenced(Faker::Address.city)
48
+ elsif @validation.name.to_s.match(/state|province/)
49
+ retval = format_if_sequenced(Faker::Address.us_state)
50
+ elsif @validation.name.to_s.match(/zip|postal/)
51
+ retval = format_if_sequenced(Faker::Address.zip_code)
52
+ elsif @validation.name.to_s == 'password' || @validation.name.to_s == 'password_confirmation'
53
+ retval = 'koujourama'
54
+ else
55
+ # If we don't match any standard stuff, just return a regular bs lorem string comprised of 10 words.
56
+ # 10 is sort of a "magic number" I might make a constant for that.
57
+ standard_text = format_if_sequenced(Faker::Lorem.words(10).to_s)
58
+ # So if there's a length validation set, we need to return just that amount of data.
59
+ retval = @required_length ? standard_text[0..@required_length - 1].to_s : standard_text
60
+ end
61
+ retval
62
+ end
63
+
64
+ def generate_text
65
+ if @required_length
66
+ # So if there's a length validation set, we need to return just that amount of data.
67
+ Faker::Lorem.paragraph(2).to_s[0..@required_length - 1]
68
+ else
69
+ Faker::Lorem.paragraph.to_s
70
+ end
71
+ end
72
+
73
+ def generate_integer
74
+ generate_number
75
+ end
76
+
77
+ def generate_float
78
+ generate_number.to_f
79
+ end
80
+
81
+ def generate_datetime
82
+ DateTime.now
83
+ end
84
+
85
+ def generate_boolean
86
+ true
87
+ end
88
+
89
+ protected
90
+ def generate_number
91
+ # If this is supposed to be sequenced (aka unique), we'll get the next int from the
92
+ # Sequence class, and randomize that.
93
+ random = rand(999)
94
+ @sequenced ? random + (Sequence.instance.next * rand(2)) : random
95
+ end
96
+
97
+ def format_if_sequenced(val)
98
+ @sequenced ? "#{Sequence.instance.next}#{val}" : val
99
+ end
100
+
101
+ def get_first_value_for_inclusion
102
+ @inclusion_values.first
103
+ end
104
+
105
+ end
106
+ end