kitestrings 1.0.0

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