tb_core 1.4.1 → 1.4.2

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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +34 -2
  3. data/app/assets/javascripts/admin/core/dashboard.js +19 -16
  4. data/app/assets/libs/sortable/sortable.js +1481 -0
  5. data/app/controllers/admin/application_controller.rb +2 -1
  6. data/app/controllers/admin/users_controller.rb +5 -1
  7. data/app/controllers/concerns/tb_core/error_handling.rb +49 -0
  8. data/app/controllers/concerns/tb_core/redirection.rb +23 -0
  9. data/app/controllers/concerns/tb_core/sortable_params.rb +80 -0
  10. data/app/controllers/concerns/tb_core/user_authentication.rb +57 -0
  11. data/app/controllers/spud/application_controller.rb +8 -136
  12. data/app/controllers/tb_core/application_controller.rb +16 -0
  13. data/app/helpers/admin/application_helper.rb +3 -1
  14. data/app/helpers/tb_core/application_helper.rb +32 -0
  15. data/app/views/admin/users/index.html.erb +6 -6
  16. data/config/locales/en.yml +1 -0
  17. data/config/routes.rb +1 -1
  18. data/lib/generators/spud/templates/application_controller.rb +1 -1
  19. data/lib/spud_core/engine.rb +1 -1
  20. data/lib/spud_core/version.rb +1 -1
  21. data/lib/tb_core/form_builder.rb +1 -1
  22. data/lib/tb_core/table_header.rb +92 -0
  23. data/spec/controllers/admin/application_controller_spec.rb +1 -1
  24. data/spec/controllers/{spud → tb_core}/application_controller_spec.rb +1 -1
  25. data/spec/controllers/tb_core/sortable_params_spec.rb +64 -0
  26. data/spec/dummy/app/controllers/application_controller.rb +1 -1
  27. data/spec/helpers/tb_core/application_helper_spec.rb +39 -0
  28. data/spec/rails_helper.rb +3 -4
  29. data/spec/spec_helper.rb +2 -0
  30. metadata +18 -11
  31. data/app/assets/javascripts/admin/core/jquery_ui.js +0 -22
  32. data/app/assets/stylesheets/admin/core/jquery_ui.scss +0 -19
  33. data/app/controllers/spud/admin/application_controller.rb +0 -12
  34. data/app/helpers/spud/application_helper.rb +0 -36
@@ -1,4 +1,5 @@
1
- class Admin::ApplicationController < Spud::ApplicationController
1
+ class Admin::ApplicationController < TbCore::ApplicationController
2
+ include TbCore::SortableParams
2
3
 
3
4
  before_action :require_admin_user
4
5
  add_breadcrumb 'Dashboard', :admin_root_path
@@ -7,8 +7,12 @@ class Admin::UsersController < Admin::ApplicationController
7
7
  after_action :send_credentials_email, only: [:create, :update]
8
8
  respond_to :html, :csv
9
9
 
10
+ sortable_by :email, :current_login_at,
11
+ name: [:last_name, :first_name],
12
+ default: :email
13
+
10
14
  def index
11
- @spud_users = SpudUser.ordered.paginate(page: params[:page], per_page: 15)
15
+ @spud_users = SpudUser.order(sortable_query).paginate(page: params[:page], per_page: 15)
12
16
  if params[:search]
13
17
  @spud_users = @spud_users.where_name_like(params[:search])
14
18
  end
