kitestrings 1.0.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.
- checksums.yaml +7 -0
- data/.gitignore +23 -0
- data/.rspec +2 -0
- data/Gemfile +4 -0
- data/LICENSE.txt +22 -0
- data/README.md +38 -0
- data/Rakefile +10 -0
- data/kitestrings.gemspec +28 -0
- data/lib/array_validator.rb +22 -0
- data/lib/async.rb +26 -0
- data/lib/form_object.rb +96 -0
- data/lib/generators/kitestrings/install_generator.rb +41 -0
- data/lib/generators/kitestrings/messages_generator.rb +11 -0
- data/lib/generators/kitestrings/templates/message.rb.erb +56 -0
- data/lib/generators/templates/deploy.rb +136 -0
- data/lib/generators/templates/deploy/integ.rb +21 -0
- data/lib/generators/templates/deploy/production.rb +26 -0
- data/lib/generators/templates/deploy/uat.rb +23 -0
- data/lib/generators/templates/haml/scaffold/_form.html.haml +17 -0
- data/lib/generators/templates/haml/scaffold/edit.html.haml +6 -0
- data/lib/generators/templates/haml/scaffold/index.html.haml +27 -0
- data/lib/generators/templates/haml/scaffold/new.html.haml +6 -0
- data/lib/generators/templates/haml/scaffold/show.html.haml +10 -0
- data/lib/generators/templates/rails/scaffold_controller/controller.rb +62 -0
- data/lib/generators/templates/rspec/helper/helper_spec.rb +17 -0
- data/lib/generators/templates/rspec/integration/request.rb +4 -0
- data/lib/generators/templates/rspec/model/model_spec.rb +14 -0
- data/lib/generators/templates/rspec/scaffold/controller_spec.rb +131 -0
- data/lib/generators/templates/rspec/scaffold/routing_spec.rb +18 -0
- data/lib/generators/templates/spec_ext/my_ip_spec.rb +7 -0
- data/lib/generators/templates/spec_ext/spec_helper_ext.rb +37 -0
- data/lib/generators/templates/views/application/_navigation.html.haml +0 -0
- data/lib/generators/templates/views/layouts/application.html.haml +23 -0
- data/lib/kitestrings.rb +6 -0
- data/lib/kitestrings/railtie.rb +15 -0
- data/lib/kitestrings/version.rb +3 -0
- data/lib/page_and_sort_helper.rb +99 -0
- data/lib/size_validator.rb +22 -0
- data/lib/tasks/test_prepare_after_migrate.rake +11 -0
- data/lib/uniqueness_without_deleted_validator.rb +7 -0
- data/spec/lib/array_validator_spec.rb +40 -0
- data/spec/lib/async_spec.rb +49 -0
- data/spec/lib/generators/kitestrings/install_generator_spec.rb +53 -0
- data/spec/lib/generators/kitestrings/messages_generator_spec.rb +19 -0
- data/spec/lib/page_and_sort_helper_spec.rb +96 -0
- data/spec/lib/size_validator_spec.rb +40 -0
- data/spec/spec_helper.rb +17 -0
- metadata +181 -0
File without changes
|
@@ -0,0 +1,23 @@
|
|
1
|
+
!!! 5
|
2
|
+
%html
|
3
|
+
%head
|
4
|
+
%meta{"name"=>"viewport", "content"=>"width=device-width", "initial-scale"=>1}
|
5
|
+
= csrf_meta_tag
|
6
|
+
= javascript_include_tag :application
|
7
|
+
= stylesheet_link_tag :application, media: 'all'
|
8
|
+
|
9
|
+
/[if lt IE 9]
|
10
|
+
= javascript_include_tag '//cdnjs.cloudflare.com/ajax/libs/html5shiv/3.7/html5shiv.min.js'
|
11
|
+
%body
|
12
|
+
.row
|
13
|
+
.col-md-12
|
14
|
+
.container
|
15
|
+
.row
|
16
|
+
= render 'navigation'
|
17
|
+
.row
|
18
|
+
- if alert
|
19
|
+
%p.alert= alert
|
20
|
+
- if notice
|
21
|
+
%p.notice= notice
|
22
|
+
.row
|
23
|
+
= yield
|
data/lib/kitestrings.rb
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'kitestrings'
|
2
|
+
require 'rails'
|
3
|
+
module Kitestrings
|
4
|
+
class Railtie < ::Rails::Railtie
|
5
|
+
railtie_name :kitestrings
|
6
|
+
|
7
|
+
rake_tasks do
|
8
|
+
load "tasks/test_prepare_after_migrate.rake"
|
9
|
+
end
|
10
|
+
|
11
|
+
initializer 'kitestrings.autoload', :before => :set_autoload_paths do |app|
|
12
|
+
app.config.autoload_paths << File.expand_path("../../", __FILE__)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module PageAndSortHelper
|
2
|
+
|
3
|
+
# read the sort column from the params hash, returning it as a symbol, or nil
|
4
|
+
# @return [Symbol]
|
5
|
+
def sort_column
|
6
|
+
params[:sort].try(:to_sym) || @_sort_order
|
7
|
+
end
|
8
|
+
|
9
|
+
# return the sort direction, defaulting to ascending, as a symbol. :asc or :desc
|
10
|
+
# @return [Symbol]
|
11
|
+
def sort_direction(default=nil)
|
12
|
+
direction = (params[:sort_direction] || @_sort_direction || default || :asc).to_sym
|
13
|
+
[:asc, :desc].include?(direction) ? direction : :asc
|
14
|
+
end
|
15
|
+
|
16
|
+
# create a link for the field by localising the field name and appending an up or down arrow indicating the current
|
17
|
+
# sort order if this field is the currently sorted field. The sort order in the link is opposite to the current
|
18
|
+
# sort order so that the user can toggle the sort order by clicking the link again.
|
19
|
+
#
|
20
|
+
# This defaults to looking up the field name on the class used in the controller for +page_and_sort+.
|
21
|
+
def sortable_title(field, options={})
|
22
|
+
klass = options[:class]
|
23
|
+
title = h(options[:title] || (klass || @_sort_klass).human_attribute_name(field))
|
24
|
+
|
25
|
+
direction = :asc
|
26
|
+
if sort_column == field
|
27
|
+
if sort_direction == :asc
|
28
|
+
title += " ↓".html_safe
|
29
|
+
direction = :desc
|
30
|
+
else
|
31
|
+
title += " ↑".html_safe
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
link_to title, params.merge(sort: field, sort_direction: direction)
|
36
|
+
end
|
37
|
+
|
38
|
+
module Controller
|
39
|
+
|
40
|
+
# return the given scope paged and sorted according to the query modifiers present in params. This includes:
|
41
|
+
# 1. Using kaminari for paging long lists.
|
42
|
+
# 2. Sorting the results by params[:sort] in the params[:sort_direction] direction (default to ASC)
|
43
|
+
#
|
44
|
+
# Note, complex order bys can be delegated to a class method (or scope) which will be passed the
|
45
|
+
# direction ("asc" or "desc"). The class method / scope should be named "order_by_" followed by the name of the
|
46
|
+
# sort.
|
47
|
+
#
|
48
|
+
# For example:
|
49
|
+
# class Model < ActiveRecord::Base
|
50
|
+
# scope :order_by_complex_thing, lamda { |direction| order("complex order clause here") }
|
51
|
+
# end
|
52
|
+
#
|
53
|
+
# And use this by params[:sort] = "complex_thing"
|
54
|
+
def page_and_sort(scope, options={})
|
55
|
+
|
56
|
+
# get the ActiveRecord class for the scope.
|
57
|
+
klass =
|
58
|
+
case
|
59
|
+
when Array
|
60
|
+
scope
|
61
|
+
when scope.respond_to?(:scoped)
|
62
|
+
scope.scoped.klass
|
63
|
+
when ActiveRecord::Base
|
64
|
+
scope.all
|
65
|
+
else
|
66
|
+
scope
|
67
|
+
end
|
68
|
+
|
69
|
+
# save the sorting object class, so that it can be used as the localisation scope to look up the attribute
|
70
|
+
# name for the link
|
71
|
+
@_sort_klass = klass
|
72
|
+
|
73
|
+
# apply the order scope if present, prefering a class method / scope if defined, otherwise composing the clause
|
74
|
+
# using arel attributes so the sort order is safe to use with queries with joins, etc.
|
75
|
+
field = sort_column || options[:default_sort]
|
76
|
+
if field
|
77
|
+
# save the sort order in the controller's instance variables where it can be accessed by the view helper
|
78
|
+
# above to show the current sort order even if it is not specified by params[:sort]
|
79
|
+
@_sort_order = field
|
80
|
+
@_sort_direction = sort_direction(options[:default_direction])
|
81
|
+
|
82
|
+
scope =
|
83
|
+
if klass.respond_to? "order_by_#{field}"
|
84
|
+
scope.__send__ "order_by_#{field}", @_sort_direction
|
85
|
+
else
|
86
|
+
attribute = klass.arel_table[field]
|
87
|
+
scope.order(@_sort_direction == :asc ? attribute.asc : attribute.desc)
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
if options[:skip_pagination]
|
92
|
+
scope
|
93
|
+
else
|
94
|
+
# apply kaminari pagination
|
95
|
+
scope.page(params[:page])
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# A validator to validate the length of an array. Works on a collection proxy (has_many relationship) too.
|
2
|
+
class SizeValidator < ActiveModel::EachValidator
|
3
|
+
# Validate the value is an Array and optionally that the number of items in it is at least a minimum or at most a
|
4
|
+
# maximum.
|
5
|
+
#
|
6
|
+
# Example:
|
7
|
+
# validates :address_states, size: {min: 1, min_message: "must have at least 1 State selected"},
|
8
|
+
#
|
9
|
+
def validate_each(record, attribute, value)
|
10
|
+
if value.respond_to?(:size)
|
11
|
+
if options[:min].present? && value.size < options[:min]
|
12
|
+
record.errors.add attribute, options[:min_message] || "must have at least #{options[:min]} #{'item'.pluralize(options[:min])}"
|
13
|
+
end
|
14
|
+
|
15
|
+
if options[:max].present? && value.size > options[:max]
|
16
|
+
record.errors.add attribute, options[:max_message] || "must have at most #{options[:max]} #{'item'.pluralize(options[:max])}"
|
17
|
+
end
|
18
|
+
else
|
19
|
+
record.errors.add attribute, "must be sizeable"
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_support'
|
3
|
+
require 'active_model'
|
4
|
+
require 'array_validator'
|
5
|
+
|
6
|
+
class ThingWithItems
|
7
|
+
include ActiveModel::Validations
|
8
|
+
attr_accessor :items
|
9
|
+
|
10
|
+
def initialize(items)
|
11
|
+
@items = items
|
12
|
+
end
|
13
|
+
|
14
|
+
validates :items, array: {min: 1, max: 5}
|
15
|
+
end
|
16
|
+
|
17
|
+
describe ArrayValidator do
|
18
|
+
|
19
|
+
before { subject.valid? }
|
20
|
+
|
21
|
+
context "validating an object with no items" do
|
22
|
+
subject { ThingWithItems.new(nil) }
|
23
|
+
it { expect(subject.errors[:items]).to include("must be an array") }
|
24
|
+
end
|
25
|
+
|
26
|
+
context "validating an object with an empty array" do
|
27
|
+
subject { ThingWithItems.new([])}
|
28
|
+
it { expect(subject.errors[:items]).to include("must have at least 1 item") }
|
29
|
+
end
|
30
|
+
|
31
|
+
context "validating an object with an empty array" do
|
32
|
+
subject { ThingWithItems.new([1,2,3,4,5,6])}
|
33
|
+
it { expect(subject.errors[:items]).to include("must have at most 5 items") }
|
34
|
+
end
|
35
|
+
|
36
|
+
context "validation passes" do
|
37
|
+
subject { ThingWithItems.new([1,2,3])}
|
38
|
+
it { expect(subject.errors[:items]).to be_empty }
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'async'
|
3
|
+
|
4
|
+
module ActiveRecord
|
5
|
+
class Base
|
6
|
+
end
|
7
|
+
end
|
8
|
+
|
9
|
+
describe Async do
|
10
|
+
class Example
|
11
|
+
include Async
|
12
|
+
|
13
|
+
attr :result
|
14
|
+
|
15
|
+
def calculate
|
16
|
+
async do
|
17
|
+
@result = Thread.current
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
before do
|
23
|
+
# stub active record for tests so we don't need a database connection.
|
24
|
+
pool = double("connection pool")
|
25
|
+
pool.stub(:with_connection).and_yield
|
26
|
+
ActiveRecord::Base.stub(:connection_pool => pool)
|
27
|
+
end
|
28
|
+
|
29
|
+
subject { Example.new }
|
30
|
+
|
31
|
+
it "done on same thread in tests" do
|
32
|
+
Thread.should_not_receive :new
|
33
|
+
subject.calculate
|
34
|
+
expect(subject.result).to eq(Thread.current)
|
35
|
+
end
|
36
|
+
|
37
|
+
it "done on a different thread" do
|
38
|
+
Async.stub :enabled => true
|
39
|
+
thread = subject.calculate
|
40
|
+
expect(thread).to be_a(Thread)
|
41
|
+
expect(thread).not_to eq(Thread.current)
|
42
|
+
thread.join
|
43
|
+
expect(subject.result).to eq(thread)
|
44
|
+
end
|
45
|
+
|
46
|
+
it "raises an exception if no block is given" do
|
47
|
+
expect { subject.async }.to raise_error(RuntimeError)
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'generator_spec'
|
4
|
+
require 'generators/kitestrings/install_generator'
|
5
|
+
|
6
|
+
describe Kitestrings::Generators::InstallGenerator do
|
7
|
+
destination File.expand_path("../../../../tmp", __FILE__)
|
8
|
+
|
9
|
+
before :all do
|
10
|
+
prepare_destination
|
11
|
+
run_generator
|
12
|
+
@dir = self.class.test_case.destination_root
|
13
|
+
end
|
14
|
+
|
15
|
+
def file_path(path)
|
16
|
+
File.join(@dir, path)
|
17
|
+
end
|
18
|
+
|
19
|
+
def file_contents(path)
|
20
|
+
File.read(file_path(path))
|
21
|
+
end
|
22
|
+
|
23
|
+
context "check files" do
|
24
|
+
%w[
|
25
|
+
spec_ext/my_ip_spec.rb
|
26
|
+
spec_ext/spec_helper_ext.rb
|
27
|
+
config/deploy.rb
|
28
|
+
config/deploy/integ.rb
|
29
|
+
config/deploy/uat.rb
|
30
|
+
config/deploy/production.rb
|
31
|
+
lib/templates/haml/scaffold/_form.html.haml
|
32
|
+
lib/templates/haml/scaffold/edit.html.haml
|
33
|
+
lib/templates/haml/scaffold/index.html.haml
|
34
|
+
lib/templates/haml/scaffold/new.html.haml
|
35
|
+
lib/templates/haml/scaffold/show.html.haml
|
36
|
+
lib/templates/rails/scaffold_controller/controller.rb
|
37
|
+
lib/templates/rspec/helper/helper_spec.rb
|
38
|
+
lib/templates/rspec/integration/request.rb
|
39
|
+
lib/templates/rspec/model/model_spec.rb
|
40
|
+
lib/templates/rspec/scaffold/controller_spec.rb
|
41
|
+
lib/templates/rspec/scaffold/routing_spec.rb
|
42
|
+
].each do |file|
|
43
|
+
it "created #{file}" do
|
44
|
+
path = File.join(@dir, file)
|
45
|
+
expect(File.exist?(path)).to be_truthy
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# otherwise it wants to run the specs in the tmp/spec directory
|
51
|
+
after(:all) { rm_rf(@dir) }
|
52
|
+
|
53
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'fileutils'
|
3
|
+
require 'generator_spec'
|
4
|
+
require 'generators/kitestrings/messages_generator'
|
5
|
+
require 'rails'
|
6
|
+
|
7
|
+
describe Kitestrings::Generators::MessagesGenerator do
|
8
|
+
destination File.expand_path("../../../../tmp", __FILE__)
|
9
|
+
|
10
|
+
before :all do
|
11
|
+
prepare_destination
|
12
|
+
run_generator
|
13
|
+
@dir = self.class.test_case.destination_root
|
14
|
+
end
|
15
|
+
|
16
|
+
it do
|
17
|
+
expect(File.exist?(File.join(@dir, "app/models/message.rb")))
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'page_and_sort_helper'
|
3
|
+
require 'active_record'
|
4
|
+
|
5
|
+
|
6
|
+
describe PageAndSortHelper do
|
7
|
+
|
8
|
+
# TODO: This spec file needs a test database to talk to. We don't have one set up for this gem yet.
|
9
|
+
|
10
|
+
class Record < ActiveRecord::Base
|
11
|
+
end
|
12
|
+
|
13
|
+
context "page_and_sort" do
|
14
|
+
let(:params) { {} }
|
15
|
+
let(:klass) do
|
16
|
+
Struct.new :params do
|
17
|
+
include PageAndSortHelper
|
18
|
+
include PageAndSortHelper::Controller
|
19
|
+
end
|
20
|
+
end
|
21
|
+
subject { klass.new(params) }
|
22
|
+
|
23
|
+
def scope_of(klass)
|
24
|
+
if ActiveRecord::VERSION::MAJOR >= 4
|
25
|
+
klass.all
|
26
|
+
else
|
27
|
+
klass.scoped
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
context "record sort" do
|
32
|
+
let(:params) { {sort: "name", sort_direction: "asc"} }
|
33
|
+
it "on Record.scoped" do
|
34
|
+
scope = subject.page_and_sort(scope_of(Record))
|
35
|
+
expect(scope.to_sql).to include("ORDER BY `companies`.`name` ASC")
|
36
|
+
end
|
37
|
+
it "on Record" do
|
38
|
+
scope = subject.page_and_sort(Record)
|
39
|
+
expect(scope.to_sql).to include("ORDER BY `companies`.`name` ASC")
|
40
|
+
end
|
41
|
+
it "on Record.where" do
|
42
|
+
scope = subject.page_and_sort(Record.where("a = b"))
|
43
|
+
expect(scope.to_sql).to include("ORDER BY `companies`.`name` ASC")
|
44
|
+
expect(scope.to_sql).to include("a = b")
|
45
|
+
end
|
46
|
+
end
|
47
|
+
context "record sort desc" do
|
48
|
+
let(:params) { {sort: "name", sort_direction: "desc"} }
|
49
|
+
it "on Record.scoped" do
|
50
|
+
scope = subject.page_and_sort(scope_of(Record))
|
51
|
+
expect(scope.to_sql).to include("ORDER BY `companies`.`name` DESC")
|
52
|
+
end
|
53
|
+
it "on Record" do
|
54
|
+
scope = subject.page_and_sort(Record)
|
55
|
+
expect(scope.to_sql).to include("ORDER BY `companies`.`name` DESC")
|
56
|
+
end
|
57
|
+
it "on Record.where" do
|
58
|
+
scope = subject.page_and_sort(Record.where("a = b"))
|
59
|
+
expect(scope.to_sql).to include("ORDER BY `companies`.`name` DESC")
|
60
|
+
expect(scope.to_sql).to include("a = b")
|
61
|
+
end
|
62
|
+
end
|
63
|
+
context "record sort asc" do
|
64
|
+
let(:params) { {sort: "record_type", sort_direction: "asc"} }
|
65
|
+
it "on Record.scoped" do
|
66
|
+
scope = subject.page_and_sort(scope_of(Record))
|
67
|
+
expect(scope.to_sql).to match(/ORDER BY CASE genre_id .* END ASC/)
|
68
|
+
end
|
69
|
+
it "on Record" do
|
70
|
+
scope = subject.page_and_sort(Record)
|
71
|
+
expect(scope.to_sql).to match(/ORDER BY CASE genre_id .* END ASC/)
|
72
|
+
end
|
73
|
+
it "on Record.where" do
|
74
|
+
scope = subject.page_and_sort(Record.where("a = b"))
|
75
|
+
expect(scope.to_sql).to match(/ORDER BY CASE genre_id .* END ASC/)
|
76
|
+
expect(scope.to_sql).to include("a = b")
|
77
|
+
end
|
78
|
+
end
|
79
|
+
context "record sort desc" do
|
80
|
+
let(:params) { {sort: "record_type", sort_direction: "desc"} }
|
81
|
+
it "on Record.scoped" do
|
82
|
+
scope = subject.page_and_sort(scope_of(Record))
|
83
|
+
expect(scope.to_sql).to match(/ORDER BY CASE genre_id .* END DESC/)
|
84
|
+
end
|
85
|
+
it "on Record" do
|
86
|
+
scope = subject.page_and_sort(Record)
|
87
|
+
expect(scope.to_sql).to match(/ORDER BY CASE genre_id .* END DESC/)
|
88
|
+
end
|
89
|
+
it "on Record.where" do
|
90
|
+
scope = subject.page_and_sort(Record.where("a = b"))
|
91
|
+
expect(scope.to_sql).to match(/ORDER BY CASE genre_id .* END DESC/)
|
92
|
+
expect(scope.to_sql).to include("a = b")
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|