godmin 0.9.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 (125) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +24 -0
  3. data/.hound.yml +2 -0
  4. data/.rubocop.yml +7 -0
  5. data/.travis.yml +7 -0
  6. data/Gemfile +11 -0
  7. data/MIT-LICENSE +20 -0
  8. data/README.md +523 -0
  9. data/Rakefile +34 -0
  10. data/app/assets/images/godmin/.keep +0 -0
  11. data/app/assets/javascripts/godmin/application.js +19 -0
  12. data/app/assets/javascripts/godmin/batch_actions.js +42 -0
  13. data/app/assets/javascripts/godmin/navigation.js +9 -0
  14. data/app/assets/javascripts/godmin/select_tags.js +8 -0
  15. data/app/assets/stylesheets/godmin/application.css.scss +62 -0
  16. data/app/views/godmin/application/welcome.html.erb +65 -0
  17. data/app/views/godmin/resource/_actions.html.erb +10 -0
  18. data/app/views/godmin/resource/_batch_actions.html.erb +12 -0
  19. data/app/views/godmin/resource/_breadcrumb.html.erb +21 -0
  20. data/app/views/godmin/resource/_errors.html.erb +9 -0
  21. data/app/views/godmin/resource/_filters.html.erb +23 -0
  22. data/app/views/godmin/resource/_form.html.erb +12 -0
  23. data/app/views/godmin/resource/_pagination.html.erb +6 -0
  24. data/app/views/godmin/resource/_scopes.html.erb +11 -0
  25. data/app/views/godmin/resource/_table.html.erb +36 -0
  26. data/app/views/godmin/resource/columns/_actions.html.erb +11 -0
  27. data/app/views/godmin/resource/edit.html.erb +5 -0
  28. data/app/views/godmin/resource/index.html.erb +17 -0
  29. data/app/views/godmin/resource/new.html.erb +5 -0
  30. data/app/views/godmin/resource/show.html.erb +1 -0
  31. data/app/views/godmin/sessions/new.html.erb +9 -0
  32. data/app/views/godmin/shared/_navigation.html.erb +29 -0
  33. data/app/views/kaminari/_first_page.html.erb +3 -0
  34. data/app/views/kaminari/_gap.html.erb +3 -0
  35. data/app/views/kaminari/_last_page.html.erb +3 -0
  36. data/app/views/kaminari/_next_page.html.erb +3 -0
  37. data/app/views/kaminari/_page.html.erb +3 -0
  38. data/app/views/kaminari/_paginator.html.erb +15 -0
  39. data/app/views/kaminari/_prev_page.html.erb +3 -0
  40. data/app/views/layouts/godmin/_content.html.erb +13 -0
  41. data/app/views/layouts/godmin/_layout.html.erb +13 -0
  42. data/app/views/layouts/godmin/application.html.erb +17 -0
  43. data/app/views/layouts/godmin/login.html.erb +18 -0
  44. data/bin/rails +8 -0
  45. data/config/locales/en.yml +41 -0
  46. data/config/locales/sv.yml +41 -0
  47. data/config/routes.rb +2 -0
  48. data/godmin.gemspec +32 -0
  49. data/lib/generators/godmin/authentication/authentication_generator.rb +61 -0
  50. data/lib/generators/godmin/install/install_generator.rb +34 -0
  51. data/lib/generators/godmin/policy/policy_generator.rb +29 -0
  52. data/lib/generators/godmin/resource/resource_generator.rb +56 -0
  53. data/lib/godmin.rb +24 -0
  54. data/lib/godmin/application.rb +37 -0
  55. data/lib/godmin/authentication.rb +35 -0
  56. data/lib/godmin/authentication/sessions.rb +45 -0
  57. data/lib/godmin/authentication/user.rb +27 -0
  58. data/lib/godmin/authorization.rb +30 -0
  59. data/lib/godmin/authorization/policy.rb +40 -0
  60. data/lib/godmin/authorization/policy_finder.rb +28 -0
  61. data/lib/godmin/engine.rb +4 -0
  62. data/lib/godmin/generators/base.rb +13 -0
  63. data/lib/godmin/helpers/application.rb +6 -0
  64. data/lib/godmin/helpers/batch_actions.rb +17 -0
  65. data/lib/godmin/helpers/filters.rb +108 -0
  66. data/lib/godmin/helpers/tables.rb +41 -0
  67. data/lib/godmin/helpers/translations.rb +19 -0
  68. data/lib/godmin/rails.rb +36 -0
  69. data/lib/godmin/resolver.rb +46 -0
  70. data/lib/godmin/resource.rb +126 -0
  71. data/lib/godmin/resource/batch_actions.rb +45 -0
  72. data/lib/godmin/resource/filters.rb +41 -0
  73. data/lib/godmin/resource/ordering.rb +25 -0
  74. data/lib/godmin/resource/pagination.rb +11 -0
  75. data/lib/godmin/resource/scopes.rb +49 -0
  76. data/lib/godmin/version.rb +3 -0
  77. data/lib/tasks/godmin_tasks.rake +4 -0
  78. data/test/dummy/README.rdoc +28 -0
  79. data/test/dummy/Rakefile +6 -0
  80. data/test/dummy/app/assets/images/.keep +0 -0
  81. data/test/dummy/app/assets/javascripts/application.js +13 -0
  82. data/test/dummy/app/assets/stylesheets/application.css +13 -0
  83. data/test/dummy/app/controllers/application_controller.rb +5 -0
  84. data/test/dummy/app/controllers/concerns/.keep +0 -0
  85. data/test/dummy/app/helpers/application_helper.rb +2 -0
  86. data/test/dummy/app/mailers/.keep +0 -0
  87. data/test/dummy/app/models/.keep +0 -0
  88. data/test/dummy/app/models/concerns/.keep +0 -0
  89. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  90. data/test/dummy/bin/bundle +3 -0
  91. data/test/dummy/bin/rails +4 -0
  92. data/test/dummy/bin/rake +4 -0
  93. data/test/dummy/config.ru +4 -0
  94. data/test/dummy/config/application.rb +23 -0
  95. data/test/dummy/config/boot.rb +5 -0
  96. data/test/dummy/config/database.yml +25 -0
  97. data/test/dummy/config/environment.rb +5 -0
  98. data/test/dummy/config/environments/development.rb +29 -0
  99. data/test/dummy/config/environments/production.rb +80 -0
  100. data/test/dummy/config/environments/test.rb +36 -0
  101. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  102. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  103. data/test/dummy/config/initializers/inflections.rb +16 -0
  104. data/test/dummy/config/initializers/mime_types.rb +5 -0
  105. data/test/dummy/config/initializers/secret_token.rb +12 -0
  106. data/test/dummy/config/initializers/session_store.rb +3 -0
  107. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  108. data/test/dummy/config/locales/en.yml +23 -0
  109. data/test/dummy/config/routes.rb +4 -0
  110. data/test/dummy/db/test.sqlite3 +0 -0
  111. data/test/dummy/lib/assets/.keep +0 -0
  112. data/test/dummy/public/404.html +58 -0
  113. data/test/dummy/public/422.html +58 -0
  114. data/test/dummy/public/500.html +57 -0
  115. data/test/dummy/public/favicon.ico +0 -0
  116. data/test/godmin_test.rb +7 -0
  117. data/test/integration/navigation_test.rb +10 -0
  118. data/test/lib/godmin/policy_finder_test.rb +30 -0
  119. data/test/lib/godmin/resolver_test.rb +31 -0
  120. data/test/test_helper.rb +21 -0
  121. data/vendor/assets/images/godmin/chosen-sprite.png +0 -0
  122. data/vendor/assets/images/godmin/chosen-sprite@2x.png +0 -0
  123. data/vendor/assets/javascripts/.keep +0 -0
  124. data/vendor/assets/stylesheets/.keep +0 -0
  125. metadata +361 -0