@@ -0,0 +1,49 @@
1
+ module TbCore
2
+ module ErrorHandling
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ rescue_from Spud::RequestError, with: :handle_request_error
7
+ rescue_from ActiveRecord::RecordNotFound, with: :handle_record_not_found
8
+ rescue_from ActionController::UnknownFormat, with: :handle_unknown_format_error
9
+ end
10
+
11
+ def handle_request_error(error)
12
+ error.request_url = request.original_url
13
+ error.template = template_for_request_error() if respond_to?(:template_for_request_error, true)
14
+
15
+ if error.is_a?(Spud::UnauthorizedError) && request.format.html?
16
+ redirect_to(login_path_for_require_user)
17
+ return false
18
+ end
19
+
20
+ do_error_response(error)
21
+ end
22
+
23
+ def do_error_response(error)
24
+ respond_to do |format|
25
+ format.json { render json: { errors: error.message }, status: error.code }
26
+ format.xml { render xml: { errors: error.message }, status: error.code }
27
+ format.all do
28
+ @error = error
29
+ render template: error.template,
30
+ layout: nil,
31
+ formats: [:html],
32
+ status: error.code,
33
+ content_type: 'text/html'
34
+ end
35
+ end
36
+ end
37
+
38
+ def handle_record_not_found(error)
39
+ error = Spud::NotFoundError.new('record')
40
+ handle_request_error(error)
41
+ end
42
+
43
+ def handle_unknown_format_error(error)
44
+ error = Spud::NotFoundError.new()
45
+ handle_request_error(error)
46
+ end
47
+
48
+ end
49
+ end
@@ -0,0 +1,23 @@
1
+ module TbCore
2
+ module Redirection
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ helper_method :back_or_default
7
+ end
8
+
9
+ def redirect_back_or_default(default)
10
+ redirect_to(back_or_default(default))
11
+ end
12
+
13
+ def back_or_default(default = '/')
14
+ if params[:return_to]
15
+ uri = URI.parse(params[:return_to].to_s)
16
+ return "#{uri.path}?#{uri.query}" if uri.query
17
+ return uri.path
18
+ end
19
+ default
20
+ end
21
+
22
+ end
23
+ end
@@ -0,0 +1,80 @@
1
+ module TbCore
2
+ module SortableParams
3
+ extend ActiveSupport::Concern
4
+
5
+ module ClassMethods
6
+ attr_accessor :default_sort_attribute
7
+ attr_accessor :sortable_mapping
8
+
9
+ # Define attributes you wish to sort by
10
+ #
11
+ # This optional configuration method tells the the sortable
12
+ # helpers how they should map attributes to queries. For
13
+ # example a "name" sort might need to sort on first and last
14
+ # name attributes
15
+ #
16
+ # The `default` argument, if passed, will determine the
17
+ # attribute used when no sort param has been passed
18
+ #
19
+ # Usage:
20
+ #
21
+ # sortable_by :email, name: [:last_name, :first_name]
22
+ # default: :email,
23
+ #
24
+ #
25
+ def sortable_by(*attributes, **options)
26
+ @default_sort_attribute = options.delete(:default)
27
+ @sortable_mapping = options.merge(attributes.map { |att| [att, att] }.to_h)
28
+ end
29
+ end
30
+
31
+ # Return the current sort direction
32
+ #
33
+ # Defaults to :asc for all values other than "desc"
34
+ #
35
+ def sort_direction
36
+ if params[:dir].present? && params[:dir] == 'desc'
37
+ :desc
38
+ else
39
+ :asc
40
+ end
41
+ end
42
+
43
+ # Return the current sort param
44
+ #
45
+ def sort_attribute
46
+ params[:sort].try(:to_sym) || self.class.default_sort_attribute
47
+ end
48
+
49
+ # Return a hash that can be used in an ActiveRecord order query
50
+ #
51
+ # Example:
52
+ # MyModel.where(...).order(sortable_query).paginate(...)
53
+ #
54
+ def sortable_query
55
+ return unless sort_attribute
56
+ normalize_sort_value(
57
+ sort_attribute,
58
+ sort_direction
59
+ )
60
+ end
61
+
62
+ # Translate the sort_by and direction into a sortable hash
63
+ #
64
+ def normalize_sort_value(sort_by, direction)
65
+ mapping = self.class.sortable_mapping[sort_by]
66
+ if mapping.is_a? Symbol
67
+ { mapping => sort_direction }
68
+ elsif mapping.is_a? String
69
+ mapping.gsub(':dir', sort_direction.to_s)
70
+ elsif mapping.is_a? Array
71
+ mapping.map { |att| [att, direction] }.to_h
72
+ elsif mapping.is_a? Proc
73
+ mapping.call(direction)
74
+ else
75
+ logger.debug("WARNING: Sort attribute '#{sort_by}' is not recognized. Did you mean to pass it to sortable_by?")
76
+ nil
77
+ end
78
+ end
79
+ end
80
+ end
@@ -0,0 +1,57 @@
1
+ module TbCore
2
+ module UserAuthentication
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ helper_method :current_user_session, :current_user, :current_user_id
7
+ before_action :check_requires_password_change
8
+ around_action :set_time_zone
9
+ end
10
+
11
+ def current_user_session
12
+ return @current_user_session if defined?(@current_user_session)
13
+ @current_user_session = SpudUserSession.find
14
+ end
15
+
16
+ def current_user
17
+ return @current_user if defined?(@current_user)
18
+ @current_user = current_user_session && current_user_session.spud_user
19
+ end
20
+
21
+ def current_user_id
22
+ return 0 unless @current_user
23
+ @current_user.id
24
+ end
25
+
26
+ def require_user
27
+ raise Spud::UnauthorizedError.new unless current_user
28
+ true
29
+ end
30
+
31
+ def require_admin_user
32
+ raise Spud::UnauthorizedError.new unless current_user
33
+ raise Spud::AccessDeniedError.new unless current_user.has_admin_rights?
34
+ true
35
+ end
36
+
37
+ def check_requires_password_change
38
+ if current_user.present? && current_user.requires_password_change?
39
+ redirect_to(login_change_password_path(return_to: request.path))
40
+ return false
41
+ end
42
+ end
43
+
44
+ def set_time_zone
45
+ old_time_zone = Time.zone
46
+ Time.zone = current_user.time_zone if current_user && current_user.time_zone.present?
47
+ yield
48
+ ensure
49
+ Time.zone = old_time_zone
50
+ end
51
+
52
+ def login_path_for_require_user
53
+ login_path(return_to: request.fullpath)
54
+ end
55
+
56
+ end
57
+ end
@@ -1,138 +1,10 @@
1
- class Spud::ApplicationController < ActionController::Base
2
-
3
- protect_from_forgery
4
- helper_method :current_user_session, :current_user, :current_user_id, :back_or_default
5
- before_action :check_requires_password_change
6
- around_action :set_time_zone
7
-
8
- include TbCore::ApplicationHelper
9
- before_action :set_mailer_default_url
10
-
11
- self.responder = TbCore::Responder
12
-
13
- rescue_from Spud::RequestError, with: :handle_request_error
14
- rescue_from ActiveRecord::RecordNotFound, with: :handle_record_not_found
15
- rescue_from ActionController::UnknownFormat, with: :handle_unknown_format_error
16
-
17
- def not_found
18
- raise Spud::NotFoundError
1
+ class Spud::ApplicationController < TbCore::ApplicationController
2
+ def initialize
3
+ ActiveSupport::Deprecation.warn(
4
+ 'Spud::ApplicationController is deprecated and may be removed from future releases,
5
+ use TbCore::ApplicationController instead.',
6
+ caller
7
+ )
8
+ super
19
9
  end
