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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +23 -0
  3. data/.rspec +2 -0
  4. data/Gemfile +4 -0
  5. data/LICENSE.txt +22 -0
  6. data/README.md +38 -0
  7. data/Rakefile +10 -0
  8. data/kitestrings.gemspec +28 -0
  9. data/lib/array_validator.rb +22 -0
  10. data/lib/async.rb +26 -0
  11. data/lib/form_object.rb +96 -0
  12. data/lib/generators/kitestrings/install_generator.rb +41 -0
  13. data/lib/generators/kitestrings/messages_generator.rb +11 -0
  14. data/lib/generators/kitestrings/templates/message.rb.erb +56 -0
  15. data/lib/generators/templates/deploy.rb +136 -0
  16. data/lib/generators/templates/deploy/integ.rb +21 -0
  17. data/lib/generators/templates/deploy/production.rb +26 -0
  18. data/lib/generators/templates/deploy/uat.rb +23 -0
  19. data/lib/generators/templates/haml/scaffold/_form.html.haml +17 -0
  20. data/lib/generators/templates/haml/scaffold/edit.html.haml +6 -0
  21. data/lib/generators/templates/haml/scaffold/index.html.haml +27 -0
  22. data/lib/generators/templates/haml/scaffold/new.html.haml +6 -0
  23. data/lib/generators/templates/haml/scaffold/show.html.haml +10 -0
  24. data/lib/generators/templates/rails/scaffold_controller/controller.rb +62 -0
  25. data/lib/generators/templates/rspec/helper/helper_spec.rb +17 -0
  26. data/lib/generators/templates/rspec/integration/request.rb +4 -0
  27. data/lib/generators/templates/rspec/model/model_spec.rb +14 -0
  28. data/lib/generators/templates/rspec/scaffold/controller_spec.rb +131 -0
  29. data/lib/generators/templates/rspec/scaffold/routing_spec.rb +18 -0
  30. data/lib/generators/templates/spec_ext/my_ip_spec.rb +7 -0
  31. data/lib/generators/templates/spec_ext/spec_helper_ext.rb +37 -0
  32. data/lib/generators/templates/views/application/_navigation.html.haml +0 -0
  33. data/lib/generators/templates/views/layouts/application.html.haml +23 -0
  34. data/lib/kitestrings.rb +6 -0
  35. data/lib/kitestrings/railtie.rb +15 -0
  36. data/lib/kitestrings/version.rb +3 -0
  37. data/lib/page_and_sort_helper.rb +99 -0
  38. data/lib/size_validator.rb +22 -0
  39. data/lib/tasks/test_prepare_after_migrate.rake +11 -0
  40. data/lib/uniqueness_without_deleted_validator.rb +7 -0
  41. data/spec/lib/array_validator_spec.rb +40 -0
  42. data/spec/lib/async_spec.rb +49 -0
  43. data/spec/lib/generators/kitestrings/install_generator_spec.rb +53 -0
  44. data/spec/lib/generators/kitestrings/messages_generator_spec.rb +19 -0
  45. data/spec/lib/page_and_sort_helper_spec.rb +96 -0
  46. data/spec/lib/size_validator_spec.rb +40 -0
  47. data/spec/spec_helper.rb +17 -0
  48. metadata +181 -0
@@ -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
@@ -0,0 +1,6 @@
1
+ require 'rails'
2
+ require "kitestrings/version"
3
+ require "kitestrings/railtie" if defined? Rails
4
+
5
+ module Kitestrings
6
+ end
@@ -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,3 @@
1
+ module Kitestrings
2
+ VERSION = "1.0.0"
3
+ 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 += " &#x2193;".html_safe
29
+ direction = :desc
30
+ else
31
+ title += " &#x2191;".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,11 @@
1
+ namespace :db do
2
+ task :migrate do
3
+ Rake::Task["db:test:prepare"].invoke if Rails.env.development?
4
+ end
5
+
6
+ namespace :test do
7
+ task :prepare do
8
+ Rake::Task["db:fixtures:load"].invoke
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,7 @@
1
+ class UniquenessWithoutDeletedValidator < ActiveRecord::Validations::UniquenessValidator
2
+ protected
3
+
4
+ def build_relation(klass, table, attribute, value) #:nodoc:
5
+ super.and(table[klass.paranoia_column].eq(nil))
6
+ end
7
+ 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