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