20
-
21
- private
22
-
23
- def set_mailer_default_url
24
- ActionMailer::Base.default_url_options = {host: request.host_with_port}
25
- end
26
-
27
- def current_user_session
28
- return @current_user_session if defined?(@current_user_session)
29
- @current_user_session = SpudUserSession.find
30
- end
31
-
32
- def current_user
33
- return @current_user if defined?(@current_user)
34
- @current_user = current_user_session && current_user_session.spud_user
35
- end
36
-
37
- def current_user_id
38
- if @current_user
39
- return @current_user.id
40
- else
41
- 0
42
- end
43
- end
44
-
45
- def require_user
46
- unless current_user
47
- raise Spud::UnauthorizedError.new()
48
- end
49
- return true
50
- end
51
-
52
- # Override this in a controller to redifine where the login form is
53
- #
54
- def login_path_for_require_user
55
- login_path(return_to: request.fullpath)
56
- end
57
-
58
- def require_admin_user
59
- if current_user.blank?
60
- raise Spud::UnauthorizedError.new()
61
- elsif !current_user.has_admin_rights?
62
- raise Spud::AccessDeniedError.new()
63
- end
64
- end
65
-
66
- def redirect_back_or_default(default)
67
- redirect_to(back_or_default(default))
68
- end
69
-
70
- def back_or_default(default='/')
71
- if params[:return_to]
72
- uri = URI.parse(params[:return_to].to_s)
73
- return "#{uri.path}?#{uri.query}" if uri.query
74
- return uri.path
75
- else
76
- return default
77
- end
78
- end
79
-
80
- def check_requires_password_change
81
- if current_user.present? && current_user.requires_password_change?
82
- redirect_to(login_change_password_path(return_to: request.path))
83
- return false
84
- end
85
- end
86
-
87
- def set_time_zone
88
- old_time_zone = Time.zone
89
- Time.zone = current_user.time_zone if current_user and current_user.time_zone.blank? == false
90
- yield
91
- ensure
92
- Time.zone = old_time_zone
93
- end
94
-
95
- def handle_request_error(error)
96
- error.request_url = request.original_url
97
- error.template = template_for_request_error() if respond_to?(:template_for_request_error, true)
98
-
99
- if error.is_a?(Spud::UnauthorizedError)
100
- if should_present_basic_auth?
101
- headers['WWW-Authenticate'] = "Basic realm=\"#{Spud::Core.config.site_name}\""
102
- elsif request.format.html?
103
- redirect_to(login_path_for_require_user)
104
- return false
105
- end
106
- end
107
-
108
- do_error_response(error)
109
- end
110
-
111
- def do_error_response(error)
112
- respond_to do |format|
113
- format.json { render json: { errors: error.message }, status: error.code }
114
- format.xml { render xml: { errors: error.message }, status: error.code }
115
- format.all {
116
- @error = error
117
- render template: error.template, layout: nil, formats: [:html], status: error.code, content_type: 'text/html'
118
- }
119
- end
120
- end
121
-
122
- def should_present_basic_auth?
123
- return request.headers['X-TWICE-BAKED-BASIC-AUTH'].present?
124
- end
125
-
126
- def handle_record_not_found(error)
127
- error = Spud::NotFoundError.new('record')
128
- handle_request_error(error)
129
- end
130
-
131
- def handle_unknown_format_error(error)
132
- error = Spud::NotFoundError.new()
133
- handle_request_error(error)
134
- end
135
-
136
- ActiveSupport.run_load_hooks(:spud_application_controller, self)
137
-
138
10
  end