data/lib/godmin.rb ADDED
@@ -0,0 +1,24 @@
1
+ require "bootstrap-sass"
2
+ require "kaminari"
3
+ require "select2-rails"
4
+ require "simple_form"
5
+ require "godmin/application"
6
+ require "godmin/authentication"
7
+ require "godmin/authorization"
8
+ require "godmin/engine"
9
+ require "godmin/rails"
10
+ require "godmin/resolver"
11
+ require "godmin/resource"
12
+ require "godmin/version"
13
+
14
+ module Godmin
15
+ mattr_accessor :namespace
16
+ self.namespace = nil
17
+
18
+ mattr_accessor :resources
19
+ self.resources = []
20
+
21
+ def self.configure
22
+ yield self
23
+ end
24
+ end
@@ -0,0 +1,37 @@
1
+ require "godmin/helpers/application"
2
+ require "godmin/helpers/translations"
3
+
4
+ module Godmin
5
+ module Application
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ helper Godmin::Helpers::Application
10
+ helper Godmin::Helpers::Translations
11
+
12
+ helper_method :authentication_enabled?
13
+ helper_method :authorization_enabled?
14
+
15
+ before_action :append_view_paths
16
+
17
+ layout "godmin/application"
18
+ end
19
+
20
+ def welcome; end
21
+
22
+ private
23
+
24
+ def append_view_paths
25
+ append_view_path Godmin::EngineResolver.new(controller_name)
26
+ append_view_path Godmin::GodminResolver.new(controller_name)
27
+ end
28
+
29
+ def authentication_enabled?
30
+ singleton_class.include?(Godmin::Authentication)
31
+ end
32
+
33
+ def authorization_enabled?
34
+ singleton_class.include?(Godmin::Authorization)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,35 @@
1
+ require "godmin/authentication/sessions"
2
+ require "godmin/authentication/user"
3
+
4
+ module Godmin
5
+ module Authentication
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ before_action :authenticate_admin_user
10
+
11
+ helper_method :admin_user
12
+ helper_method :admin_user_signed_in?
13
+ end
14
+
15
+ def authenticate_admin_user
16
+ unless admin_user_signed_in? || controller_name == "sessions"
17
+ redirect_to new_session_path, alert: "Authentication needed"
18
+ end
19
+ end
20
+
21
+ def admin_user_class
22
+ raise NotImplementedError, "Must define the admin user class"
23
+ end
24
+
25
+ def admin_user
26
+ if session[:admin_user_id]
27
+ @admin_user ||= admin_user_class.find_by(id: session[:admin_user_id])
28
+ end
29
+ end
30
+
31
+ def admin_user_signed_in?
32
+ admin_user.present?
33
+ end
34
+ end
35
+ end
@@ -0,0 +1,45 @@
1
+ module Godmin
2
+ module Authentication
3
+ module Sessions
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ layout "godmin/login"
8
+ end
9
+
10
+ def new
11
+ @admin_user = admin_user_class.new
12
+ end
13
+
14
+ def create
15
+ @admin_user = admin_user_class.find_by_login(admin_user_login)
16
+
17
+ if @admin_user && @admin_user.authenticate(admin_user_params[:password])
18
+ session[:admin_user_id] = @admin_user.id
19
+ redirect_to root_path, notice: t("godmin.sessions.signed_in")
20
+ else
21
+ redirect_to new_session_path, alert: t("godmin.sessions.failed_sign_in")
22
+ end
23
+ end
24
+
25
+ def destroy
26
+ session[:admin_user_id] = nil
27
+ redirect_to new_session_path, notice: t("godmin.sessions.signed_out")
28
+ end
29
+
30
+ private
31
+
32
+ def admin_user_login
33
+ admin_user_params[admin_user_class.login_column]
34
+ end
35
+
36
+ def admin_user_params
37
+ params.require(:admin_user).permit(
38
+ admin_user_class.login_column,
39
+ :password,
40
+ :password_confirm
41
+ )
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,27 @@
1
+ module Godmin
2
+ module Authentication
3
+ module User
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ has_secure_password
8
+
9
+ validates :password, length: { minimum: 8 }, allow_nil: true
10
+ end
11
+
12
+ def login
13
+ send(self.class.login_column)
14
+ end
15
+
16
+ module ClassMethods
17
+ def find_by_login(login)
18
+ find_by(login_column => login)
19
+ end
20
+
21
+ def login_column
22
+ raise NotImplementedError, "Must define the admin user login column"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,30 @@
1
+ require "godmin/authorization/policy"
2
+ require "godmin/authorization/policy_finder"
3
+
4
+ module Godmin
5
+ module Authorization
6
+ extend ActiveSupport::Concern
7
+
8
+ included do
9
+ helper_method :policy
10
+ end
11
+
12
+ def authorize(record)
13
+ policy = policy(record)
14
+
15
+ unless policy.public_send(action_name + "?")
16
+ raise NotAuthorizedError
17
+ end
18
+ end
19
+
20
+ def policy(record)
21
+ policies[record] ||= PolicyFinder.find(record).constantize.new(admin_user, record)
22
+ end
23
+
24
+ def policies
25
+ @_policies ||= {}
26
+ end
27
+
28
+ class NotAuthorizedError < StandardError; end
29
+ end
30
+ end
@@ -0,0 +1,40 @@
1
+ module Godmin
2
+ module Authorization
3
+ class Policy
4
+ attr_reader :user, :record
5
+
6
+ def initialize(user, record)
7
+ @user = user
8
+ @record = record
9
+ end
10
+
11
+ def index?
12
+ false
13
+ end
14
+
15
+ def show?
16
+ false
17
+ end
18
+
19
+ def new?
20
+ create?
21
+ end
22
+
23
+ def edit?
24
+ update?
25
+ end
26
+
27
+ def create?
28
+ false
29
+ end
30
+
31
+ def update?
32
+ false
33
+ end
34
+
35
+ def destroy?
36
+ false
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,28 @@
1
+ module Godmin
2
+ module Authorization
3
+ class PolicyFinder
4
+ class << self
5
+ def find(object)
6
+ klass =
7
+ if object.respond_to?(:model_name)
8
+ object.model_name
9
+ elsif object.class.respond_to?(:model_name)
10
+ object.class.model_name
11
+ elsif object.is_a?(Class)
12
+ object
13
+ elsif object.is_a?(Symbol)
14
+ object.to_s.classify
15
+ else
16
+ object.class
17
+ end
18
+
19
+ if Godmin.namespace
20
+ "#{Godmin.namespace.classify}::#{klass}Policy"
21
+ else
22
+ "#{klass}Policy"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,4 @@
1
+ module Godmin
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,13 @@
1
+ require "active_support/all"
2
+
3
+ module Godmin
4
+ module Generators
5
+ class Base < Rails::Generators::Base
6
+ private
7
+
8
+ def namespace
9
+ Rails::Generators.namespace.to_s.underscore.presence
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,6 @@
1
+ module Godmin
2
+ module Helpers
3
+ module Application
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,17 @@
1
+ module Godmin
2
+ module Helpers
3
+ module BatchActions
4
+ def batch_action_link(name, options)
5
+ if (options[:only].nil? && options[:except].nil?) ||
6
+ (options[:only] && options[:only].include?(params[:scope].to_sym)) ||
7
+ (options[:except] && !options[:except].include?(params[:scope].to_sym))
8
+
9
+ link_to(translate_scoped("batch_actions.#{name}", default: name.to_s.titleize), "#", class: "btn btn-default batch-actions-action-link hidden", data: {
10
+ value: name,
11
+ confirm: options[:confirm] ? translate_scoped("batch_actions.confirm_message") : false
12
+ })
13
+ end
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,108 @@
1
+ module Godmin
2
+ module Helpers
3
+ module Filters
4
+ def filter_input_tag(name, options)
5
+ case options[:as]
6
+ when :string
7
+ filter_string_tag(name, options)
8
+ when :select
9
+ filter_select_tag(name, options)
10
+ when :multiselect
11
+ filter_multiselect_tag(name, options)
12
+ when :checkboxes
13
+ filter_checkbox_tags(name, options)
14
+ end
15
+ end
16
+
17
+ private
18
+
19
+ def filter_string_tag(name, _options)
20
+ text_field_tag(
21
+ name,
22
+ default_filter_value(name),
23
+ name: "filter[#{name}]",
24
+ class: "form-control",
25
+ placeholder: translate_scoped("filters.labels.#{name}", default: name.to_s.titleize)
26
+ )
27
+ end
28
+
29
+ def filter_select_tag(name, options)
30
+ filter_select_tag_helper(
31
+ name,
32
+ options,
33
+ name: "filter[#{name}]",
34
+ include_blank: true,
35
+ class: "form-control select-tag",
36
+ data: {
37
+ placeholder: translate_scoped("filters.select.placeholder.one")
38
+ }
39
+ )
40
+ end
41
+
42
+ def filter_multiselect_tag(name, options)
43
+ filter_select_tag_helper(
44
+ name,
45
+ options,
46
+ name: "filter[#{name}][]",
47
+ multiple: true,
48
+ class: "form-control select-tag",
49
+ data: {
50
+ placeholder: translate_scoped("filters.select.placeholder.many")
51
+ }
52
+ )
53
+ end
54
+
55
+ def filter_select_tag_helper(name, options, html_options)
56
+ unless options[:collection].is_a? Proc
57
+ raise "A collection proc must be specified for select filters"
58
+ end
59
+
60
+ collection = options[:collection].call
61
+
62
+ if collection.is_a? ActiveRecord::Relation
63
+ choices = options_from_collection_for_select(
64
+ collection,
65
+ options[:option_value],
66
+ options[:option_text],
67
+ selected: default_filter_value(name)
68
+ )
69
+ else
70
+ choices = options_for_select(
71
+ collection,
72
+ selected: default_filter_value(name)
73
+ )
74
+ end
75
+
76
+ select_tag(name, choices, html_options)
77
+ end
78
+
79
+ def filter_checkbox_tags(name, options)
80
+ unless options[:collection].is_a? Proc
81
+ raise "A collection proc must be specified for checkbox filters"
82
+ end
83
+
84
+ collection = options[:collection].call
85
+
86
+ collection.map do |item|
87
+ text, value = if !item.is_a?(String) && item.respond_to?(:first) && item.respond_to?(:last)
88
+ [item.first.to_s, item.last.to_s]
89
+ else
90
+ [item.to_s, item.to_s]
91
+ end
92
+
93
+ is_checked = default_filter_value(name) ? default_filter_value(name).include?(value) : false
94
+
95
+ content_tag :div, class: "checkbox" do
96
+ label_tag("#{name}_#{value}") do
97
+ check_box_tag("filter[#{name}][]", value, is_checked, id: "#{name}_#{value}") << text
98
+ end
99
+ end
100
+ end.join("\n").html_safe
101
+ end
102
+
103
+ def default_filter_value(name)
104
+ params[:filter] ? params[:filter][name] : nil
105
+ end
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,41 @@
1
+ module Godmin
2
+ module Helpers
3
+ module Tables
4
+ def column_header(attribute)
5
+ if @resource_class.column_names.include?(attribute.to_s)
6
+ direction =
7
+ if params[:order]
8
+ if params[:order].split("_").first == attribute.to_s
9
+ params[:order].split("_").last == "desc" ? "asc" : "desc"
10
+ else
11
+ params[:order].split("_").last
12
+ end
13
+ else
14
+ "desc"
15
+ end
16
+ link_to @resource_class.human_attribute_name(attribute.to_s), url_for(params.merge(order: "#{attribute}_#{direction}"))
17
+ else
18
+ @resource_class.human_attribute_name(attribute.to_s)
19
+ end
20
+ end
21
+
22
+ def column_value(resource, attribute)
23
+ if lookup_context.exists?("columns/#{attribute}", nil, true)
24
+ render partial: "columns/#{attribute}", locals: { resource: resource }
25
+ else
26
+ column_value = resource.send(attribute)
27
+
28
+ if column_value.is_a?(Date) || column_value.is_a?(Time)
29
+ column_value = l(column_value, format: :long)
30
+ end
31
+
32
+ if column_value.is_a?(TrueClass) || column_value.is_a?(FalseClass)
33
+ column_value = t(column_value.to_s)
34
+ end
35
+
36
+ column_value
37
+ end
38
+ end
39
+ end
40
+ end
41
+ end