godmin 0.9.0

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