@@ -0,0 +1,16 @@
1
+ module TbCore
2
+ class ApplicationController < ActionController::Base
3
+ protect_from_forgery
4
+
5
+ include TbCore::ErrorHandling
6
+ include TbCore::Redirection
7
+ include TbCore::UserAuthentication
8
+ self.responder = TbCore::Responder
9
+
10
+ def not_found
11
+ raise Spud::NotFoundError
12
+ end
13
+
14
+ ActiveSupport.run_load_hooks(:tb_core_application_controller, self)
15
+ end
16
+ end
@@ -35,7 +35,9 @@ module Admin::ApplicationHelper
35
35
  end
36
36
  end
37
37
 
38
- WHITE_LIST_PARAMS = [:tab, :page, :sort, :direction, :search, :id].freeze
38
+ # The following params will automatically be white listed for tabbed navigation
39
+ #
40
+ WHITE_LIST_PARAMS = [:tab, :sort, :direction, :dir, :search, :id].freeze
39
41
 
40
42
  # Build a Bootstrap nav-tabs element
41
43
  #
@@ -67,6 +67,38 @@ module TbCore::ApplicationHelper
67
67
  return content_tag :title, title
68
68
  end
69
69
 
70
+ # Build a table header for a model
71
+ #
72
+ # * path_helper: The helper method you want to use to generate URLs.
73
+ # * model: The class we should use for translations (optional)
74
+ # * permit: Array of request params that are forwarded to the sort links
75
+ #
76
+ # Example:
77
+ #
78
+ # <%= tb_table_header :admin_users_path, model: SpudUser do |t| %>
79
+ # <%= t.sortable :name %>
80
+ # <%= t.sortable :email %>
81
+ # <%= t.header :last_login %>
82
+ # <th></th>
83
+ # <% end %>
84
+ #
85
+ # Header labels will be pulled from en.yml. To provide a different
86
+ # label pass the label: option
87
+ #
88
+ # Example:
89
+ #
90
+ # <%= t.header :name, label: 'Full Name' %>
91
+ #
92
+ def tb_table_header(path_helper, model: nil, permit: [], &block)
93
+ header = TbCore::TableHeader.new(
94
+ path_helper: path_helper,
95
+ model: model,
96
+ params: params.permit(permit.concat([:sort, :dir, :search, :tab])),
97
+ context: self)
98
+ header.capture(block) if block
99
+ header.to_html
100
+ end
101
+
70
102
  def current_site_name
71
103
  return Spud::Core.config.site_name
72
104
  end
@@ -8,12 +8,12 @@
8
8
  <%= content_for :detail do %>
9
9
  <div class="table-responsive">
10
10
  <table class="table table-striped table-hover">
11
- <thead>
12
- <th>Name</th>
13
- <th>Email</th>
14
- <th>Last Login</th>
15
- <th>&nbsp;</th>
16
- </thead>
11
+ <%= tb_table_header :admin_users_path, model: SpudUser do |t| %>
12
+ <%= t.sortable :name %>
13
+ <%= t.sortable :email %>
14
+ <%= t.sortable :current_login_at %>
15
+ <th></th>
16
+ <% end %>
17
17
  <tbody>
18
18
  <% @spud_users.each do |spud_user| %>
19
19
  <tr>
@@ -20,6 +20,7 @@ en:
20
20
  spud_role_id: 'Role'
21
21
  password_confirmation: 'Confirm password'
22
22
  requires_password_change: 'Require change'
23
+ current_login_at: 'Logged in at'
23
24
  tb_core_mailer:
24
25
  forgot_password_notification:
25
26
  subject: 'Forgot Password Request from %{site_name}'
data/config/routes.rb CHANGED
@@ -32,6 +32,6 @@ Rails.application.routes.draw do
32
32
  resources :password_resets, :only => [:index, :create, :show, :update], :path => 'login/forgot'
33
33
 
34
34
  get 'spud/admin' => 'admin/user_sessions#legacy_redirect'
35
- get 'spud/not_found' => 'spud/application#not_found', :as => 'not_found'
35
+ get 'tb_core/not_found' => 'tb_core/application#not_found', as: 'not_found' unless Rails.env.production?
36
36
 
37
37
  end
@@ -1,3 +1,3 @@
1
- class ApplicationController < Spud::ApplicationController
1
+ class ApplicationController < TbCore::ApplicationController
2
2
  protect_from_forgery
3
3
  end
@@ -1,6 +1,5 @@
1
1
  require 'responders'
2
2
  require 'jquery-rails'
3
- require 'jquery-ui-rails'
4
3
  require 'authlogic'
5
4
  require 'bootstrap-sass'
6
5
  require 'autoprefixer-rails'
@@ -18,6 +17,7 @@ module Spud
18
17
  require "#{root}/lib/spud_core/errors"
19
18
  require "#{root}/lib/spud_core/searchable"
20
19
  require "#{root}/lib/tb_core/form_builder"
20
+ require "#{root}/lib/tb_core/table_header"
21
21
 
22
22
  engine_name :tb_core
23
23
  config.autoload_paths << "#{root}/lib"
@@ -1,5 +1,5 @@
1
1
  module Spud
2
2
  module Core
3
- VERSION = '1.4.1'.freeze
3
+ VERSION = '1.4.2'.freeze
4
4
  end
5
5
  end
@@ -143,7 +143,7 @@ class TbCore::FormBuilder < ActionView::Helpers::FormBuilder
143
143
  # ie, container div + label + input + error message
144
144
  #
145
145
 
146
- def tb_sub_title(text)
146
+ def tb_sub_title(text, options={})
147
147
  content_tag :div, options.merge(class: 'form-group') do
148
148
  content_tag :div, class: 'col-sm-offset-2 col-sm-10' do
149
149
  content_tag :h4, text, class: 'form-sub-title'
@@ -0,0 +1,92 @@
1
+ module TbCore
2
+ class TableHeader
3
+ attr_reader :context
4
+ attr_accessor :header_html
5
+
6
+ # Forward view helper methods to the view context
7
+ delegate :concat, :content_tag, :link_to, to: :context
8
+
9
+ def initialize(path_helper:, model: nil, params: {}, context:)
10
+ @path_helper = path_helper
11
+ @model = model
12
+ @params = params
13
+ @context = context
14
+ end
15
+
16
+ def capture(block)
17
+ @html = context.capture do
18
+ block.call(self)
19
+ end
20
+ end
21
+
22
+ def sortable(attribute, label: nil)
23
+ content_tag :th, class: classes_for_attribute(attribute) do
24
+ concat(link_to_for_attribute(attribute, label))
25
+ concat(sort_arrow_for_attribute(attribute))
26
+ end
27
+ end
28
+
29
+ def header(attribute, label: nil)
30
+ label ||= translated_attribute(attribute)
31
+ content_tag :th, label
32
+ end
33
+
34
+ def to_row
35
+ content_tag(:tr, @html)
36
+ end
37
+
38
+ def to_html
39
+ content_tag :thead, to_row, class: 'tb-table-header'
40
+ end
41
+
42
+ private
43
+
44
+ def translated_attribute(attribute)
45
+ if @model
46
+ @model.human_attribute_name(attribute)
47
+ else
48
+ context.t(attribute)
49
+ end
50
+ end
51
+
52
+ def current_direction
53
+ @params[:dir] || 'asc'
54
+ end
55
+
56
+ def inverted_direction
57
+ current_direction == 'asc' ? 'desc' : 'asc'
58
+ end
59
+
60
+ def determine_direction(attribute)
61
+ if @params[:sort].nil? || @params[:sort] != attribute.to_s
62
+ 'asc'
63
+ else
64
+ inverted_direction
65
+ end
66
+ end
67
+
68
+ def classes_for_attribute(attribute)
69
+ classes = ['sortable']
70
+ if @params[:sort] == attribute.to_s
71
+ classes << 'sortable-active'
72
+ classes << "sortable-#{current_direction}"
73
+ end
74
+ classes.join(' ')
75
+ end
76
+
77
+ def link_to_for_attribute(attribute, label)
78
+ label ||= translated_attribute(attribute)
79
+ path = context.send(
80
+ @path_helper,
81
+ @params.merge(sort: attribute, dir: determine_direction(attribute))
82
+ )
83
+ link_to(label, path)
84
+ end
85
+
86
+ def sort_arrow_for_attribute(attribute)
87
+ return '' unless @params[:sort] == attribute.to_s
88
+ arrow_class = current_direction == 'asc' ? 'up' : 'down'
89
+ content_tag(:span, '', class: "glyphicon glyphicon-arrow-#{arrow_class}")
90
+ end
91
+ end
92
+ end
@@ -16,7 +16,7 @@ RSpec.describe Admin::ApplicationController, type: :controller do
16
16
  end
17
17
  end
18
18
  it 'should respond successfully if the current user is a super admin' do
19
- @user.update_attribute(:super_admin, true)
19
+ @user.update_attributes(super_admin: true)
20
20
  get :index
21
21
  expect(response).to be_success
22
22
  end
@@ -2,7 +2,7 @@ require 'rails_helper'
2
2
  require 'rspec/mocks'
3
3
  require 'rspec/mocks/standalone'
4
4
 
5
- describe Spud::ApplicationController, type: :controller do
5
+ describe TbCore::ApplicationController, type: :controller do
6
6
 
7
7
  describe 'not_found' do
8
8
  it 'throws a 404 error' do