├── test ├── dummy │ ├── log │ │ └── .keep │ ├── app │ │ ├── models │ │ │ ├── .keep │ │ │ ├── comment.rb │ │ │ ├── admin_user.rb │ │ │ ├── another_admin_user.rb │ │ │ └── article.rb │ │ ├── views │ │ │ ├── articles │ │ │ │ ├── .keep │ │ │ │ ├── columns │ │ │ │ │ └── .keep │ │ │ │ └── filters │ │ │ │ │ └── .keep │ │ │ ├── resource │ │ │ │ ├── .keep │ │ │ │ ├── columns │ │ │ │ │ └── .keep │ │ │ │ └── filters │ │ │ │ │ └── .keep │ │ │ └── shared │ │ │ │ └── _navigation.html.erb │ │ ├── controllers │ │ │ ├── comments_controller.rb │ │ │ ├── sessions_controller.rb │ │ │ ├── another_admin_sessions_controller.rb │ │ │ ├── authorized_articles_controller.rb │ │ │ ├── authenticated_articles_controller.rb │ │ │ ├── application_controller.rb │ │ │ └── articles_controller.rb │ │ ├── services │ │ │ ├── comment_service.rb │ │ │ └── article_service.rb │ │ ├── policies │ │ │ └── article_policy.rb │ │ └── assets │ │ │ ├── stylesheets │ │ │ └── application.css │ │ │ └── javascripts │ │ │ └── application.js │ ├── lib │ │ └── assets │ │ │ └── .keep │ ├── public │ │ ├── favicon.ico │ │ ├── 500.html │ │ ├── 422.html │ │ └── 404.html │ ├── admin │ │ ├── app │ │ │ ├── views │ │ │ │ └── admin │ │ │ │ │ ├── articles │ │ │ │ │ ├── .keep │ │ │ │ │ ├── columns │ │ │ │ │ │ └── .keep │ │ │ │ │ └── filters │ │ │ │ │ │ └── .keep │ │ │ │ │ ├── resource │ │ │ │ │ ├── .keep │ │ │ │ │ ├── columns │ │ │ │ │ │ └── .keep │ │ │ │ │ └── filters │ │ │ │ │ │ └── .keep │ │ │ │ │ └── shared │ │ │ │ │ └── _navigation.html.erb │ │ │ ├── models │ │ │ │ └── admin │ │ │ │ │ └── article.rb │ │ │ ├── policies │ │ │ │ └── admin │ │ │ │ │ └── article_policy.rb │ │ │ ├── controllers │ │ │ │ └── admin │ │ │ │ │ ├── application_controller.rb │ │ │ │ │ ├── articles_controller.rb │ │ │ │ │ └── authorized_articles_controller.rb │ │ │ ├── services │ │ │ │ └── admin │ │ │ │ │ └── article_service.rb │ │ │ └── assets │ │ │ │ ├── javascripts │ │ │ │ └── admin │ │ │ │ │ └── application.js │ │ │ │ └── stylesheets │ │ │ │ └── admin │ │ │ │ └── application.css │ │ ├── lib │ │ │ ├── admin.rb │ │ │ └── admin │ │ │ │ ├── version.rb │ │ │ │ └── engine.rb │ │ ├── config │ │ │ └── routes.rb │ │ ├── admin.gemspec │ │ └── bin │ │ │ └── rails │ ├── bin │ │ ├── rake │ │ ├── bundle │ │ └── rails │ ├── config.ru │ ├── config │ │ ├── initializers │ │ │ ├── session_store.rb │ │ │ ├── filter_parameter_logging.rb │ │ │ ├── mime_types.rb │ │ │ ├── backtrace_silencers.rb │ │ │ ├── wrap_parameters.rb │ │ │ ├── secret_token.rb │ │ │ └── inflections.rb │ │ ├── environment.rb │ │ ├── locales │ │ │ └── en.yml │ │ ├── application.rb │ │ ├── boot.rb │ │ ├── routes.rb │ │ ├── database.yml │ │ └── environments │ │ │ ├── development.rb │ │ │ └── test.rb │ ├── Rakefile │ └── db │ │ ├── migrate │ │ ├── 20150907133753_create_admin_users.rb │ │ ├── 20160713134238_create_comment.rb │ │ ├── 20170207081043_create_another_admin_user.rb │ │ └── 20150717121532_create_articles.rb │ │ └── schema.rb ├── fakes │ ├── article.rb │ └── article_service.rb ├── integration │ ├── welcome_test.rb │ ├── scopes_test.rb │ ├── filters_test.rb │ ├── authentication_test.rb │ ├── authorization_test.rb │ ├── column_ordering_test.rb │ ├── nested_resources_test.rb │ ├── view_overriding │ │ ├── partial_overriding_test.rb │ │ ├── template_overriding_test.rb │ │ ├── filter_overriding_test.rb │ │ └── column_overriding_test.rb │ ├── crud_test.rb │ └── batch_actions_test.rb ├── unit │ ├── resources │ │ ├── resource_service_test.rb │ │ └── resource_service │ │ │ ├── pagination_test.rb │ │ │ ├── ordering_test.rb │ │ │ ├── batch_actions_test.rb │ │ │ ├── filters_test.rb │ │ │ └── scopes_test.rb │ ├── resolver_test.rb │ ├── helpers │ │ └── filters_test.rb │ ├── engine_wrapper_test.rb │ └── paginator_test.rb ├── test_helper.rb └── generators │ └── resource_generator_test.rb ├── app ├── assets │ ├── images │ │ └── godmin │ │ │ └── .keep │ ├── javascripts │ │ └── godmin │ │ │ ├── index.js │ │ │ ├── select-boxes.js │ │ │ ├── navigation.js │ │ │ ├── datetimepickers.js │ │ │ └── batch-actions.js │ └── stylesheets │ │ └── godmin │ │ └── index.css.scss └── views │ ├── godmin │ ├── shared │ │ ├── _navigation.html.erb │ │ └── _navigation_aside.html.erb │ ├── resource │ │ ├── show.json.jbuilder │ │ ├── index.json.jbuilder │ │ ├── edit.html.erb │ │ ├── new.html.erb │ │ ├── index.csv.csvbuilder │ │ ├── _errors.html.erb │ │ ├── _actions.html.erb │ │ ├── _button_actions.html.erb │ │ ├── index.html.erb │ │ ├── show.html.erb │ │ ├── _form.html.erb │ │ ├── _batch_actions.html.erb │ │ ├── _scopes.html.erb │ │ ├── _export_actions.html.erb │ │ ├── _filters.html.erb │ │ ├── columns │ │ │ └── _actions.html.erb │ │ ├── _breadcrumb.html.erb │ │ ├── _table.html.erb │ │ ├── _breadcrumb_actions.html.erb │ │ └── _pagination.html.erb │ ├── sessions │ │ └── new.html.erb │ └── application │ │ └── welcome.html.erb │ └── layouts │ └── godmin │ ├── _content.html.erb │ ├── _layout.html.erb │ ├── login.html.erb │ └── application.html.erb ├── config ├── routes.rb └── locales │ ├── sv.yml │ ├── en.yml │ └── pt-BR.yml ├── Appraisals ├── lib ├── godmin │ ├── version.rb │ ├── engine.rb │ ├── authorization.rb │ ├── resources │ │ ├── resource_service │ │ │ ├── pagination.rb │ │ │ ├── associations.rb │ │ │ ├── filters.rb │ │ │ ├── ordering.rb │ │ │ ├── batch_actions.rb │ │ │ └── scopes.rb │ │ ├── resource_controller │ │ │ └── batch_actions.rb │ │ ├── resource_service.rb │ │ └── resource_controller.rb │ ├── helpers │ │ ├── translations.rb │ │ ├── batch_actions.rb │ │ ├── application.rb │ │ ├── tables.rb │ │ ├── navigation.rb │ │ ├── forms.rb │ │ └── filters.rb │ ├── authentication │ │ ├── user.rb │ │ └── sessions_controller.rb │ ├── authorization │ │ └── policy.rb │ ├── generators │ │ ├── named_base.rb │ │ └── base.rb │ ├── authentication.rb │ ├── engine_wrapper.rb │ ├── paginator.rb │ ├── application_controller.rb │ └── resolver.rb ├── generators │ └── godmin │ │ ├── resource │ │ ├── templates │ │ │ ├── resource_model.rb │ │ │ ├── resource_controller.rb │ │ │ └── resource_service.rb │ │ └── resource_generator.rb │ │ ├── policy │ │ ├── policy_generator.rb │ │ └── templates │ │ │ └── policy.rb │ │ ├── authentication │ │ ├── templates │ │ │ └── sessions_controller.rb │ │ └── authentication_generator.rb │ │ └── install │ │ └── install_generator.rb └── godmin.rb ├── screenshot.png ├── gemfiles └── rails_5.gemfile ├── .travis.yml ├── .rubocop.yml ├── .codeclimate.yml ├── .gitignore ├── Gemfile ├── MIT-LICENSE ├── Rakefile ├── godmin.gemspec ├── CODE_OF_CONDUCT.md ├── vendor └── assets │ └── stylesheets │ └── bootstrap-datetimepicker.css ├── CHANGELOG.md └── template.rb /test/dummy/log/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/models/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/lib/assets/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/godmin/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/public/favicon.ico: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/views/articles/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/views/resource/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/views/articles/columns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/views/articles/filters/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/views/resource/columns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/app/views/resource/filters/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/views/godmin/shared/_navigation.html.erb: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/admin/app/views/admin/articles/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/admin/app/views/admin/resource/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Godmin::Engine.routes.draw do 2 | end 3 | -------------------------------------------------------------------------------- /test/dummy/admin/app/views/admin/articles/columns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/admin/app/views/admin/articles/filters/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/admin/app/views/admin/resource/columns/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /test/dummy/admin/app/views/admin/resource/filters/.keep: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /Appraisals: -------------------------------------------------------------------------------- 1 | appraise "rails-5" do 2 | gem "rails", "~> 5.0" 3 | end 4 | -------------------------------------------------------------------------------- /lib/godmin/version.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | VERSION = "2.0.0" 3 | end 4 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiajian/godmin/master/screenshot.png -------------------------------------------------------------------------------- /test/dummy/app/views/shared/_navigation.html.erb: -------------------------------------------------------------------------------- 1 | <%= navbar_item Article %> 2 | -------------------------------------------------------------------------------- /test/dummy/admin/lib/admin.rb: -------------------------------------------------------------------------------- 1 | require "admin/engine" 2 | 3 | module Admin 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/admin/lib/admin/version.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | VERSION = "0.0.1" 3 | end 4 | -------------------------------------------------------------------------------- /lib/godmin/engine.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | class Engine < ::Rails::Engine 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/admin/app/views/admin/shared/_navigation.html.erb: -------------------------------------------------------------------------------- 1 | <%= navbar_item Article %> 2 | -------------------------------------------------------------------------------- /app/views/godmin/resource/show.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.extract! @resource, *@resource_service.attrs_for_export 2 | -------------------------------------------------------------------------------- /test/dummy/admin/app/models/admin/article.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class Article < ::Article 3 | end 4 | end 5 | -------------------------------------------------------------------------------- /test/dummy/bin/rake: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | require_relative '../config/boot' 3 | require 'rake' 4 | Rake.application.run 5 | -------------------------------------------------------------------------------- /test/dummy/admin/lib/admin/engine.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class Engine < ::Rails::Engine 3 | isolate_namespace Admin 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/app/models/comment.rb: -------------------------------------------------------------------------------- 1 | class Comment < ActiveRecord::Base 2 | belongs_to :article 3 | 4 | def to_s 5 | title 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/comments_controller.rb: -------------------------------------------------------------------------------- 1 | class CommentsController < ApplicationController 2 | include Godmin::Resources::ResourceController 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/bin/bundle: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 3 | load Gem.bin_path('bundler', 'bundle') 4 | -------------------------------------------------------------------------------- /lib/generators/godmin/resource/templates/resource_model.rb: -------------------------------------------------------------------------------- 1 | <% module_namespacing do -%> 2 | class <%= class_name %> < ::<%= class_name %> 3 | end 4 | <% end -%> 5 | -------------------------------------------------------------------------------- /test/dummy/admin/config/routes.rb: -------------------------------------------------------------------------------- 1 | Admin::Engine.routes.draw do 2 | resources :articles 3 | resources :authorized_articles 4 | root to: "application#welcome" 5 | end 6 | -------------------------------------------------------------------------------- /test/dummy/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | APP_PATH = File.expand_path('../config/application', __dir__) 3 | require_relative '../config/boot' 4 | require 'rails/commands' 5 | -------------------------------------------------------------------------------- /app/views/godmin/resource/index.json.jbuilder: -------------------------------------------------------------------------------- 1 | json.array! @resources.limit(nil).offset(nil) do |resource| 2 | json.extract! resource, *@resource_service.attrs_for_export 3 | end 4 | -------------------------------------------------------------------------------- /test/dummy/app/models/admin_user.rb: -------------------------------------------------------------------------------- 1 | class AdminUser < ActiveRecord::Base 2 | include Godmin::Authentication::User 3 | 4 | def self.login_column 5 | :email 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/admin/app/policies/admin/article_policy.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class ArticlePolicy < Godmin::Authorization::Policy 3 | def index? 4 | false 5 | end 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/config.ru: -------------------------------------------------------------------------------- 1 | # This file is used by Rack-based servers to start the application. 2 | 3 | require ::File.expand_path('../config/environment', __FILE__) 4 | run Rails.application 5 | -------------------------------------------------------------------------------- /app/views/godmin/resource/edit.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "breadcrumb" %> 2 | 3 | <%= render partial: "errors", locals: { errors: @resource.errors } %> 4 | 5 | <%= render partial: "form" %> 6 | -------------------------------------------------------------------------------- /app/views/godmin/resource/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "breadcrumb" %> 2 | 3 | <%= render partial: "errors", locals: { errors: @resource.errors } %> 4 | 5 | <%= render partial: "form" %> 6 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/session_store.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | Dummy::Application.config.session_store :cookie_store, key: '_dummy_session' 4 | -------------------------------------------------------------------------------- /test/dummy/app/models/another_admin_user.rb: -------------------------------------------------------------------------------- 1 | class AnotherAdminUser < ActiveRecord::Base 2 | include Godmin::Authentication::User 3 | 4 | def self.login_column 5 | :email 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/config/environment.rb: -------------------------------------------------------------------------------- 1 | # Load the Rails application. 2 | require File.expand_path('../application', __FILE__) 3 | 4 | # Initialize the Rails application. 5 | Dummy::Application.initialize! 6 | -------------------------------------------------------------------------------- /test/dummy/admin/app/controllers/admin/application_controller.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class ApplicationController < ActionController::Base 3 | include Godmin::ApplicationController 4 | end 5 | end 6 | -------------------------------------------------------------------------------- /test/fakes/article.rb: -------------------------------------------------------------------------------- 1 | module Fakes 2 | class Article 3 | def self.table_name 4 | "articles" 5 | end 6 | 7 | def self.column_names 8 | %w[id title] 9 | end 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/integration/welcome_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class WelcomeTest < ActionDispatch::IntegrationTest 4 | def test_welcome 5 | visit "/" 6 | assert page.has_content? "Welcome" 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/dummy/config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | activerecord: 3 | models: 4 | article: 5 | one: Article 6 | other: Articles 7 | comment: 8 | one: Comment 9 | other: Comments 10 | -------------------------------------------------------------------------------- /test/dummy/app/services/comment_service.rb: -------------------------------------------------------------------------------- 1 | class CommentService 2 | include Godmin::Resources::ResourceService 3 | 4 | attrs_for_index :id, :title 5 | attrs_for_show :id, :title, :body 6 | attrs_for_form :title, :body 7 | end 8 | -------------------------------------------------------------------------------- /app/views/godmin/resource/index.csv.csvbuilder: -------------------------------------------------------------------------------- 1 | csv << @resource_service.attrs_for_export 2 | 3 | @resources.limit(nil).offset(nil).each do |resource| 4 | csv << @resource_service.attrs_for_export.map { |attr| resource.send(attr) } 5 | end 6 | -------------------------------------------------------------------------------- /gemfiles/rails_5.gemfile: -------------------------------------------------------------------------------- 1 | # This file was generated by Appraisal 2 | 3 | source "https://rubygems.org" 4 | 5 | gem "admin", :path => "../test/dummy/admin", :group => [:test, :development] 6 | gem "rails", "~> 5.0" 7 | 8 | gemspec :path => "../" 9 | -------------------------------------------------------------------------------- /test/dummy/app/models/article.rb: -------------------------------------------------------------------------------- 1 | class Article < ActiveRecord::Base 2 | belongs_to :admin_user 3 | has_many :comments 4 | 5 | def non_orderable_column 6 | "Not orderable" 7 | end 8 | 9 | def to_s 10 | title 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /test/dummy/app/policies/article_policy.rb: -------------------------------------------------------------------------------- 1 | class ArticlePolicy < Godmin::Authorization::Policy 2 | def index? 3 | true 4 | end 5 | 6 | def show? 7 | user == "admin" 8 | end 9 | 10 | def destroy? 11 | false 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /test/dummy/admin/app/controllers/admin/articles_controller.rb: -------------------------------------------------------------------------------- 1 | require_dependency "admin/application_controller" 2 | 3 | module Admin 4 | class ArticlesController < ApplicationController 5 | include Godmin::Resources::ResourceController 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class SessionsController < ApplicationController 2 | include Godmin::Authentication::SessionsController 3 | include Godmin::Authentication 4 | 5 | def admin_user_class 6 | AdminUser 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/filter_parameter_logging.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Configure sensitive parameters which will be filtered from the log file. 4 | Rails.application.config.filter_parameters += [:password] 5 | -------------------------------------------------------------------------------- /test/dummy/config/application.rb: -------------------------------------------------------------------------------- 1 | require File.expand_path("../boot", __FILE__) 2 | 3 | require "rails/all" 4 | 5 | Bundler.require(*Rails.groups) 6 | require "godmin" 7 | 8 | module Dummy 9 | class Application < Rails::Application 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/mime_types.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new mime types for use in respond_to blocks: 4 | # Mime::Type.register "text/richtext", :rtf 5 | # Mime::Type.register_alias "text/html", :iphone 6 | -------------------------------------------------------------------------------- /test/dummy/config/boot.rb: -------------------------------------------------------------------------------- 1 | # Set up gems listed in the Gemfile. 2 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../../../Gemfile', __FILE__) 3 | 4 | require 'bundler/setup' if File.exists?(ENV['BUNDLE_GEMFILE']) 5 | $LOAD_PATH.unshift File.expand_path('../../../../lib', __FILE__) 6 | -------------------------------------------------------------------------------- /test/dummy/Rakefile: -------------------------------------------------------------------------------- 1 | # Add your own tasks in files placed in lib/tasks ending in .rake, 2 | # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake. 3 | 4 | require File.expand_path('../config/application', __FILE__) 5 | 6 | Dummy::Application.load_tasks 7 | -------------------------------------------------------------------------------- /lib/generators/godmin/policy/policy_generator.rb: -------------------------------------------------------------------------------- 1 | require "godmin/generators/named_base" 2 | 3 | class Godmin::PolicyGenerator < Godmin::Generators::NamedBase 4 | def create_policy 5 | template "policy.rb", File.join("app/policies", class_path, "#{file_name}_policy.rb") 6 | end 7 | end 8 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/another_admin_sessions_controller.rb: -------------------------------------------------------------------------------- 1 | class AnotherAdminSessionsController < ApplicationController 2 | include Godmin::Authentication::SessionsController 3 | include Godmin::Authentication 4 | 5 | def admin_user_class 6 | AnotherAdminUser 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /app/views/godmin/resource/_errors.html.erb: -------------------------------------------------------------------------------- 1 | <% if errors.present? %> 2 |
3 | 8 |
9 | <% end %> 10 | -------------------------------------------------------------------------------- /app/views/godmin/sessions/new.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for(@admin_user, url: session_path) do |f| %> 2 | <%= f.text_field @admin_user.class.login_column %> 3 | <%= f.password_field :password %> 4 | <%= f.submit translate_scoped("sessions.sign_in"), class: "btn btn-block btn-primary" %> 5 | <% end %> 6 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/authorized_articles_controller.rb: -------------------------------------------------------------------------------- 1 | class AuthorizedArticlesController < ArticlesController 2 | include Godmin::Authorization 3 | 4 | def admin_user 5 | "admin" 6 | end 7 | 8 | def resource_service_class 9 | ArticleService 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /app/views/godmin/resource/_actions.html.erb: -------------------------------------------------------------------------------- 1 |
2 |
3 | <%= render partial: "batch_actions" %> 4 |
5 |
6 | <%= render partial: "button_actions" %> 7 | <%= render partial: "export_actions" %> 8 |
9 |
10 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/authenticated_articles_controller.rb: -------------------------------------------------------------------------------- 1 | class AuthenticatedArticlesController < ArticlesController 2 | include Godmin::Authentication 3 | 4 | def admin_user_class 5 | AdminUser 6 | end 7 | 8 | def resource_service_class 9 | ArticleService 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20150907133753_create_admin_users.rb: -------------------------------------------------------------------------------- 1 | class CreateAdminUsers < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :admin_users do |t| 4 | t.string :email 5 | t.text :password_digest 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20160713134238_create_comment.rb: -------------------------------------------------------------------------------- 1 | class CreateComment < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :comments do |t| 4 | t.references :article, index: true, foreign_key: true 5 | t.string :title 6 | t.text :body 7 | end 8 | end 9 | end 10 | -------------------------------------------------------------------------------- /app/views/godmin/resource/_button_actions.html.erb: -------------------------------------------------------------------------------- 1 | <% if policy(@resource_service.build_resource({})).new? %> 2 | <%= link_to t("helpers.submit.create", model: @resource_class.model_name.human), [:new, :admin, *@resource_parents, @resource_class.model_name.singular_route_key], class: "btn btn-default" %> 3 | <% end %> 4 | -------------------------------------------------------------------------------- /app/views/layouts/godmin/_content.html.erb: -------------------------------------------------------------------------------- 1 | <% if flash[:alert] %> 2 |
3 | <%= flash[:alert] %> 4 |
5 | <% end %> 6 | 7 | <% if flash[:notice] %> 8 |
9 | <%= flash[:notice] %> 10 |
11 | <% end %> 12 | 13 | <%= yield %> 14 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/application_controller.rb: -------------------------------------------------------------------------------- 1 | class ApplicationController < ActionController::Base 2 | include Godmin::ApplicationController 3 | 4 | # Prevent CSRF attacks by raising an exception. 5 | # For APIs, you may want to use :null_session instead. 6 | protect_from_forgery with: :exception 7 | end 8 | -------------------------------------------------------------------------------- /app/views/godmin/shared/_navigation_aside.html.erb: -------------------------------------------------------------------------------- 1 | <% if authentication_enabled? && 2 | admin_user.singleton_class.include?(Godmin::Authentication::User) && 3 | admin_user_signed_in? %> 4 |
  • 5 | <%= link_to translate_scoped("sessions.sign_out"), session_path, method: :delete %> 6 |
  • 7 | <% end %> 8 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20170207081043_create_another_admin_user.rb: -------------------------------------------------------------------------------- 1 | class CreateAnotherAdminUser < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :another_admin_users do |t| 4 | t.string :email 5 | t.text :password_digest 6 | 7 | t.timestamps null: false 8 | end 9 | end 10 | end 11 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | bundler_args: --without debug 2 | cache: bundler 3 | script: "bundle exec rake test" 4 | sudo: false 5 | 6 | rvm: 7 | - 2.2.2 8 | - 2.3.5 9 | 10 | gemfile: 11 | - gemfiles/rails_5.gemfile 12 | 13 | addons: 14 | code_climate: 15 | repo_token: 7e7ee66c976bdfe7a0d40d41958b97a1cf8a03b0462df5cba415f624c07c2071 16 | -------------------------------------------------------------------------------- /app/views/godmin/resource/index.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "breadcrumb" %> 2 | <%= render partial: "scopes" %> 3 | <%= render partial: "filters" %> 4 | 5 |
    6 | <%= render partial: "actions" %> 7 | <%= render partial: "table" %> 8 |
    9 | 10 | <%= render partial: "pagination" %> 11 | -------------------------------------------------------------------------------- /.rubocop.yml: -------------------------------------------------------------------------------- 1 | Rails: 2 | Enabled: true 3 | 4 | Documentation: 5 | Enabled: false 6 | 7 | Metrics/LineLength: 8 | Max: 120 9 | 10 | Metrics/ClassLength: 11 | Max: 300 12 | 13 | Metrics/ModuleLength: 14 | Max: 300 15 | 16 | Metrics/MethodLength: 17 | Max: 25 18 | 19 | Style/StringLiterals: 20 | EnforcedStyle: double_quotes 21 | -------------------------------------------------------------------------------- /app/views/godmin/resource/show.html.erb: -------------------------------------------------------------------------------- 1 | <%= render partial: "breadcrumb" %> 2 | 3 | 4 | <% @resource_service.attrs_for_show.each do |attr| %> 5 | 6 | 7 | 8 | 9 | <% end %> 10 |
    <%= @resource_class.human_attribute_name(attr) %><%= column_value(@resource, attr) %>
    11 | -------------------------------------------------------------------------------- /test/dummy/admin/app/services/admin/article_service.rb: -------------------------------------------------------------------------------- 1 | module Admin 2 | class ArticleService 3 | include Godmin::Resources::ResourceService 4 | 5 | attrs_for_index :id, :title, :published 6 | attrs_for_show :id, :title, :body, :published 7 | attrs_for_form :title, :body, :published 8 | 9 | filter :title 10 | end 11 | end 12 | -------------------------------------------------------------------------------- /lib/generators/godmin/authentication/templates/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | <% if namespaced? -%> 2 | require_dependency "<%= File.join(namespaced_path, "application_controller") %>" 3 | 4 | <% end -%> 5 | <% module_namespacing do -%> 6 | class SessionsController < ApplicationController 7 | include Godmin::Authentication::SessionsController 8 | end 9 | <% end -%> 10 | -------------------------------------------------------------------------------- /test/dummy/db/migrate/20150717121532_create_articles.rb: -------------------------------------------------------------------------------- 1 | class CreateArticles < ActiveRecord::Migration[5.0] 2 | def change 3 | create_table :articles do |t| 4 | t.string :title 5 | t.text :body 6 | t.boolean :published, default: false 7 | t.references :admin_user 8 | 9 | t.timestamps null: false 10 | end 11 | end 12 | end 13 | -------------------------------------------------------------------------------- /lib/generators/godmin/resource/templates/resource_controller.rb: -------------------------------------------------------------------------------- 1 | <% if namespaced? -%> 2 | require_dependency "<%= File.join(namespaced_path, "application_controller") %>" 3 | 4 | <% end -%> 5 | <% module_namespacing do -%> 6 | class <%= class_name.pluralize %>Controller < ApplicationController 7 | include Godmin::Resources::ResourceController 8 | end 9 | <% end -%> 10 | -------------------------------------------------------------------------------- /.codeclimate.yml: -------------------------------------------------------------------------------- 1 | --- 2 | engines: 3 | rubocop: 4 | enabled: true 5 | eslint: 6 | enabled: true 7 | csslint: 8 | enabled: true 9 | bundler-audit: 10 | enabled: true 11 | ratings: 12 | paths: 13 | - "**.rb" 14 | - "**.js" 15 | - "**.jsx" 16 | - "**.css" 17 | exclude_paths: 18 | - config/**/* 19 | - test/**/* 20 | - vendor/**/* 21 | - tmp/**/* 22 | -------------------------------------------------------------------------------- /test/dummy/app/controllers/articles_controller.rb: -------------------------------------------------------------------------------- 1 | class ArticlesController < ApplicationController 2 | include Godmin::Resources::ResourceController 3 | 4 | private 5 | 6 | def redirect_after_batch_action_publish 7 | articles_path(scope: :published) 8 | end 9 | 10 | def redirect_after_batch_action_unpublish 11 | articles_path(scope: :unpublished) 12 | end 13 | end 14 | -------------------------------------------------------------------------------- /app/views/godmin/resource/_form.html.erb: -------------------------------------------------------------------------------- 1 | <%= form_for @resource, url: [:admin, @resource] do |f| %> 2 | <% @resource_service.attrs_for_form.each do |attribute| %> 3 | <% if f.object.class.reflect_on_association(attribute) %> 4 | <%= f.association attribute %> 5 | <% else %> 6 | <%= f.input attribute %> 7 | <% end %> 8 | <% end %> 9 | <%= f.submit %> 10 | <% end %> 11 | -------------------------------------------------------------------------------- /lib/generators/godmin/policy/templates/policy.rb: -------------------------------------------------------------------------------- 1 | <% module_namespacing do -%> 2 | class <%= class_name %>Policy < Godmin::Authorization::Policy 3 | def index? 4 | true 5 | end 6 | 7 | def show? 8 | true 9 | end 10 | 11 | def create? 12 | true 13 | end 14 | 15 | def update? 16 | true 17 | end 18 | 19 | def destroy? 20 | true 21 | end 22 | end 23 | <% end -%> 24 | -------------------------------------------------------------------------------- /test/dummy/admin/app/controllers/admin/authorized_articles_controller.rb: -------------------------------------------------------------------------------- 1 | require_dependency "admin/articles_controller" 2 | 3 | module Admin 4 | class AuthorizedArticlesController < ArticlesController 5 | include Godmin::Authorization 6 | 7 | def admin_user 8 | "admin" 9 | end 10 | 11 | def resource_service_class 12 | Admin::ArticleService 13 | end 14 | end 15 | end 16 | -------------------------------------------------------------------------------- /test/dummy/config/routes.rb: -------------------------------------------------------------------------------- 1 | Rails.application.routes.draw do 2 | resources :articles do 3 | resources :comments 4 | end 5 | resources :authenticated_articles 6 | resources :authorized_articles 7 | resource :session, only: [:new, :create, :destroy] 8 | 9 | resource :another_admin_session, only: [:create] 10 | 11 | root to: "application#welcome" 12 | mount Admin::Engine, at: "admin" 13 | end 14 | -------------------------------------------------------------------------------- /lib/generators/godmin/resource/templates/resource_service.rb: -------------------------------------------------------------------------------- 1 | <% module_namespacing do -%> 2 | class <%= class_name %>Service 3 | include Godmin::Resources::ResourceService 4 | 5 | attrs_for_index <%= @attributes.map { |x| ":#{x}" }.join(", ") %> 6 | attrs_for_show <%= @attributes.map { |x| ":#{x}" }.join(", ") %> 7 | attrs_for_form <%= @attributes.map { |x| ":#{x}" }.join(", ") %> 8 | end 9 | <% end -%> 10 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/backtrace_silencers.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # You can add backtrace silencers for libraries that you're using but don't wish to see in your backtraces. 4 | # Rails.backtrace_cleaner.add_silencer { |line| line =~ /my_noisy_library/ } 5 | 6 | # You can also remove all the silencers if you're trying to debug a problem that might stem from framework code. 7 | # Rails.backtrace_cleaner.remove_silencers! 8 | -------------------------------------------------------------------------------- /test/dummy/admin/admin.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | 3 | # Maintain your gem's version: 4 | require "admin/version" 5 | 6 | # Describe your gem and declare its dependencies: 7 | Gem::Specification.new do |s| 8 | s.name = "admin" 9 | s.version = Admin::VERSION 10 | s.authors = ["admin"] 11 | s.email = ["admin@example.com"] 12 | s.summary = "admin" 13 | 14 | s.files = Dir["{app,config,db,lib}/**/*"] 15 | end 16 | -------------------------------------------------------------------------------- /lib/godmin/authorization.rb: -------------------------------------------------------------------------------- 1 | require "pundit" 2 | require "godmin/authorization/policy" 3 | 4 | module Godmin 5 | module Authorization 6 | extend ActiveSupport::Concern 7 | 8 | include Pundit 9 | 10 | included do 11 | rescue_from Pundit::NotAuthorizedError do 12 | render plain: "You are not authorized to do this", status: 403, layout: "godmin/login" 13 | end 14 | end 15 | 16 | def pundit_user 17 | admin_user 18 | end 19 | end 20 | end 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.rbc 2 | *.sassc 3 | .sass-cache 4 | capybara-*.html 5 | .rspec 6 | .rvmrc 7 | /.bundle 8 | /vendor/bundle 9 | /log/* 10 | /tmp/* 11 | /db/*.sqlite3 12 | /public/system/* 13 | /public/uploads/* 14 | /coverage/ 15 | /spec/tmp/* 16 | **.orig 17 | rerun.txt 18 | pickle-email-*.html 19 | .project 20 | config/initializers/secret_token.rb 21 | .DS_Store 22 | *.swp 23 | /test/dummy/log/* 24 | /test/dummy/tmp/* 25 | /test/dummy/db/*.sqlite3 26 | /test/tmp/* 27 | Gemfile.lock 28 | .tags 29 | gemfiles/.bundle 30 | gemfiles/*.lock 31 | -------------------------------------------------------------------------------- /test/dummy/admin/bin/rails: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env ruby 2 | # This command will automatically be run when you run "rails" with Rails 4 gems installed from the root of your application. 3 | 4 | ENGINE_ROOT = File.expand_path('../..', __FILE__) 5 | ENGINE_PATH = File.expand_path('../../lib/admin/engine', __FILE__) 6 | 7 | # Set up gems listed in the Gemfile. 8 | ENV['BUNDLE_GEMFILE'] ||= File.expand_path('../../Gemfile', __FILE__) 9 | require 'bundler/setup' if File.exist?(ENV['BUNDLE_GEMFILE']) 10 | 11 | require 'rails/all' 12 | require 'rails/engine/commands' 13 | -------------------------------------------------------------------------------- /app/views/layouts/godmin/_layout.html.erb: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <%= t("godmin.title") %> 5 | <%= stylesheet_link_tag File.join(engine_wrapper.namespaced_path, "application").gsub(/^\//, ""), media: "all" %> 6 | <%= javascript_include_tag File.join(engine_wrapper.namespaced_path, "application").gsub(/^\//, "") %> 7 | <%= csrf_meta_tags %> 8 | 9 | <%= yield :head %> 10 | 11 | 12 | <%= yield :body %> 13 | 14 | 15 | -------------------------------------------------------------------------------- /lib/godmin/resources/resource_service/pagination.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | module Resources 3 | module ResourceService 4 | module Pagination 5 | extend ActiveSupport::Concern 6 | 7 | def apply_pagination(page_param, resources) 8 | @paginator = Paginator.new(resources, per_page: per_page, current_page: page_param) 9 | @paginator.paginate 10 | end 11 | 12 | def paginator 13 | @paginator 14 | end 15 | 16 | def per_page 17 | 25 18 | end 19 | end 20 | end 21 | end 22 | end 23 | -------------------------------------------------------------------------------- /lib/godmin/helpers/translations.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | module Helpers 3 | module Translations 4 | def translate_scoped(translate, scope: nil, default: nil, **options) 5 | if @resource_class 6 | scope ||= @resource_class.to_s.underscore 7 | end 8 | 9 | defaults = [ 10 | ["godmin", scope, translate].compact.join(".").to_sym, 11 | ["godmin", translate].compact.join(".").to_sym, 12 | default 13 | ] 14 | 15 | t(defaults.shift, default: defaults, **options) 16 | end 17 | end 18 | end 19 | end 20 | -------------------------------------------------------------------------------- /test/dummy/config/database.yml: -------------------------------------------------------------------------------- 1 | # SQLite version 3.x 2 | # gem install sqlite3 3 | # 4 | # Ensure the SQLite 3 gem is defined in your Gemfile 5 | # gem 'sqlite3' 6 | development: 7 | adapter: sqlite3 8 | database: db/development.sqlite3 9 | pool: 5 10 | timeout: 5000 11 | 12 | # Warning: The database defined as "test" will be erased and 13 | # re-generated from your development database when you run "rake". 14 | # Do not set this db to the same as development or production. 15 | test: 16 | adapter: sqlite3 17 | database: db/test.sqlite3 18 | pool: 5 19 | timeout: 5000 20 | -------------------------------------------------------------------------------- /app/views/layouts/godmin/login.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :body do %> 2 |
    3 |
    4 |
    5 |
    6 |
    7 |

    <%= t("godmin.title") %>

    8 |
    9 |
    10 | <%= render "layouts/godmin/content" %> 11 |
    12 |
    13 |
    14 |
    15 |
    16 | <% end %> 17 | 18 | <%= render "layouts/godmin/layout" %> 19 | -------------------------------------------------------------------------------- /lib/godmin.rb: -------------------------------------------------------------------------------- 1 | require "bootstrap-sass" 2 | require "bootstrap_form" 3 | require "csv_builder" 4 | require "jquery-rails" 5 | require "momentjs-rails" 6 | require "sass-rails" 7 | require "selectize-rails" 8 | require "godmin/application_controller" 9 | require "godmin/authentication" 10 | require "godmin/authorization" 11 | require "godmin/engine" 12 | require "godmin/engine_wrapper" 13 | require "godmin/paginator" 14 | require "godmin/resolver" 15 | require "godmin/resources/resource_controller" 16 | require "godmin/resources/resource_service" 17 | require "godmin/version" 18 | 19 | module Godmin 20 | end 21 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/wrap_parameters.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # This file contains settings for ActionController::ParamsWrapper which 4 | # is enabled by default. 5 | 6 | # Enable parameter wrapping for JSON. You can disable this by setting :format to an empty array. 7 | ActiveSupport.on_load(:action_controller) do 8 | wrap_parameters format: [:json] if respond_to?(:wrap_parameters) 9 | end 10 | 11 | # To enable root element in JSON for ActiveRecord objects. 12 | # ActiveSupport.on_load(:active_record) do 13 | # self.include_root_in_json = true 14 | # end 15 | -------------------------------------------------------------------------------- /app/views/godmin/application/welcome.html.erb: -------------------------------------------------------------------------------- 1 |
    2 |
    3 |
    4 |

    Welcome

    5 |

    6 | You are running Godmin <%= Godmin::VERSION %>. 7 | Add your own root route to the routes file in order to remove this page. 8 |

    9 |

    10 | 11 | 12 | Browse the documentation 13 | 14 |

    15 |
    16 |
    17 |
    18 | -------------------------------------------------------------------------------- /lib/godmin/resources/resource_service/associations.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | module Resources 3 | module ResourceService 4 | module Associations 5 | extend ActiveSupport::Concern 6 | 7 | delegate :has_many_map, to: "self.class" 8 | 9 | module ClassMethods 10 | def has_many_map 11 | @has_many_map ||= {} 12 | end 13 | 14 | def has_many(attr, options = {}) 15 | has_many_map[attr] = { 16 | class_name: attr.to_s.singularize.classify 17 | }.merge(options) 18 | end 19 | end 20 | end 21 | end 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /test/dummy/app/assets/stylesheets/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require godmin 12 | *= require_tree . 13 | *= require_self 14 | */ 15 | -------------------------------------------------------------------------------- /app/views/godmin/resource/_batch_actions.html.erb: -------------------------------------------------------------------------------- 1 | <% if @resource_service.include_batch_actions? %> 2 | <%= link_to translate_scoped("batch_actions.buttons.select_all"), "#", class: "btn btn-default", 3 | data: { behavior: "batch-actions-select batch-actions-select-all" } %> 4 | <%= link_to translate_scoped("batch_actions.buttons.deselect_all"), "#", class: "btn btn-default hidden", 5 | data: { behavior: "batch-actions-select batch-actions-select-none" } %> 6 | 7 |
    8 | <% @resource_service.batch_action_map.each do |name, options| %> 9 | <%= batch_action_link(name, options) %> 10 | <% end %> 11 |
    12 | <% end %> 13 | -------------------------------------------------------------------------------- /test/integration/scopes_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ScopesTest < ActionDispatch::IntegrationTest 4 | def test_scopes 5 | Article.create! title: "foo" 6 | Article.create! title: "bar" 7 | Article.create! title: "baz", published: true 8 | 9 | visit articles_path(scope: :unpublished) 10 | 11 | assert page.has_content? "foo" 12 | assert page.has_content? "bar" 13 | assert page.has_no_content? "baz" 14 | 15 | within "#scopes" do 16 | click_link "Published" 17 | end 18 | 19 | assert page.has_no_content? "foo" 20 | assert page.has_no_content? "bar" 21 | assert page.has_content? "baz" 22 | end 23 | end 24 | -------------------------------------------------------------------------------- /lib/godmin/authentication/user.rb: -------------------------------------------------------------------------------- 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 28 | -------------------------------------------------------------------------------- /app/views/godmin/resource/_scopes.html.erb: -------------------------------------------------------------------------------- 1 | <% unless @resource_service.scope_map.empty? %> 2 |
    3 | 13 |
    14 | <% end %> 15 | -------------------------------------------------------------------------------- /test/dummy/app/assets/javascripts/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require moment 14 | //= require godmin 15 | //= require_tree . 16 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/secret_token.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Your secret key is used for verifying the integrity of signed cookies. 4 | # If you change this key, all old signed cookies will become invalid! 5 | 6 | # Make sure the secret is at least 30 characters and all random, 7 | # no regular words or you'll be exposed to dictionary attacks. 8 | # You can use `rake secret` to generate a secure secret key. 9 | 10 | # Make sure your secret_key_base is kept private 11 | # if you're sharing your code publicly. 12 | Dummy::Application.config.secret_key_base = '0cf88d4cc7f6bdc55d581d1e138403a5480559ff3198e315bef5f3a98692c8944e84b677f6d976226ee80179d461ca5e10821ad9db1c22706eca0af28566da75' 13 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | # Declare your gem's dependencies in godmin.gemspec. 4 | # Bundler will treat runtime dependencies like base dependencies, and 5 | # development dependencies will be added by default to the :development group. 6 | gemspec 7 | 8 | # Declare any dependencies that are still in development here instead of in 9 | # your gemspec. These might include edge Rails or gems from your path or 10 | # Git. Remember to move these dependencies to your gemspec before releasing 11 | # your gem to rubygems.org. 12 | 13 | # The dummy app loads whatever is specified in this gemfile, therefore 14 | # we add the admin engine used by the dummy app here 15 | gem "admin", path: "test/dummy/admin", group: %i[test development] 16 | -------------------------------------------------------------------------------- /test/dummy/admin/app/assets/javascripts/admin/application.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require moment 14 | //= require godmin 15 | //= require_tree . 16 | -------------------------------------------------------------------------------- /test/dummy/config/initializers/inflections.rb: -------------------------------------------------------------------------------- 1 | # Be sure to restart your server when you modify this file. 2 | 3 | # Add new inflection rules using the following format. Inflections 4 | # are locale specific, and you may define rules for as many different 5 | # locales as you wish. All of these examples are active by default: 6 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 7 | # inflect.plural /^(ox)$/i, '\1en' 8 | # inflect.singular /^(ox)en/i, '\1' 9 | # inflect.irregular 'person', 'people' 10 | # inflect.uncountable %w( fish sheep ) 11 | # end 12 | 13 | # These inflection rules are supported but not enabled by default: 14 | # ActiveSupport::Inflector.inflections(:en) do |inflect| 15 | # inflect.acronym 'RESTful' 16 | # end 17 | -------------------------------------------------------------------------------- /app/views/godmin/resource/_export_actions.html.erb: -------------------------------------------------------------------------------- 1 | <% if @resource_service.attrs_for_export.present? %> 2 |
    3 | 6 | 14 |
    15 | <% end %> 16 | -------------------------------------------------------------------------------- /lib/godmin/helpers/batch_actions.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | module Helpers 3 | module BatchActions 4 | def batch_action_link(name, options) 5 | return unless @resource_service.include_batch_action?(name) 6 | 7 | link_to( 8 | translate_scoped("batch_actions.labels.#{name}", default: name.to_s.titleize), 9 | [:admin, *@resource_parents, @resource_class], 10 | method: :patch, 11 | class: "btn btn-default hidden", 12 | data: { 13 | behavior: "batch-actions-action-link", 14 | confirm: options[:confirm] ? translate_scoped("batch_actions.confirm_message") : false, 15 | value: name 16 | } 17 | ) 18 | end 19 | end 20 | end 21 | end 22 | -------------------------------------------------------------------------------- /lib/godmin/authorization/policy.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | module Authorization 3 | class Policy 4 | attr_reader :user, :record 5 | 6 | def initialize(user, record, default: false) 7 | @user = user 8 | @record = record 9 | @default = default 10 | end 11 | 12 | def index? 13 | @default 14 | end 15 | 16 | def show? 17 | @default 18 | end 19 | 20 | def new? 21 | create? 22 | end 23 | 24 | def edit? 25 | update? 26 | end 27 | 28 | def create? 29 | @default 30 | end 31 | 32 | def update? 33 | @default 34 | end 35 | 36 | def destroy? 37 | @default 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/godmin/generators/named_base.rb: -------------------------------------------------------------------------------- 1 | require "godmin/generators/base" 2 | 3 | module Godmin 4 | module Generators 5 | class NamedBase < Base 6 | argument :name, type: :string 7 | 8 | private 9 | 10 | def full_class_name 11 | if namespaced? 12 | "#{namespace}::#{class_name}" 13 | else 14 | class_name 15 | end 16 | end 17 | 18 | def class_name 19 | @_class_name ||= name.classify 20 | end 21 | 22 | def class_path 23 | @_class_path ||= namespaced_path + name.classify.deconstantize.split("::").map(&:underscore) 24 | end 25 | 26 | def file_name 27 | @_file_name ||= class_name.demodulize.underscore 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/dummy/admin/app/assets/stylesheets/admin/application.css: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the bottom of the 9 | * compiled file so the styles you add here take precedence over styles defined in any styles 10 | * defined in the other CSS/SCSS files in this directory. It is generally better to create a new 11 | * file per style scope. 12 | * 13 | *= require godmin 14 | *= require_tree . 15 | *= require_self 16 | */ 17 | -------------------------------------------------------------------------------- /test/unit/resources/resource_service_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Godmin 4 | class ResourceServiceTest < ActiveSupport::TestCase 5 | def setup 6 | @article_service = Fakes::ArticleService.new 7 | end 8 | 9 | def test_resource_class 10 | assert_equal Fakes::Article, @article_service.resource_class 11 | end 12 | 13 | def test_attrs_for_index 14 | assert_equal [:id, :title, :country], @article_service.attrs_for_index 15 | end 16 | 17 | def test_attrs_for_show 18 | assert_equal [:title, :country], @article_service.attrs_for_show 19 | end 20 | 21 | def test_attrs_for_form 22 | assert_equal [:id, :title, :country, :body], @article_service.attrs_for_form 23 | end 24 | 25 | def test_attrs_for_export 26 | assert_equal [:id, :title], @article_service.attrs_for_export 27 | end 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/views/godmin/resource/_filters.html.erb: -------------------------------------------------------------------------------- 1 | <% if @resource_service.filter_map.present? %> 2 |
    3 |
    4 | <%= filter_form do |f| %> 5 | <%= f.hidden_field :scope, value: params[:scope] %> 6 | <%= f.hidden_field :order, value: params[:order] %> 7 | 8 | <% @resource_service.filter_map.each do |name, options| %> 9 | <%= partial_override "#{controller_path}/filters/#{name}", f: f, name: name, options: options do %> 10 | <%= f.filter_field(name, options) %> 11 | <% end %> 12 | <% end %> 13 | 14 |
    15 |
    16 | <%= f.apply_filters_button %> 17 | <%= f.clear_filters_button %> 18 |
    19 | <% end %> 20 |
    21 |
    22 | <% end %> 23 | -------------------------------------------------------------------------------- /lib/godmin/authentication.rb: -------------------------------------------------------------------------------- 1 | require "godmin/authentication/sessions_controller" 2 | require "godmin/authentication/user" 3 | 4 | module Godmin 5 | module Authentication 6 | extend ActiveSupport::Concern 7 | 8 | included do 9 | before_action :authenticate 10 | 11 | helper_method :admin_user 12 | helper_method :admin_user_signed_in? 13 | end 14 | 15 | def authenticate 16 | return unless authentication_enabled? 17 | return if admin_user_signed_in? 18 | 19 | redirect_to new_session_path 20 | end 21 | 22 | def admin_user_class; end 23 | 24 | def admin_user 25 | return unless admin_user_class 26 | return unless session[:admin_user_id] 27 | 28 | @_admin_user ||= admin_user_class.find_by(id: session[:admin_user_id]) 29 | end 30 | 31 | def admin_user_signed_in? 32 | admin_user.present? 33 | end 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/unit/resources/resource_service/pagination_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Godmin 4 | module ResourceService 5 | class PaginationTest < ActiveSupport::TestCase 6 | def setup 7 | @article_service = Fakes::ArticleService.new 8 | 9 | resources_class = Class.new do 10 | def limit(_limit_param) 11 | self 12 | end 13 | 14 | def offset(_offset_param) 15 | self 16 | end 17 | end 18 | 19 | @resources = resources_class.new 20 | end 21 | 22 | def test_paginator_is_set_correctly 23 | @article_service.apply_pagination(1, @resources) 24 | 25 | assert_kind_of Paginator, @article_service.paginator 26 | assert_equal 1, @article_service.paginator.current_page 27 | assert_equal 25, @article_service.paginator.per_page 28 | end 29 | end 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /app/assets/javascripts/godmin/index.js: -------------------------------------------------------------------------------- 1 | // This is a manifest file that'll be compiled into application.js, which will include all the files 2 | // listed below. 3 | // 4 | // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts, 5 | // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path. 6 | // 7 | // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the 8 | // compiled file. 9 | // 10 | // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details 11 | // about supported directives. 12 | // 13 | //= require jquery 14 | //= require jquery_ujs 15 | //= require bootstrap-sprockets 16 | //= require bootstrap-datetimepicker 17 | //= require selectize 18 | //= require godmin/batch-actions 19 | //= require godmin/datetimepickers 20 | //= require godmin/navigation 21 | //= require godmin/select-boxes 22 | -------------------------------------------------------------------------------- /app/assets/javascripts/godmin/select-boxes.js: -------------------------------------------------------------------------------- 1 | window.Godmin = window.Godmin || {}; 2 | 3 | Godmin.SelectBoxes = (function() { 4 | function initialize() { 5 | initializeEvents(); 6 | initializeState(); 7 | } 8 | 9 | function initializeEvents() {} 10 | 11 | function initializeState() { 12 | initializeSelectBox($('[data-behavior~=select-box]')); 13 | } 14 | 15 | function initializeSelectBox($el, options) { 16 | var defaults = { 17 | inputClass: 'selectize-input', 18 | render: { 19 | option_create: function(data, escape) { 20 | return '
    ' + (this.$input.data("add-label") || "+") + ' ' + escape(data.input) + '
    '; 21 | } 22 | } 23 | }; 24 | 25 | $el.selectize($.extend(defaults, options)); 26 | } 27 | 28 | return { 29 | initialize: initialize, 30 | initializeSelectBox: initializeSelectBox 31 | }; 32 | })(); 33 | 34 | $(function() { 35 | Godmin.SelectBoxes.initialize(); 36 | }); 37 | -------------------------------------------------------------------------------- /test/integration/filters_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class FiltersTest < ActionDispatch::IntegrationTest 4 | def test_filter 5 | Article.create! title: "foo" 6 | Article.create! title: "bar" 7 | 8 | visit articles_path 9 | 10 | fill_in "Title", with: "foo" 11 | click_button "Filter" 12 | assert_equal 200, page.status_code 13 | assert page.has_content? "foo" 14 | assert page.has_no_content? "bar" 15 | 16 | click_link "Clear filter" 17 | assert_equal 200, page.status_code 18 | assert page.has_content? "foo" 19 | assert page.has_content? "bar" 20 | end 21 | 22 | def test_select_filter 23 | Article.create! title: "foo", published: true 24 | Article.create! title: "bar" 25 | 26 | visit articles_path 27 | 28 | within "#filters" do 29 | find("select").select("Published") 30 | end 31 | click_button "Filter" 32 | assert_equal 200, page.status_code 33 | assert page.has_content? "foo" 34 | assert page.has_no_content? "bar" 35 | end 36 | end 37 | -------------------------------------------------------------------------------- /lib/generators/godmin/resource/resource_generator.rb: -------------------------------------------------------------------------------- 1 | require "godmin/generators/named_base" 2 | 3 | class Godmin::ResourceGenerator < Godmin::Generators::NamedBase 4 | argument :attributes, type: :array, default: [], banner: "attribute attribute" 5 | 6 | def add_route 7 | invoke "resource_route" 8 | end 9 | 10 | def add_navigation 11 | append_to_file File.join("app/views", namespaced_path, "shared/_navigation.html.erb") do 12 | <<-END.strip_heredoc 13 | <%= navbar_item #{class_name} %> 14 | END 15 | end 16 | end 17 | 18 | def create_model 19 | if namespaced? 20 | template "resource_model.rb", File.join("app/models", class_path, "#{file_name}.rb") 21 | end 22 | end 23 | 24 | def create_controller 25 | template "resource_controller.rb", File.join("app/controllers", class_path, "#{file_name.pluralize}_controller.rb") 26 | end 27 | 28 | def create_service 29 | template "resource_service.rb", File.join("app/services", class_path, "#{file_name}_service.rb") 30 | end 31 | end 32 | -------------------------------------------------------------------------------- /test/integration/authentication_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class AuthenticationTest < ActionDispatch::IntegrationTest 4 | def test_sign_in_and_out 5 | AdminUser.create!(email: "admin@example.com", password: "password") 6 | visit authenticated_articles_path 7 | assert_not_equal authenticated_articles_path, current_path 8 | fill_in "Email", with: "admin@example.com" 9 | fill_in "Password", with: "password" 10 | click_button "Sign in" 11 | visit authenticated_articles_path 12 | assert_equal authenticated_articles_path, current_path 13 | click_link "Sign out" 14 | visit authenticated_articles_path 15 | assert_not_equal authenticated_articles_path, current_path 16 | end 17 | 18 | def test_sign_in_with_non_default_user 19 | AnotherAdminUser.create!(email: "another_admin@example.com", password: "password") 20 | post another_admin_session_path, params: { 21 | another_admin_user: { email: "another_admin@example.com", password: "password" } 22 | } 23 | assert_redirected_to root_path 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /test/integration/authorization_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class AuthorizationTest < ActionDispatch::IntegrationTest 4 | def test_can_index? 5 | visit authorized_articles_path 6 | assert_equal 200, page.status_code 7 | end 8 | 9 | def test_can_show? 10 | article = Article.create! title: "foo" 11 | 12 | visit authorized_articles_path 13 | within "[data-resource-id='#{article.id}']" do 14 | click_link "Show" 15 | end 16 | 17 | assert_equal article_path(article), current_path 18 | assert_equal 200, page.status_code 19 | end 20 | 21 | def test_cannot_destroy? 22 | article = Article.create! title: "foo" 23 | 24 | visit authorized_articles_path 25 | within "[data-resource-id='#{article.id}']" do 26 | assert page.has_no_content? "Destroy" 27 | end 28 | 29 | page.driver.delete authorized_article_path(article) 30 | assert_equal 403, page.status_code 31 | end 32 | 33 | def test_cannot_index_in_engine? 34 | visit admin.authorized_articles_path 35 | assert_equal 403, page.status_code 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /app/views/godmin/resource/columns/_actions.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <% if policy(resource).show? %> 3 | <%= link_to( 4 | translate_scoped("actions.show"), 5 | [:admin, *@resource_parents, resource], 6 | class: "btn btn-default", 7 | title: translate_scoped("actions.show_title", resource: resource) 8 | ) %> 9 | <% end %> 10 | <% if policy(resource).edit? %> 11 | <%= link_to( 12 | translate_scoped("actions.edit"), 13 | [:edit, :admin, *@resource_parents, resource], 14 | class: "btn btn-default", 15 | title: translate_scoped("actions.edit_title", resource: resource) 16 | ) %> 17 | <% end %> 18 | <% if policy(resource).destroy? %> 19 | <%= link_to( 20 | translate_scoped("actions.destroy"), 21 | [:admin, *@resource_parents, resource], 22 | method: :delete, 23 | class: "btn btn-default", 24 | title: translate_scoped("actions.destroy_title", resource: resource), 25 | data: { confirm: translate_scoped("actions.confirm_message") } 26 | ) %> 27 | <% end %> 28 |
    29 | -------------------------------------------------------------------------------- /MIT-LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2013 Varvet 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /lib/godmin/helpers/application.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | module Helpers 3 | module Application 4 | # Renders the provided partial with locals if it exists, otherwise 5 | # yields the given block. The lookup context call is cached for 6 | # each partial. 7 | def partial_override(partial, locals = {}, &block) 8 | @_partial_override ||= {} 9 | 10 | unless @_partial_override.key?(partial) 11 | @_partial_override[partial] = lookup_context.exists?(partial, nil, true) 12 | end 13 | 14 | if @_partial_override[partial] 15 | render partial: partial, locals: locals 16 | else 17 | capture(&block) 18 | end 19 | end 20 | 21 | # Wraps the policy helper so that it is always accessible, even when 22 | # authorization is not enabled. When that is the case, it returns a 23 | # policy that always returns true. 24 | def policy(resource) 25 | if authorization_enabled? 26 | super(resource) 27 | else 28 | Authorization::Policy.new(nil, nil, default: true) 29 | end 30 | end 31 | end 32 | end 33 | end 34 | -------------------------------------------------------------------------------- /app/views/godmin/resource/_breadcrumb.html.erb: -------------------------------------------------------------------------------- 1 | 34 | -------------------------------------------------------------------------------- /lib/godmin/engine_wrapper.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | class EngineWrapper 3 | attr_reader :engine 4 | 5 | def initialize(controller) 6 | @engine = find_engine(controller) 7 | end 8 | 9 | def namespace 10 | @namespace ||= engine.railtie_namespace 11 | end 12 | 13 | def namespaced? 14 | @namespaced ||= namespace.present? 15 | end 16 | 17 | def namespaced_path 18 | @namespaced_path ||= begin 19 | if namespaced? 20 | namespace.name.split("::").map(&:underscore) 21 | else 22 | [] 23 | end 24 | end 25 | end 26 | 27 | def root 28 | engine.root 29 | end 30 | 31 | private 32 | 33 | def find_engine(controller) 34 | engine_module = find_engine_module(controller) 35 | 36 | if engine_module 37 | "#{engine_module}::Engine".constantize 38 | else 39 | Rails.application 40 | end 41 | end 42 | 43 | def find_engine_module(controller) 44 | controller.parents.find do |parent| 45 | parent.respond_to?(:use_relative_model_naming?) && parent.use_relative_model_naming? 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /app/assets/javascripts/godmin/navigation.js: -------------------------------------------------------------------------------- 1 | window.Godmin = window.Godmin || {}; 2 | 3 | Godmin.Navigation = (function() { 4 | function initialize() { 5 | initializeEvents(); 6 | initializeState(); 7 | } 8 | 9 | function initializeEvents() {} 10 | 11 | function initializeState() { 12 | setActiveLink(); 13 | removeEmptyDropdowns(); 14 | } 15 | 16 | function setActiveLink() { 17 | var $links = $('.nav.navbar-nav a[href="' + window.location.pathname + window.location.search + '"]'); 18 | 19 | if ($links.length) { 20 | addActiveClass($links); 21 | } else { 22 | addActiveClass($('.nav.navbar-nav a[href="' + window.location.pathname + '"]')); 23 | } 24 | } 25 | 26 | function addActiveClass($links) { 27 | $links.closest('li').addClass('active'); 28 | $links.closest('li.dropdown').addClass('active'); 29 | } 30 | 31 | function removeEmptyDropdowns() { 32 | $('.navbar-nav .dropdown, .breadcrumb .dropdown').each(function() { 33 | if ($(this).find('li').length === 0) { 34 | $(this).remove(); 35 | } 36 | }); 37 | } 38 | 39 | return { 40 | initialize: initialize 41 | }; 42 | })(); 43 | 44 | $(function() { 45 | Godmin.Navigation.initialize(); 46 | }); 47 | -------------------------------------------------------------------------------- /test/integration/column_ordering_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ColumnOrderingTest < ActionDispatch::IntegrationTest 4 | def test_default_order_links 5 | visit articles_path 6 | link = find("#table th.column-created_at a") 7 | assert_equal "/articles?order=created_at_desc", link[:href] 8 | end 9 | 10 | def test_order_flipped 11 | visit articles_path 12 | link_matcher = "#table th.column-created_at a" 13 | find(link_matcher).click 14 | link = find(link_matcher) 15 | assert_equal "/articles?order=created_at_asc", link[:href] 16 | end 17 | 18 | def test_order_links_when_order_empty 19 | visit articles_path(order: "") 20 | link = find("#table th.column-created_at a") 21 | assert_equal "/articles?order=created_at_desc", link[:href] 22 | end 23 | 24 | def test_order_links_when_not_orderable 25 | visit articles_path 26 | assert has_selector?("#table th.column-non_orderable_column") 27 | assert has_no_link?("#table th.column-non_orderable_column a") 28 | end 29 | 30 | def test_order_links_when_custom_orderable 31 | visit articles_path 32 | link = find("#table th.column-admin_user a") 33 | assert_equal "/articles?order=admin_user_desc", link[:href] 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /test/dummy/config/environments/development.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # In the development environment your application's code is reloaded on 5 | # every request. This slows down response time but is perfect for development 6 | # since you don't have to restart the web server when you make code changes. 7 | config.cache_classes = false 8 | 9 | # Do not eager load code on boot. 10 | config.eager_load = false 11 | 12 | # Show full error reports and disable caching. 13 | config.consider_all_requests_local = true 14 | config.action_controller.perform_caching = false 15 | 16 | # Don't care if the mailer can't send. 17 | config.action_mailer.raise_delivery_errors = false 18 | 19 | # Print deprecation notices to the Rails logger. 20 | config.active_support.deprecation = :log 21 | 22 | # Raise an error on page load if there are pending migrations 23 | config.active_record.migration_error = :page_load 24 | 25 | # Debug mode disables concatenation and preprocessing of assets. 26 | # This option may cause significant delays in view rendering with a large 27 | # number of complex assets. 28 | config.assets.debug = true 29 | end 30 | -------------------------------------------------------------------------------- /test/unit/resources/resource_service/ordering_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Godmin 4 | module ResourceService 5 | class OrderingTest < ActiveSupport::TestCase 6 | def setup 7 | @resources_class = Class.new do 8 | attr_reader :order_param 9 | 10 | def order(order_param) 11 | @order_param = order_param 12 | end 13 | end 14 | 15 | @resources = @resources_class.new 16 | @article_service = Fakes::ArticleService.new(resources: @resources) 17 | end 18 | 19 | def test_apply_order 20 | resources = @resources_class.new 21 | @article_service.apply_order("title_desc", resources) 22 | assert_equal "articles.title desc", resources.order_param 23 | end 24 | 25 | def test_apply_order_without_order 26 | resources = @resources_class.new 27 | @article_service.apply_order("", resources) 28 | assert_nil resources.order_param 29 | end 30 | 31 | def test_apply_order_with_custom_ordering_method 32 | @article_service.apply_order("foobar_desc", @resources) 33 | assert_equal [@resources, "desc"], @article_service.called_methods[:ordering][:by_foobar] 34 | end 35 | end 36 | end 37 | end 38 | -------------------------------------------------------------------------------- /lib/godmin/resources/resource_service/filters.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | module Resources 3 | module ResourceService 4 | module Filters 5 | extend ActiveSupport::Concern 6 | 7 | delegate :filter_map, to: "self.class" 8 | 9 | def apply_filters(filter_params, resources) 10 | if filter_params.present? 11 | filter_params.each do |name, value| 12 | if apply_filter?(name, value) 13 | resources = send("filter_#{name}", resources, value) 14 | end 15 | end 16 | end 17 | resources 18 | end 19 | 20 | private 21 | 22 | def apply_filter?(name, value) 23 | return false if value == [""] 24 | filter_map.key?(name.to_sym) && value.present? 25 | end 26 | 27 | module ClassMethods 28 | def filter_map 29 | @filter_map ||= {} 30 | end 31 | 32 | def filter(attr, options = {}) 33 | filter_map[attr] = { 34 | as: :string, 35 | option_text: "to_s", 36 | option_value: "id", 37 | collection: nil 38 | }.merge(options) 39 | end 40 | end 41 | end 42 | end 43 | end 44 | end 45 | -------------------------------------------------------------------------------- /app/views/layouts/godmin/application.html.erb: -------------------------------------------------------------------------------- 1 | <% content_for :body do %> 2 | 23 |
    24 | <%= render "layouts/godmin/content" %> 25 |
    26 | <% end %> 27 | 28 | <%= render "layouts/godmin/layout" %> 29 | -------------------------------------------------------------------------------- /test/integration/nested_resources_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class NestedResourcesTest < ActionDispatch::IntegrationTest 4 | def test_list_nested_resources 5 | article = Article.create! title: "foo", comments: [ 6 | Comment.new(title: "bar") 7 | ] 8 | 9 | visit articles_path 10 | within "[data-resource-id='#{article.id}']" do 11 | click_link "Show" 12 | end 13 | click_link "Comments" 14 | 15 | assert_equal article_comments_path(article), current_path 16 | assert page.has_content? "bar" 17 | end 18 | 19 | def test_list_nested_resources_scoping 20 | article = Article.create! title: "foo", comments: [ 21 | Comment.new(title: "bar") 22 | ] 23 | Comment.create! title: "baz" 24 | 25 | visit article_comments_path(article) 26 | 27 | assert page.has_content? "bar" 28 | assert page.has_no_content? "baz" 29 | end 30 | 31 | def test_create_nested_resource 32 | article = Article.create! title: "foo" 33 | 34 | visit new_article_comment_path(article) 35 | 36 | fill_in "Title", with: "bar" 37 | click_button "Create Comment" 38 | 39 | assert_equal article_comment_path(article, Comment.last), current_path 40 | 41 | within "#breadcrumb" do 42 | click_link "Comments" 43 | end 44 | 45 | assert page.has_content? "bar" 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /app/assets/javascripts/godmin/datetimepickers.js: -------------------------------------------------------------------------------- 1 | window.Godmin = window.Godmin || {}; 2 | 3 | Godmin.Datetimepickers = (function() { 4 | function initialize() { 5 | initializeEvents(); 6 | initializeState(); 7 | } 8 | 9 | function initializeEvents() {} 10 | 11 | function initializeState() { 12 | initializeDatepicker($('[data-behavior~=datepicker]')); 13 | initializeTimepicker($('[data-behavior~=timepicker]')); 14 | initializeDatetimepicker($('[data-behavior~=datetimepicker]')); 15 | } 16 | 17 | function initializeDatepicker($el, options) { 18 | var defaults = { 19 | pickTime: false 20 | }; 21 | initializeDatetimepicker($el, $.extend(defaults, options)); 22 | } 23 | 24 | function initializeTimepicker($el, options) { 25 | var defaults = { 26 | pickDate: false 27 | }; 28 | initializeDatetimepicker($el, $.extend(defaults, options)); 29 | } 30 | 31 | function initializeDatetimepicker($el, options) { 32 | var defaults = {}; 33 | $el.datetimepicker($.extend(defaults, options)); 34 | } 35 | 36 | return { 37 | initialize: initialize, 38 | initializeDatepicker: initializeDatepicker, 39 | initializeTimepicker: initializeTimepicker, 40 | initializeDatetimepicker: initializeDatetimepicker 41 | }; 42 | })(); 43 | 44 | $(function() { 45 | Godmin.Datetimepickers.initialize(); 46 | }); 47 | -------------------------------------------------------------------------------- /lib/generators/godmin/authentication/authentication_generator.rb: -------------------------------------------------------------------------------- 1 | require "godmin/generators/named_base" 2 | 3 | class Godmin::AuthenticationGenerator < Godmin::Generators::NamedBase 4 | argument :name, type: :string, default: "admin_user" 5 | 6 | def create_model 7 | generate "model", "#{name} email:string password_digest:text --no-test-framework" 8 | end 9 | 10 | def modify_model 11 | inject_into_file File.join("app/models", class_path, "#{file_name}.rb"), after: "ActiveRecord::Base\n" do 12 | <<-END.strip_heredoc.indent(namespace ? 4 : 2) 13 | include Godmin::Authentication::User 14 | 15 | def self.login_column 16 | :email 17 | end 18 | END 19 | end 20 | end 21 | 22 | def create_route 23 | route "resource :session, only: [:new, :create, :destroy]" 24 | end 25 | 26 | def create_sessions_controller 27 | template "sessions_controller.rb", File.join("app/controllers", namespaced_path, "sessions_controller.rb") 28 | end 29 | 30 | def modify_application_controller 31 | inject_into_file File.join("app/controllers", namespaced_path, "application_controller.rb"), after: "Godmin::ApplicationController\n" do 32 | <<-END.strip_heredoc.indent(namespace ? 4 : 2) 33 | include Godmin::Authentication 34 | 35 | def admin_user_class 36 | #{full_class_name} 37 | end 38 | END 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/godmin/authentication/sessions_controller.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | module Authentication 3 | module SessionsController 4 | extend ActiveSupport::Concern 5 | 6 | included do 7 | layout "godmin/login" 8 | prepend_before_action :disable_authentication 9 | end 10 | 11 | def new 12 | @admin_user = admin_user_class.new 13 | end 14 | 15 | def create 16 | @admin_user = admin_user_class.find_by_login(admin_user_login) 17 | 18 | if @admin_user && @admin_user.authenticate(admin_user_params[:password]) 19 | session[:admin_user_id] = @admin_user.id 20 | redirect_to root_path, notice: t("godmin.sessions.signed_in") 21 | else 22 | redirect_to new_session_path, alert: t("godmin.sessions.failed_sign_in") 23 | end 24 | end 25 | 26 | def destroy 27 | session[:admin_user_id] = nil 28 | redirect_to new_session_path, notice: t("godmin.sessions.signed_out") 29 | end 30 | 31 | private 32 | 33 | def admin_user_login 34 | admin_user_params[admin_user_class.login_column] 35 | end 36 | 37 | def admin_user_params 38 | params.require(admin_user_class.model_name.param_key.to_sym).permit( 39 | admin_user_class.login_column, 40 | :password, 41 | :password_confirm 42 | ) 43 | end 44 | end 45 | end 46 | end 47 | -------------------------------------------------------------------------------- /lib/godmin/generators/base.rb: -------------------------------------------------------------------------------- 1 | require "active_support/all" 2 | 3 | module Godmin 4 | module Generators 5 | class Base < Rails::Generators::Base 6 | def self.source_paths 7 | %w[authentication install policy resource].map do |path| 8 | File.expand_path("../../../generators/godmin/#{path}/templates", __FILE__) 9 | end 10 | end 11 | 12 | private 13 | 14 | def namespace 15 | @_namespace ||= Rails::Generators.namespace 16 | end 17 | 18 | def namespaced? 19 | @_namespaced ||= namespace.present? 20 | end 21 | 22 | def namespaced_path 23 | @_namespaced_path ||= begin 24 | if namespaced? 25 | namespace.name.split("::").map(&:underscore) 26 | else 27 | [] 28 | end 29 | end 30 | end 31 | 32 | def module_namespacing(&block) 33 | content = capture(&block) 34 | content = wrap_with_namespace(content) if namespaced? 35 | concat(content) 36 | end 37 | 38 | def indent(content, multiplier = 2) 39 | spaces = " " * multiplier 40 | content.each_line.map { |line| line.blank? ? line : "#{spaces}#{line}" }.join 41 | end 42 | 43 | def wrap_with_namespace(content) 44 | content = indent(content).chomp 45 | "module #{namespace.name}\n#{content}\nend\n" 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /lib/godmin/resources/resource_service/ordering.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | module Resources 3 | module ResourceService 4 | module Ordering 5 | extend ActiveSupport::Concern 6 | 7 | def apply_order(order_param, resources) 8 | if order_param.present? && order_column_method?(order_column(order_param)) 9 | send("order_by_#{order_column(order_param)}", resources, order_direction(order_param)) 10 | elsif order_param.present? && order_column_column?(order_column(order_param)) 11 | resources.order("#{resource_class.table_name}.#{order_column(order_param)} #{order_direction(order_param)}") 12 | else 13 | resources 14 | end 15 | end 16 | 17 | def orderable_column?(column) 18 | order_column_method?(column) || order_column_column?(column) 19 | end 20 | 21 | protected 22 | 23 | def order_column_method?(column) 24 | respond_to?("order_by_#{column}") 25 | end 26 | 27 | def order_column_column?(column) 28 | resource_class.column_names.include?(column) 29 | end 30 | 31 | def order_column(order_param) 32 | order_param.rpartition("_").first 33 | end 34 | 35 | def order_direction(order_param) 36 | order_param.rpartition("_").last == "asc" ? "asc" : "desc" 37 | end 38 | end 39 | end 40 | end 41 | end 42 | -------------------------------------------------------------------------------- /lib/godmin/paginator.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | class Paginator 3 | WINDOW_SIZE = 7 4 | 5 | attr_reader :per_page, :current_page 6 | 7 | def initialize(resources, per_page: 25, current_page: nil) 8 | @resources = resources 9 | @per_page = per_page 10 | @current_page = current_page ? current_page.to_i : 1 11 | end 12 | 13 | def paginate 14 | @resources.limit(per_page).offset(offset) 15 | end 16 | 17 | def pages 18 | @pages ||= begin 19 | pages = (1..total_pages).to_a 20 | 21 | return pages unless total_pages > WINDOW_SIZE 22 | 23 | if current_page < WINDOW_SIZE 24 | pages.slice(0, WINDOW_SIZE) 25 | elsif current_page > (total_pages - WINDOW_SIZE) 26 | pages.slice(-WINDOW_SIZE, WINDOW_SIZE) 27 | else 28 | pages.slice(pages.index(current_page) - (WINDOW_SIZE / 2), WINDOW_SIZE) 29 | end 30 | end 31 | end 32 | 33 | def total_pages 34 | @total_pages ||= (total_resources.to_f / per_page).ceil 35 | end 36 | 37 | def total_resources 38 | @total_resources ||= begin 39 | count = @resources.count 40 | 41 | if count.respond_to?(:count) 42 | count.count 43 | else 44 | count 45 | end 46 | end 47 | end 48 | 49 | private 50 | 51 | def offset 52 | (current_page * per_page) - per_page 53 | end 54 | end 55 | end 56 | -------------------------------------------------------------------------------- /config/locales/sv.yml: -------------------------------------------------------------------------------- 1 | sv: 2 | godmin: 3 | title: Godmin 4 | batch_actions: 5 | buttons: 6 | select_all: Markera alla 7 | deselect_all: Avmarkera alla 8 | confirm_message: Är du säker? 9 | filters: 10 | select: 11 | placeholder: 12 | one: Välj någonting 13 | many: Välj någonting 14 | buttons: 15 | apply: Filtrera 16 | clear: Rensa filter 17 | actions: 18 | label: Alternativ 19 | show: Visa 20 | show_title: Visa %{resource} 21 | edit: Ändra 22 | edit_title: Ändra %{resource} 23 | destroy: Ta bort 24 | destroy_title: Ta bort %{resource} 25 | confirm_message: Är du säker? 26 | export: Exportera 27 | export_as: Som 28 | associations: 29 | label: Nästlade resurser 30 | sessions: 31 | sign_in: Logga in 32 | sign_out: Logga ut 33 | signed_in: Loggade in 34 | signed_out: Loggade ut 35 | failed_sign_in: Fel inloggningsuppgifter 36 | flash: 37 | create: "%{resource} skapades" 38 | update: "%{resource} uppdaterades" 39 | destroy: "%{resource} togs bort" 40 | batch_action: "%{number_of_records} %{resource} påverkades" 41 | pagination: 42 | first: "Första" 43 | last: "Sista" 44 | entries: 45 | zero: "Inga %{resource} funna" 46 | other: "Visar %{count} av %{total} %{resource}" 47 | datetimepickers: 48 | datepicker: ! "%Y-%m-%d" 49 | datetimepicker: ! "%Y-%m-%d %H:%M" 50 | -------------------------------------------------------------------------------- /test/dummy/public/500.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | We're sorry, but something went wrong (500) 5 | 48 | 49 | 50 | 51 | 52 |
    53 |

    We're sorry, but something went wrong.

    54 |
    55 |

    If you are the application owner check the logs for more information.

    56 | 57 | 58 | -------------------------------------------------------------------------------- /lib/godmin/helpers/tables.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | module Helpers 3 | module Tables 4 | def column_header(attribute) 5 | if @resource_service.orderable_column?(attribute.to_s) 6 | direction = 7 | if params[:order].present? 8 | if params[:order].match(/\A#{attribute.to_s}_(asc|desc)\z/) 9 | $1 == "desc" ? "asc" : "desc" 10 | elsif params[:order].match(/\A\w+_(asc|desc)\z/) 11 | $1 12 | else 13 | "desc" 14 | end 15 | else 16 | "desc" 17 | end 18 | link_to @resource_class.human_attribute_name(attribute.to_s), url_for(params.to_unsafe_h.merge(order: "#{attribute}_#{direction}")) 19 | else 20 | @resource_class.human_attribute_name(attribute.to_s) 21 | end 22 | end 23 | 24 | # NOTE: 外层包裹的 capture 的方法,在参数是征信的时候,直接不显示, 25 | def column_value(resource, attribute) 26 | partial_override "#{controller_path}/columns/#{attribute}", resource: resource do 27 | column_value = resource.send(attribute) 28 | 29 | if column_value.is_a?(Date) || column_value.is_a?(Time) 30 | column_value = l(column_value, format: :long) 31 | end 32 | 33 | if column_value.is_a?(TrueClass) || column_value.is_a?(FalseClass) 34 | column_value = t(column_value.to_s) 35 | end 36 | 37 | column_value.to_s 38 | end 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/unit/resources/resource_service/batch_actions_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Godmin 4 | module ResourceService 5 | class BatchActionsTest < ActiveSupport::TestCase 6 | def setup 7 | @article_service = Fakes::ArticleService.new 8 | end 9 | 10 | def test_batch_action 11 | assert @article_service.batch_action(:unpublish, [:foo, :bar]) 12 | assert_equal [:foo, :bar], @article_service.called_methods[:batch_actions][:unpublish] 13 | end 14 | 15 | def test_batch_action_when_it_does_not_exist 16 | assert_not @article_service.batch_action(:foobar, [:foo, :bar]) 17 | assert_nil @article_service.called_methods[:batch_actions][:foobar] 18 | end 19 | 20 | def test_batch_action_exists 21 | assert @article_service.batch_action?(:unpublish) 22 | end 23 | 24 | def test_batch_action_does_not_exist 25 | assert_not @article_service.batch_action?(:foobar) 26 | end 27 | 28 | def test_batch_action_map_with_default_options 29 | expected_batch_action_map = { only: nil, except: nil, confirm: false } 30 | assert_equal expected_batch_action_map, @article_service.batch_action_map[:unpublish] 31 | end 32 | 33 | def test_batch_action_map_with_custom_options 34 | expected_batch_action_map = { only: [:unpublished], except: [:published], confirm: true } 35 | assert_equal expected_batch_action_map, @article_service.batch_action_map[:publish] 36 | end 37 | end 38 | end 39 | end 40 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | godmin: 3 | title: Godmin 4 | batch_actions: 5 | buttons: 6 | select_all: Select all 7 | deselect_all: Deselect all 8 | confirm_message: Are you sure? 9 | filters: 10 | select: 11 | placeholder: 12 | one: Select something 13 | many: Select something 14 | buttons: 15 | apply: Filter 16 | clear: Clear filter 17 | actions: 18 | label: Actions 19 | show: Show 20 | show_title: Show %{resource} 21 | edit: Edit 22 | edit_title: Edit %{resource} 23 | destroy: Destroy 24 | destroy_title: Destroy %{resource} 25 | confirm_message: Are you sure? 26 | export: Export 27 | export_as: As 28 | associations: 29 | label: Nested resources 30 | sessions: 31 | sign_in: Sign in 32 | sign_out: Sign out 33 | signed_in: Signed in 34 | signed_out: Signed out 35 | failed_sign_in: Invalid credentials 36 | flash: 37 | create: "%{resource} was successfully created" 38 | update: "%{resource} was successfully updated" 39 | destroy: "%{resource} was successfully destroyed" 40 | batch_action: "%{number_of_records} %{resource} were affected" 41 | pagination: 42 | first: "First" 43 | last: "Last" 44 | entries: 45 | zero: "No %{resource} found" 46 | other: "Showing %{count} out of %{total} %{resource}" 47 | datetimepickers: 48 | datepicker: ! "%d/%m/%Y" 49 | datetimepicker: ! "%d/%m/%Y %H:%M" 50 | -------------------------------------------------------------------------------- /lib/godmin/application_controller.rb: -------------------------------------------------------------------------------- 1 | require "godmin/helpers/application" 2 | require "godmin/helpers/forms" 3 | require "godmin/helpers/navigation" 4 | require "godmin/helpers/translations" 5 | 6 | module Godmin 7 | module ApplicationController 8 | extend ActiveSupport::Concern 9 | 10 | included do 11 | include Godmin::Helpers::Translations 12 | 13 | helper Godmin::Helpers::Application 14 | helper Godmin::Helpers::Forms 15 | helper Godmin::Helpers::Navigation 16 | helper Godmin::Helpers::Translations 17 | 18 | helper_method :authentication_enabled? 19 | helper_method :authorization_enabled? 20 | helper_method :engine_wrapper 21 | 22 | before_action :append_view_paths 23 | 24 | layout "godmin/application" 25 | end 26 | 27 | def welcome; end 28 | 29 | protected 30 | 31 | private 32 | 33 | def engine_wrapper 34 | EngineWrapper.new(self.class) 35 | end 36 | 37 | def append_view_paths 38 | append_view_path Godmin::Resolver.resolvers(controller_path, engine_wrapper) 39 | end 40 | 41 | def disable_authentication 42 | @_disable_authentication = true 43 | end 44 | 45 | def disable_authorization 46 | @_disable_authorization = true 47 | end 48 | 49 | def authentication_enabled? 50 | !@_disable_authentication && singleton_class.include?(Godmin::Authentication) 51 | end 52 | 53 | def authorization_enabled? 54 | !@_disable_authorization && singleton_class.include?(Godmin::Authorization) 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /lib/godmin/resources/resource_service/batch_actions.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | module Resources 3 | module ResourceService 4 | module BatchActions 5 | extend ActiveSupport::Concern 6 | 7 | delegate :batch_action_map, to: "self.class" 8 | 9 | def batch_action(action, records) 10 | if batch_action?(action) 11 | send("batch_action_#{action}", records) 12 | true 13 | else 14 | false 15 | end 16 | end 17 | 18 | def batch_action?(action) 19 | batch_action_map.key?(action.to_sym) 20 | end 21 | 22 | def include_batch_action?(action) 23 | options = batch_action_map[action.to_sym] 24 | 25 | (options[:only].nil? && options[:except].nil?) || 26 | (options[:only] && options[:only].include?(scope.to_sym)) || 27 | (options[:except] && !options[:except].include?(scope.to_sym)) 28 | end 29 | 30 | def include_batch_actions? 31 | batch_action_map.keys.any? do |action| 32 | include_batch_action?(action) 33 | end 34 | end 35 | 36 | module ClassMethods 37 | def batch_action_map 38 | @batch_action_map ||= {} 39 | end 40 | 41 | def batch_action(attr, options = {}) 42 | batch_action_map[attr] = { 43 | only: nil, 44 | except: nil, 45 | confirm: false 46 | }.merge(options) 47 | end 48 | end 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /lib/godmin/helpers/navigation.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | module Helpers 3 | module Navigation 4 | def navbar_item(resource, url = resource, show: nil, icon: nil, **options) 5 | show ||= lambda do 6 | resource.is_a?(String) ? true : policy(resource).index? 7 | end 8 | 9 | return unless show.call 10 | 11 | link_text = 12 | if block_given? 13 | capture do 14 | yield 15 | end 16 | else 17 | resource.respond_to?(:model_name) ? resource.model_name.human(count: :many) : resource 18 | end 19 | 20 | content_tag :li do 21 | link_to url, options do 22 | if icon.present? 23 | concat content_tag :span, "", class: "glyphicon glyphicon-#{icon}" 24 | concat " " 25 | end 26 | concat link_text 27 | end 28 | end 29 | end 30 | 31 | def navbar_dropdown(title) 32 | dropdown_toggle = link_to "#", class: "dropdown-toggle", data: { toggle: "dropdown" } do 33 | concat "#{title} " 34 | concat content_tag :span, "", class: "caret" 35 | end 36 | 37 | dropdown_menu = content_tag :ul, class: "dropdown-menu" do 38 | yield 39 | end 40 | 41 | content_tag :li, class: "dropdown" do 42 | concat dropdown_toggle 43 | concat dropdown_menu 44 | end 45 | end 46 | 47 | def navbar_divider 48 | content_tag :li, "", class: "divider" 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /config/locales/pt-BR.yml: -------------------------------------------------------------------------------- 1 | pt-BR: 2 | godmin: 3 | title: Godmin 4 | batch_actions: 5 | buttons: 6 | select_all: Selecionar todos 7 | deselect_all: Remover seleção 8 | confirm_message: Você tem certeza? 9 | filters: 10 | select: 11 | placeholder: 12 | one: Selecionar um 13 | many: Selecionar vários 14 | buttons: 15 | apply: Filtrar 16 | clear: Limpar filtro 17 | actions: 18 | label: Ações 19 | show: Exibir 20 | show_ttitle: Exibir %{resource} 21 | edit: Editar 22 | edit_title: Editar %{resource} 23 | destroy: Remover 24 | destroy_title: Remover %{resource} 25 | confirm_message: Você tem certeza? 26 | export: Exportar 27 | export_as: Como 28 | associations: 29 | label: Nested resources 30 | sessions: 31 | sign_in: Entrar 32 | sign_out: Sair 33 | signed_in: Conectado 34 | signed_out: Desconectado 35 | failed_sign_in: Credenciais inválidas 36 | flash: 37 | create: "%{resource} foi criado com sucesso" 38 | update: "%{resource} foi atualizado com sucesso" 39 | destroy: "%{resource} foi removido com sucesso" 40 | batch_action: "%{number_of_records} %{resource} foram modificados" 41 | pagination: 42 | first: "Primeiro" 43 | last: "Último" 44 | entries: 45 | zero: "Nenhum %{resource} encontrado" 46 | other: "Exibindo %{count} de %{total} %{resource}" 47 | datetimepickers: 48 | datepicker: ! "%d/%m/%Y" 49 | datetimepicker: ! "%d/%m/%Y %H:%M" 50 | -------------------------------------------------------------------------------- /test/dummy/public/422.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The change you wanted was rejected (422) 5 | 48 | 49 | 50 | 51 | 52 |
    53 |

    The change you wanted was rejected.

    54 |

    Maybe you tried to change something you didn't have access to.

    55 |
    56 |

    If you are the application owner check the logs for more information.

    57 | 58 | 59 | -------------------------------------------------------------------------------- /app/views/godmin/resource/_table.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | 3 | 6 | 7 | 8 | <% if @resource_service.include_batch_actions? %> 9 | 10 | <% end %> 11 | <% @resource_service.attrs_for_index.each do |attr| %> 12 | 15 | <% end %> 16 | 17 | 18 | 19 | 20 | <% @resources.each do |resource| %> 21 | "> 22 | <% if @resource_service.include_batch_actions? %> 23 | 27 | <% end %> 28 | <% @resource_service.attrs_for_index.each do |attr| %> 29 | 32 | <% end %> 33 | 36 | 37 | <% end %> 38 | 39 |
    4 | <%= @resource_class.model_name.human(count: 2) %> 5 |
    13 | <%= column_header attr %> 14 |
    24 | <%= check_box_tag "batch_action[items][#{resource.id}]", nil, nil, 25 | data: { behavior: "batch-actions-checkbox" } %> 26 | 30 | <%= column_value(resource, attr) %> 31 | 34 | <%= render partial: "#{controller_path}/columns/actions", locals: { resource: resource } %> 35 |
    40 |
    41 | -------------------------------------------------------------------------------- /test/dummy/public/404.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | The page you were looking for doesn't exist (404) 5 | 48 | 49 | 50 | 51 | 52 |
    53 |

    The page you were looking for doesn't exist.

    54 |

    You may have mistyped the address or the page may have moved.

    55 |
    56 |

    If you are the application owner check the logs for more information.

    57 | 58 | 59 | -------------------------------------------------------------------------------- /lib/godmin/resources/resource_service/scopes.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | module Resources 3 | module ResourceService 4 | module Scopes 5 | extend ActiveSupport::Concern 6 | 7 | delegate :scope_map, to: "self.class" 8 | 9 | def apply_scope(scope_param, resources) 10 | return resources if scope_map.empty? 11 | 12 | self.scope = scope_param 13 | 14 | if scope && scope_map.key?(scope.to_sym) 15 | send("scope_#{@scope}", resources) 16 | else 17 | fail NotImplementedError, "Scope #{@scope} not implemented" 18 | end 19 | end 20 | 21 | def scope=(scope) 22 | @scope = scope.blank? ? default_scope : scope 23 | end 24 | 25 | def scope 26 | @scope 27 | end 28 | 29 | def scoped_by?(name) 30 | @scope == name.to_s 31 | end 32 | 33 | def scope_count(scope) 34 | send("scope_#{scope}", resources_relation).count 35 | end 36 | 37 | protected 38 | 39 | def default_scope 40 | scope = scope_map.find -> { scope_map.first } do |_key, value| 41 | value[:default] == true 42 | end 43 | 44 | scope ? scope[0].to_s : nil 45 | end 46 | 47 | module ClassMethods 48 | def scope_map 49 | @scope_map ||= {} 50 | end 51 | 52 | def scope(attr, options = {}) 53 | scope_map[attr] = { 54 | default: false 55 | }.merge(options) 56 | end 57 | end 58 | end 59 | end 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /test/unit/resolver_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Godmin 4 | class ResolverTest < ActiveSupport::TestCase 5 | module Admin 6 | class Engine < Rails::Engine 7 | isolate_namespace Admin 8 | end 9 | 10 | class Controller < ActionController::Base; end 11 | end 12 | 13 | class Controller < ActionController::Base; end 14 | 15 | def setup 16 | @engine_wrapper_1 = EngineWrapper.new(Controller) 17 | @engine_wrapper_2 = EngineWrapper.new(Admin::Controller) 18 | end 19 | 20 | def test_engine_resolver_when_not_namespaced 21 | resolver = EngineResolver.new("articles", @engine_wrapper_1) 22 | 23 | assert_equal [ 24 | "resource" 25 | ], resolver.template_paths("articles") 26 | end 27 | 28 | def test_engine_resolver_when_namespaced 29 | resolver = EngineResolver.new("godmin/resolver_test/admin/articles", @engine_wrapper_2) 30 | 31 | assert_equal [ 32 | "godmin/resolver_test/admin/resource" 33 | ], resolver.template_paths("godmin/resolver_test/admin/articles") 34 | end 35 | 36 | def test_godmin_resolver_when_not_namespaced 37 | resolver = GodminResolver.new("articles", @engine_wrapper_1) 38 | 39 | assert_equal [ 40 | "articles", 41 | "resource" 42 | ], resolver.template_paths("articles") 43 | end 44 | 45 | def test_godmin_resolver_when_namespaced 46 | resolver = GodminResolver.new("godmin/resolver_test/admin/articles", @engine_wrapper_2) 47 | 48 | assert_equal [ 49 | "articles", 50 | "resource" 51 | ], resolver.template_paths("godmin/resolver_test/admin/articles") 52 | end 53 | end 54 | end 55 | -------------------------------------------------------------------------------- /app/views/godmin/resource/_breadcrumb_actions.html.erb: -------------------------------------------------------------------------------- 1 | 24 | 25 | <% if @resource_service.has_many_map.present? %> 26 | 41 | <% end %> 42 | -------------------------------------------------------------------------------- /lib/godmin/resources/resource_controller/batch_actions.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | module Resources 3 | module ResourceController 4 | module BatchActions 5 | extend ActiveSupport::Concern 6 | 7 | included do 8 | prepend_before_action :perform_batch_action, only: :update 9 | end 10 | 11 | protected 12 | 13 | def perform_batch_action 14 | return unless params[:batch_action].present? 15 | 16 | set_resource_service 17 | set_resource_class 18 | 19 | if authorization_enabled? 20 | authorize(batch_action_records, "batch_action_#{params[:batch_action]}?") 21 | end 22 | 23 | if @resource_service.batch_action(params[:batch_action], batch_action_records) 24 | flash[:notice] = translate_scoped( 25 | "flash.batch_action", number_of_records: batch_action_ids.length, 26 | resource: @resource_class.model_name.human(count: batch_action_ids.length) 27 | ) 28 | flash[:updated_ids] = batch_action_ids 29 | 30 | if respond_to?("redirect_after_batch_action_#{params[:batch_action]}", true) 31 | redirect_to send("redirect_after_batch_action_#{params[:batch_action]}") 32 | return 33 | end 34 | end 35 | 36 | redirect_back(fallback_location: root_path) 37 | end 38 | 39 | def batch_action_ids 40 | @_batch_action_ids ||= params[:id].split(",").map(&:to_i) 41 | end 42 | 43 | def batch_action_records 44 | @_batch_action_records ||= @resource_class.where(id: batch_action_ids) 45 | end 46 | end 47 | end 48 | end 49 | end 50 | -------------------------------------------------------------------------------- /test/integration/view_overriding/partial_overriding_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class PartialOverridingTest < ActionDispatch::IntegrationTest 4 | def test_default_partial 5 | visit new_article_path 6 | assert page.has_content? "Title" 7 | end 8 | 9 | def test_override_partial 10 | add_template "app/views/articles/_form.html.erb", "foo" 11 | visit new_article_path 12 | assert page.has_content? "foo" 13 | end 14 | 15 | def test_override_resource_partial 16 | add_template "app/views/resource/_form.html.erb", "foo" 17 | visit new_article_path 18 | assert page.has_content? "foo" 19 | end 20 | 21 | def test_override_partial_and_resource_partial 22 | add_template "app/views/articles/_form.html.erb", "foo" 23 | add_template "app/views/resource/_form.html.erb", "bar" 24 | visit new_article_path 25 | assert page.has_content? "foo" 26 | end 27 | 28 | def test_default_partial_in_engine 29 | visit admin.new_article_path 30 | assert page.has_content? "Title" 31 | end 32 | 33 | def test_override_partial_in_engine 34 | add_template "admin/app/views/admin/articles/_form.html.erb", "foo" 35 | visit admin.new_article_path 36 | assert page.has_content? "foo" 37 | end 38 | 39 | def test_override_resource_partial_in_engine 40 | add_template "admin/app/views/admin/resource/_form.html.erb", "foo" 41 | visit admin.new_article_path 42 | assert page.has_content? "foo" 43 | end 44 | 45 | def test_override_partial_and_resource_partial_in_engine 46 | add_template "admin/app/views/admin/articles/_form.html.erb", "foo" 47 | add_template "admin/app/views/admin/resource/_form.html.erb", "bar" 48 | visit admin.new_article_path 49 | assert page.has_content? "foo" 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | begin 2 | require "bundler/setup" 3 | rescue LoadError 4 | puts "You must `gem install bundler` and `bundle install` to run rake tasks" 5 | end 6 | 7 | require "rdoc/task" 8 | 9 | RDoc::Task.new(:rdoc) do |rdoc| 10 | rdoc.rdoc_dir = "rdoc" 11 | rdoc.title = "Godmin" 12 | rdoc.options << "--line-numbers" 13 | rdoc.rdoc_files.include("README.rdoc") 14 | rdoc.rdoc_files.include("lib/**/*.rb") 15 | end 16 | 17 | APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__) 18 | load "rails/tasks/engine.rake" 19 | 20 | Bundler::GemHelper.install_tasks 21 | 22 | require "rake/testtask" 23 | 24 | Rake::TestTask.new(:test) do |t| 25 | t.libs << "lib" 26 | t.libs << "test" 27 | t.pattern = "test/**/*_test.rb" 28 | t.warning = false 29 | t.verbose = false 30 | end 31 | 32 | task default: :test 33 | 34 | namespace :sandbox do 35 | desc "Generate the Sandbox app then push it to GitHub which deploys it to Heroku" 36 | task :deploy do 37 | message = "Generated from: https://github.com/varvet/godmin/commit/#{`git rev-parse HEAD`.strip}" 38 | template_path = File.expand_path("../template.rb", __FILE__) 39 | Bundler.with_clean_env do 40 | Dir.mktmpdir do |dir| 41 | Dir.chdir(dir) 42 | system("git clone https://github.com/varvet/godmin-sandbox.git") 43 | if $CHILD_STATUS.success? 44 | Dir.chdir("godmin-sandbox") 45 | system("rm -rf *") 46 | system("rails new . -d postgresql -m #{template_path} --without-engine --skip-spring") 47 | if $CHILD_STATUS.success? 48 | system("git add --all") 49 | system("git commit -m '#{message}'") 50 | system("git push origin master") 51 | end 52 | end 53 | end 54 | end 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /test/integration/view_overriding/template_overriding_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class TemplateOverridingTest < ActionDispatch::IntegrationTest 4 | def test_default_template 5 | visit articles_path 6 | assert page.has_content? "Create Article" 7 | end 8 | 9 | def test_override_template 10 | add_template "app/views/articles/index.html.erb", "foo" 11 | visit articles_path 12 | assert page.has_content? "foo" 13 | end 14 | 15 | def test_override_resource_template 16 | add_template "app/views/resource/index.html.erb", "foo" 17 | visit articles_path 18 | assert page.has_content? "foo" 19 | end 20 | 21 | def test_override_template_and_resource_template 22 | add_template "app/views/articles/index.html.erb", "foo" 23 | add_template "app/views/resource/index.html.erb", "bar" 24 | visit articles_path 25 | assert page.has_content? "foo" 26 | end 27 | 28 | def test_default_template_in_engine 29 | visit admin.articles_path 30 | assert page.has_content? "Create Article" 31 | end 32 | 33 | def test_override_template_in_engine 34 | add_template "admin/app/views/admin/articles/index.html.erb", "foo" 35 | visit admin.articles_path 36 | assert page.has_content? "foo" 37 | end 38 | 39 | def test_override_resource_template_in_engine 40 | add_template "admin/app/views/admin/resource/index.html.erb", "foo" 41 | visit admin.articles_path 42 | assert page.has_content? "foo" 43 | end 44 | 45 | def test_override_template_and_resource_template_in_engine 46 | add_template "admin/app/views/admin/articles/index.html.erb", "foo" 47 | add_template "admin/app/views/admin/resource/index.html.erb", "bar" 48 | visit admin.articles_path 49 | assert page.has_content? "foo" 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /test/dummy/app/services/article_service.rb: -------------------------------------------------------------------------------- 1 | class ArticleService 2 | include Godmin::Resources::ResourceService 3 | 4 | attrs_for_index :id, :title, :non_orderable_column, :admin_user, :published, :created_at 5 | attrs_for_show :id, :title, :body, :admin_user, :published 6 | attrs_for_form :title, :body, :admin_user, :published 7 | 8 | has_many :comments 9 | 10 | def order_by_admin_user(resources, direction) 11 | resources.joins(:admin_users).order("admin_users.email #{direction}") 12 | end 13 | 14 | scope :all 15 | scope :unpublished 16 | scope :published 17 | scope :no_batch_actions 18 | 19 | def scope_all(articles) 20 | articles 21 | end 22 | 23 | def scope_unpublished(articles) 24 | articles.where(published: false) 25 | end 26 | 27 | def scope_published(articles) 28 | articles.where(published: true) 29 | end 30 | 31 | def scope_no_batch_actions(articles) 32 | articles 33 | end 34 | 35 | filter :title 36 | filter :status, as: :select, collection: -> { [["Published", :published], ["Unpublished", :unpublished]] } 37 | 38 | def filter_title(articles, value) 39 | articles.where(title: value) 40 | end 41 | 42 | def filter_status(articles, value) 43 | articles.where(published: value == "published") 44 | end 45 | 46 | batch_action :publish, except: [:published, :no_batch_actions] 47 | batch_action :unpublish, except: [:unpublished, :no_batch_actions] 48 | batch_action :destroy, except: [:no_batch_actions] 49 | 50 | def batch_action_destroy(articles) 51 | articles.destroy_all 52 | end 53 | 54 | def batch_action_publish(articles) 55 | articles.update_all(published: true) 56 | end 57 | 58 | def batch_action_unpublish(articles) 59 | articles.update_all(published: false) 60 | end 61 | end 62 | -------------------------------------------------------------------------------- /lib/generators/godmin/install/install_generator.rb: -------------------------------------------------------------------------------- 1 | require "godmin/generators/base" 2 | 3 | class Godmin::InstallGenerator < Godmin::Generators::Base 4 | def create_routes 5 | inject_into_file "config/routes.rb", before: /^end/ do 6 | <<-END.strip_heredoc.indent(2) 7 | root to: "application#welcome" 8 | END 9 | end 10 | end 11 | 12 | def create_navigation 13 | create_file File.join("app/views", namespaced_path, "shared/_navigation.html.erb") 14 | end 15 | 16 | def modify_application_controller 17 | inject_into_file File.join("app/controllers", namespaced_path, "application_controller.rb"), after: "ActionController::Base\n" do 18 | <<-END.strip_heredoc.indent(namespace ? 4 : 2) 19 | include Godmin::ApplicationController 20 | END 21 | end 22 | end 23 | 24 | def modify_application_js 25 | application_js = File.join("app/assets/javascripts", namespaced_path, "application.js") 26 | 27 | inject_into_file application_js, before: "//= require_tree ." do 28 | <<-END.strip_heredoc 29 | //= require moment 30 | //= require moment/en-gb 31 | //= require godmin 32 | END 33 | end 34 | 35 | gsub_file application_js, /\/\/= require turbolinks\n/, "" 36 | end 37 | 38 | def modify_application_css 39 | inject_into_file File.join("app/assets/stylesheets", namespaced_path, "application.css"), before: " *= require_tree ." do 40 | " *= require godmin\n" 41 | end 42 | end 43 | 44 | def require_library_if_namespaced 45 | return unless namespaced? 46 | 47 | inject_into_file File.join("lib", namespaced_path) + ".rb", before: "require" do 48 | <<-END.strip_heredoc 49 | require "godmin" 50 | END 51 | end 52 | end 53 | 54 | def remove_layouts 55 | remove_dir "app/views/layouts" 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /godmin.gemspec: -------------------------------------------------------------------------------- 1 | $:.push File.expand_path("../lib", __FILE__) 2 | 3 | # Maintain your gem's version: 4 | require "godmin/version" 5 | 6 | # Describe your gem and declare its dependencies: 7 | Gem::Specification.new do |gem| 8 | gem.name = "godmin" 9 | gem.version = Godmin::VERSION 10 | gem.authors = ["Jens Ljungblad", "Linus Pettersson", "Varvet"] 11 | gem.email = ["info@varvet.se"] 12 | gem.homepage = "https://github.com/varvet/godmin" 13 | gem.summary = "Godmin is an admin framework for Rails 5+" 14 | gem.description = "Godmin is an admin framework for Rails 5+. Use it to build dedicated admin sections for your apps, or stand alone admin apps such as internal tools." 15 | gem.license = "MIT" 16 | 17 | gem.files = `git ls-files`.split($/) 18 | gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } 19 | gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) 20 | gem.require_paths = ["lib"] 21 | 22 | gem.add_dependency "bcrypt", [">= 3.0", "< 4.0"] 23 | gem.add_dependency "bootstrap_form", "~> 2.4" 24 | gem.add_dependency "bootstrap-sass", "~> 3.3" 25 | gem.add_dependency "csv_builder", "~> 2.1" 26 | gem.add_dependency "jquery-rails", [">= 4.0", "< 5.0"] 27 | gem.add_dependency "momentjs-rails", "~> 2.8" 28 | gem.add_dependency "pundit", [">= 1.1", "< 2.0"] 29 | gem.add_dependency "rails", [">= 5.0", "< 6.0"] 30 | gem.add_dependency "sass-rails", [">= 5.0", "< 6.0"] 31 | gem.add_dependency "selectize-rails", "~> 0.12" 32 | 33 | gem.add_development_dependency "appraisal" 34 | gem.add_development_dependency "capybara" 35 | gem.add_development_dependency "minitest-reporters" 36 | gem.add_development_dependency "minitest" 37 | gem.add_development_dependency "poltergeist" 38 | gem.add_development_dependency "pry" 39 | gem.add_development_dependency "sqlite3" 40 | end 41 | -------------------------------------------------------------------------------- /test/dummy/db/schema.rb: -------------------------------------------------------------------------------- 1 | # This file is auto-generated from the current state of the database. Instead 2 | # of editing this file, please use the migrations feature of Active Record to 3 | # incrementally modify your database, and then regenerate this schema definition. 4 | # 5 | # Note that this schema.rb definition is the authoritative source for your 6 | # database schema. If you need to create the application database on another 7 | # system, you should be using db:schema:load, not running all the migrations 8 | # from scratch. The latter is a flawed and unsustainable approach (the more migrations 9 | # you'll amass, the slower it'll run and the greater likelihood for issues). 10 | # 11 | # It's strongly recommended that you check this file into your version control system. 12 | 13 | ActiveRecord::Schema.define(version: 20170207081043) do 14 | 15 | create_table "admin_users", force: :cascade do |t| 16 | t.string "email" 17 | t.text "password_digest" 18 | t.datetime "created_at", null: false 19 | t.datetime "updated_at", null: false 20 | end 21 | 22 | create_table "another_admin_users", force: :cascade do |t| 23 | t.string "email" 24 | t.text "password_digest" 25 | t.datetime "created_at", null: false 26 | t.datetime "updated_at", null: false 27 | end 28 | 29 | create_table "articles", force: :cascade do |t| 30 | t.string "title" 31 | t.text "body" 32 | t.boolean "published", default: false 33 | t.integer "admin_user_id" 34 | t.datetime "created_at", null: false 35 | t.datetime "updated_at", null: false 36 | t.index ["admin_user_id"], name: "index_articles_on_admin_user_id" 37 | end 38 | 39 | create_table "comments", force: :cascade do |t| 40 | t.integer "article_id" 41 | t.string "title" 42 | t.text "body" 43 | t.index ["article_id"], name: "index_comments_on_article_id" 44 | end 45 | 46 | end 47 | -------------------------------------------------------------------------------- /test/unit/resources/resource_service/filters_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Godmin 4 | module ResourceService 5 | class FiltersTest < ActiveSupport::TestCase 6 | def setup 7 | @article_service = Fakes::ArticleService.new 8 | end 9 | 10 | def test_calls_one_filter 11 | @article_service.apply_filters({ title: "foobar" }, :resources) 12 | assert_equal [:resources, "foobar"], @article_service.called_methods[:filters][:title] 13 | end 14 | 15 | def test_calls_multiple_filters 16 | @article_service.apply_filters({ title: "foobar", country: "Sweden" }, :resources) 17 | assert_equal [:resources, "foobar"], @article_service.called_methods[:filters][:title] 18 | assert_equal [:resources, "Sweden"], @article_service.called_methods[:filters][:country] 19 | end 20 | 21 | def test_calls_filter_when_present_multiselect 22 | @article_service.apply_filters({ tags: ["Banana"] }, :resources) 23 | assert_equal [:resources, ["Banana"]], @article_service.called_methods[:filters][:tags] 24 | end 25 | 26 | def test_does_not_call_filter_when_empty_multiselect 27 | @article_service.apply_filters({ tags: [""] }, :resources) 28 | assert_nil @article_service.called_methods[:filters][:tags] 29 | end 30 | 31 | def test_filter_map_with_default_options 32 | expected_filter_map = { as: :string, option_text: "to_s", option_value: "id", collection: nil } 33 | assert_equal expected_filter_map, @article_service.filter_map[:title] 34 | end 35 | 36 | def test_filter_map_with_custom_options 37 | expected_filter_map = { as: :select, option_text: "to_s", option_value: "id", collection: %w(Sweden Canada) } 38 | assert_equal expected_filter_map, @article_service.filter_map[:country] 39 | end 40 | end 41 | end 42 | end 43 | -------------------------------------------------------------------------------- /test/integration/view_overriding/filter_overriding_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ColumnOverridingTest < ActionDispatch::IntegrationTest 4 | def test_default_filter 5 | visit articles_path 6 | assert find("#filters").has_content? "Title" 7 | end 8 | 9 | def test_override_filter 10 | add_template "app/views/articles/filters/_title.html.erb", "foo" 11 | visit articles_path 12 | assert find("#filters").has_content? "foo" 13 | end 14 | 15 | def test_override_resource_filter 16 | add_template "app/views/resource/filters/_title.html.erb", "foo" 17 | visit articles_path 18 | assert find("#filters").has_content? "foo" 19 | end 20 | 21 | def test_override_filter_and_resource_filter 22 | add_template "app/views/articles/filters/_title.html.erb", "foo" 23 | add_template "app/views/resource/filters/_title.html.erb", "bar" 24 | visit articles_path 25 | assert find("#filters").has_content? "foo" 26 | end 27 | 28 | def test_default_filter_in_engine 29 | visit admin.articles_path 30 | assert find("#filters").has_content? "Title" 31 | end 32 | 33 | def test_override_filter_in_engine 34 | add_template "admin/app/views/admin/articles/filters/_title.html.erb", "foo" 35 | visit admin.articles_path 36 | assert find("#filters").has_content? "foo" 37 | end 38 | 39 | def test_override_resource_filter_in_engine 40 | add_template "admin/app/views/admin/resource/filters/_title.html.erb", "foo" 41 | visit admin.articles_path 42 | assert find("#filters").has_content? "foo" 43 | end 44 | 45 | def test_override_filter_and_resource_filter_in_engine 46 | add_template "admin/app/views/admin/articles/filters/_title.html.erb", "foo" 47 | add_template "admin/app/views/admin/resource/filters/_title.html.erb", "bar" 48 | visit admin.articles_path 49 | assert find("#filters").has_content? "foo" 50 | end 51 | end 52 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | # Configure Rails Environment 2 | ENV["RAILS_ENV"] = "test" 3 | ENV["DISABLE_DATABASE_ENVIRONMENT_CHECK"] = "1" 4 | 5 | require File.expand_path("../dummy/config/environment.rb", __FILE__) 6 | require "rails/test_help" 7 | require "capybara/rails" 8 | require "capybara/poltergeist" 9 | require "minitest/reporters" 10 | require "pry" 11 | 12 | require File.expand_path("../fakes/article.rb", __FILE__) 13 | require File.expand_path("../fakes/article_service.rb", __FILE__) 14 | 15 | Minitest::Reporters.use! [Minitest::Reporters::DefaultReporter.new( 16 | color: true 17 | )] 18 | 19 | Rails.backtrace_cleaner.remove_silencers! 20 | 21 | # Load support files 22 | Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each { |f| require f } 23 | 24 | # Load fixtures from the engine 25 | if ActiveSupport::TestCase.method_defined?(:fixture_path=) 26 | ActiveSupport::TestCase.fixture_path = File.expand_path("../fixtures", __FILE__) 27 | end 28 | 29 | class ActiveRecord::Base 30 | mattr_accessor :shared_connection 31 | @@shared_connection = nil 32 | 33 | def self.connection 34 | @@shared_connection || retrieve_connection 35 | end 36 | end 37 | 38 | # Forces all threads to share the same connection. This works on 39 | # Capybara because it starts the web server in a thread. 40 | ActiveRecord::Base.shared_connection = ActiveRecord::Base.connection 41 | 42 | Capybara.javascript_driver = :poltergeist 43 | 44 | class ActionDispatch::IntegrationTest 45 | include Capybara::DSL 46 | 47 | def setup 48 | @template_paths = [] 49 | end 50 | 51 | def teardown 52 | @template_paths.each do |path| 53 | File.delete(path) 54 | end 55 | end 56 | 57 | private 58 | 59 | def add_template(path, content) 60 | template_path = File.expand_path("../dummy/#{path}", __FILE__) 61 | @template_paths << template_path 62 | File.write(template_path, content) 63 | end 64 | end 65 | -------------------------------------------------------------------------------- /app/assets/stylesheets/godmin/index.css.scss: -------------------------------------------------------------------------------- 1 | /* 2 | * This is a manifest file that'll be compiled into application.css, which will include all the files 3 | * listed below. 4 | * 5 | * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, 6 | * or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path. 7 | * 8 | * You're free to add application-wide styles to this file and they'll appear at the top of the 9 | * compiled file, but it's generally better to create a new file per style scope. 10 | * 11 | *= require_self 12 | */ 13 | @import "bootstrap-sprockets"; 14 | @import "bootstrap"; 15 | @import "bootstrap-datetimepicker"; 16 | @import "selectize"; 17 | @import "selectize.bootstrap3"; 18 | 19 | body { 20 | margin-bottom: 20px; 21 | } 22 | 23 | #login { 24 | margin-top: 20px; 25 | } 26 | 27 | #breadcrumb { 28 | .dropdown:before { 29 | content: " "; 30 | } 31 | } 32 | 33 | #scopes { 34 | margin-bottom: 20px; 35 | } 36 | 37 | #filters { 38 | .filter .control-label { 39 | display: block; 40 | } 41 | .filter .form-control { 42 | min-width: 170px; 43 | } 44 | .filter .form-control.selectize-control { 45 | height: 34px; 46 | } 47 | .filter .form-control.selectize-control.single .item { 48 | padding-right: 25px; 49 | } 50 | } 51 | 52 | #actions { 53 | @include clearfix(); 54 | margin-bottom: 20px; 55 | } 56 | 57 | #table { 58 | td { 59 | vertical-align: middle; 60 | } 61 | td .btn-group { 62 | display: flex; 63 | } 64 | } 65 | 66 | .pagination { 67 | margin: 0 0 10px 0; 68 | } 69 | 70 | .pagination-entries { 71 | line-height: 35px; 72 | } 73 | 74 | .highlight { 75 | -moz-animation: highlight 3s; 76 | -webkit-animation: highlight 3s; 77 | } 78 | 79 | @-moz-keyframes highlight { from { background: #fffbcc; } to { background: none; } } 80 | @-webkit-keyframes highlight { from { background: #fffbcc; } to { background: none; } } 81 | -------------------------------------------------------------------------------- /test/integration/crud_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class CrudTest < ActionDispatch::IntegrationTest 4 | def test_list_resources 5 | Article.create! title: "foo" 6 | Article.create! title: "bar" 7 | 8 | visit articles_path 9 | 10 | assert page.has_content? "foo" 11 | assert page.has_content? "bar" 12 | end 13 | 14 | def test_show_resource 15 | article = Article.create! title: "foo", body: "bar" 16 | 17 | visit articles_path 18 | within "[data-resource-id='#{article.id}']" do 19 | click_link "Show" 20 | end 21 | 22 | assert_equal article_path(article), current_path 23 | assert page.has_content? "Title foo" 24 | assert page.has_content? "Body bar" 25 | end 26 | 27 | def test_create_resource 28 | visit articles_path 29 | click_link "Create Article" 30 | fill_in "Title", with: "foo" 31 | fill_in "Body", with: "bar" 32 | click_button "Create Article" 33 | 34 | assert_equal article_path(Article.last), current_path 35 | assert page.has_content? "Title foo" 36 | assert page.has_content? "Body bar" 37 | end 38 | 39 | def test_update_resource 40 | article = Article.create! title: "foo", body: "bar" 41 | 42 | visit articles_path 43 | within "[data-resource-id='#{article.id}']" do 44 | click_link "Edit" 45 | end 46 | fill_in "Title", with: "baz" 47 | click_button "Update Article" 48 | 49 | assert_equal article_path(article), current_path 50 | assert page.has_content? "Title baz" 51 | assert page.has_content? "Body bar" 52 | end 53 | 54 | def test_destroy_resource 55 | article_1 = Article.create! title: "foo" 56 | article_2 = Article.create! title: "bar" 57 | 58 | visit articles_path 59 | within "[data-resource-id='#{article_1.id}']" do 60 | click_link "Destroy" 61 | end 62 | 63 | assert_equal articles_path, current_path 64 | assert page.has_no_content? "foo" 65 | assert page.has_content? "bar" 66 | end 67 | end 68 | -------------------------------------------------------------------------------- /test/unit/helpers/filters_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Godmin 4 | module Helpers 5 | class FiltersTest < ActionView::TestCase 6 | include BootstrapForm::Helper 7 | include Godmin::Helpers::Filters 8 | include Godmin::Helpers::Translations 9 | 10 | def test_filter_form_is_a_bootstrap_form_builder 11 | filter_form url: "/" do |f| 12 | assert f.is_a? BootstrapForm::FormBuilder 13 | end 14 | end 15 | 16 | def test_filter_field_as_string 17 | @output_buffer = filter_form url: "/" do |f| 18 | f.filter_field :foo, { as: :string }, value: "Foobar" 19 | end 20 | 21 | assert_select "label[for=foo]", 1, "No label was found" 22 | assert_select "input[type=text][name=?]", "filter[foo]", 1, "No text input found with name foo" do |element| 23 | assert_equal "Foo", element.attr("placeholder").text 24 | assert_equal "Foobar", element.attr("value").text 25 | end 26 | end 27 | 28 | def test_filter_field_as_select 29 | @output_buffer = filter_form url: "/" do |f| 30 | f.filter_field :foo, as: :select, collection: -> { %w(Foo Bar) } 31 | end 32 | 33 | assert_select "label[for=foo]", 1, "No label was found" 34 | assert_select "select[name=?]", "filter[foo]", 1, "No select found with name foo" do 35 | assert_select "option", 3 36 | end 37 | end 38 | 39 | def test_filter_field_as_multiselect 40 | @output_buffer = filter_form url: "/" do |f| 41 | f.filter_field :foo, as: :multiselect, collection: -> { %w(Foo Bar) } 42 | end 43 | 44 | assert_select "label[for=foo]", 1, "No label was found" 45 | assert_select "select[name=?]", "filter[foo][]", 1, "No select found with name foo" do 46 | assert_select "option", 3 47 | end 48 | assert_select "input[type=hidden][name=?]", "filter[foo][]", 0 49 | end 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/unit/resources/resource_service/scopes_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Godmin 4 | module ResourceService 5 | class ScopesTest < ActiveSupport::TestCase 6 | def setup 7 | @article_service = Fakes::ArticleService.new 8 | end 9 | 10 | def test_returns_resources_when_no_scopes_are_defined 11 | service_class = Class.new do 12 | include Godmin::Resources::ResourceService 13 | end 14 | 15 | service = service_class.new 16 | assert_equal :resources, service.apply_scope("", :resources) 17 | end 18 | 19 | def test_calls_default_scope 20 | @article_service.apply_scope("", :resources) 21 | assert_equal :resources, @article_service.called_methods[:scopes][:unpublished] 22 | end 23 | 24 | def test_calls_non_default_scope 25 | @article_service.apply_scope("published", :resources) 26 | assert_equal :resources, @article_service.called_methods[:scopes][:published] 27 | end 28 | 29 | def test_calls_unimplemented_scope 30 | assert_raises NotImplementedError do 31 | @article_service.apply_scope("trashed", :resources) 32 | end 33 | end 34 | 35 | def test_current_scope 36 | @article_service.apply_scope("", :resources) 37 | assert_equal "unpublished", @article_service.scope 38 | end 39 | 40 | def test_currently_scoped_by 41 | @article_service.apply_scope("", :resources) 42 | assert @article_service.scoped_by?("unpublished") 43 | assert_not @article_service.scoped_by?("published") 44 | end 45 | 46 | def test_scope_count 47 | assert_equal 2, @article_service.scope_count("unpublished") 48 | assert_equal 1, @article_service.scope_count("published") 49 | end 50 | 51 | def test_scope_map 52 | assert_equal({ default: true }, @article_service.scope_map[:unpublished]) 53 | assert_equal({ default: false }, @article_service.scope_map[:published]) 54 | end 55 | end 56 | end 57 | end 58 | -------------------------------------------------------------------------------- /test/fakes/article_service.rb: -------------------------------------------------------------------------------- 1 | module Fakes 2 | class ArticleService 3 | include Godmin::Resources::ResourceService 4 | 5 | attr_accessor :called_methods 6 | 7 | attrs_for_index :id, :title, :country 8 | attrs_for_show :title, :country 9 | attrs_for_form :id, :title, :country, :body 10 | attrs_for_export :id, :title 11 | 12 | scope :unpublished, default: true 13 | scope :published 14 | 15 | filter :title 16 | filter :country, as: :select, collection: %w[Sweden Canada] 17 | filter :tags, as: :multiselect, collection: %w[Apple Banana] 18 | 19 | batch_action :unpublish 20 | batch_action :publish, confirm: true, only: [:unpublished], except: [:published] 21 | 22 | def initialize(*) 23 | super 24 | @called_methods = { scopes: {}, filters: {}, batch_actions: {}, ordering: {} } 25 | end 26 | 27 | def resources_relation 28 | %i[foo bar baz] 29 | end 30 | 31 | def order_by_foobar(resources, direction) 32 | called_methods[:ordering][:by_foobar] = [resources, direction] 33 | resources 34 | end 35 | 36 | def scope_unpublished(resources) 37 | called_methods[:scopes][:unpublished] = resources 38 | resources.slice(1, 3) 39 | end 40 | 41 | def scope_published(resources) 42 | called_methods[:scopes][:published] = resources 43 | resources.slice(0, 1) 44 | end 45 | 46 | def filter_title(resources, value) 47 | called_methods[:filters][:title] = [resources, value] 48 | resources 49 | end 50 | 51 | def filter_country(resources, value) 52 | called_methods[:filters][:country] = [resources, value] 53 | resources 54 | end 55 | 56 | def filter_tags(resources, value) 57 | called_methods[:filters][:tags] = [resources, value] 58 | resources 59 | end 60 | 61 | def batch_action_unpublish(resources) 62 | called_methods[:batch_actions][:unpublish] = resources 63 | end 64 | 65 | def batch_action_publish(resources) 66 | called_methods[:batch_actions][:publish] = resources 67 | end 68 | end 69 | end 70 | -------------------------------------------------------------------------------- /test/integration/view_overriding/column_overriding_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class ColumnOverridingTest < ActionDispatch::IntegrationTest 4 | def test_default_column 5 | Article.create! title: "foo" 6 | visit articles_path 7 | assert find("#table").has_content? "foo" 8 | end 9 | 10 | def test_override_column 11 | Article.create! title: "foo" 12 | add_template "app/views/articles/columns/_title.html.erb", "bar" 13 | visit articles_path 14 | assert find("#table").has_content? "bar" 15 | end 16 | 17 | def test_override_resource_column 18 | Article.create! title: "foo" 19 | add_template "app/views/resource/columns/_title.html.erb", "bar" 20 | visit articles_path 21 | assert find("#table").has_content? "bar" 22 | end 23 | 24 | def test_override_column_and_resource_column 25 | Article.create! title: "foo" 26 | add_template "app/views/articles/columns/_title.html.erb", "bar" 27 | add_template "app/views/resource/columns/_title.html.erb", "baz" 28 | visit articles_path 29 | assert find("#table").has_content? "bar" 30 | end 31 | 32 | def test_default_column_in_engine 33 | Article.create! title: "foo" 34 | visit admin.articles_path 35 | assert find("#table").has_content? "foo" 36 | end 37 | 38 | def test_override_column_in_engine 39 | Article.create! title: "foo" 40 | add_template "admin/app/views/admin/articles/columns/_title.html.erb", "bar" 41 | visit admin.articles_path 42 | assert find("#table").has_content? "bar" 43 | end 44 | 45 | def test_override_resource_column_in_engine 46 | Article.create! title: "foo" 47 | add_template "admin/app/views/admin/resource/columns/_title.html.erb", "bar" 48 | visit admin.articles_path 49 | assert find("#table").has_content? "bar" 50 | end 51 | 52 | def test_override_column_and_resource_column_in_engine 53 | Article.create! title: "foo" 54 | add_template "admin/app/views/admin/articles/columns/_title.html.erb", "bar" 55 | add_template "admin/app/views/admin/resource/columns/_title.html.erb", "baz" 56 | visit admin.articles_path 57 | assert find("#table").has_content? "bar" 58 | end 59 | end 60 | -------------------------------------------------------------------------------- /test/dummy/config/environments/test.rb: -------------------------------------------------------------------------------- 1 | Dummy::Application.configure do 2 | # Settings specified here will take precedence over those in config/application.rb. 3 | 4 | # The test environment is used exclusively to run your application's 5 | # test suite. You never need to work with it otherwise. Remember that 6 | # your test database is "scratch space" for the test suite and is wiped 7 | # and recreated between test runs. Don't rely on the data there! 8 | config.cache_classes = true 9 | 10 | # Do not eager load code on boot. This avoids loading your whole application 11 | # just for the purpose of running a single test. If you are using a tool that 12 | # preloads Rails for running tests, you may have to set it to true. 13 | config.eager_load = false 14 | 15 | # Configure static asset server for tests with Cache-Control for performance. 16 | case Rails::VERSION::MAJOR 17 | when 4 18 | config.serve_static_files = true 19 | config.static_cache_control = "public, max-age=3600" 20 | when 5 21 | config.public_file_server.enabled = true 22 | config.public_file_server.headers = { "Cache-Control" => "public, max-age=3600" } 23 | end 24 | 25 | # Show full error reports and disable caching. 26 | config.consider_all_requests_local = true 27 | config.action_controller.perform_caching = false 28 | 29 | # Raise exceptions instead of rendering exception templates. 30 | config.action_dispatch.show_exceptions = false 31 | 32 | # Disable request forgery protection in test environment. 33 | config.action_controller.allow_forgery_protection = false 34 | 35 | # Tell Action Mailer not to deliver emails to the real world. 36 | # The :test delivery method accumulates sent emails in the 37 | # ActionMailer::Base.deliveries array. 38 | config.action_mailer.delivery_method = :test 39 | 40 | # Randomize the order test cases are executed. 41 | config.active_support.test_order = :random 42 | 43 | # Print deprecation notices to the stderr. 44 | config.active_support.deprecation = :stderr 45 | 46 | # Disable caching of templates so that our template override 47 | # tests work properly. 48 | config.action_view.cache_template_loading = false 49 | end 50 | -------------------------------------------------------------------------------- /test/unit/engine_wrapper_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Godmin 4 | class EngineWrapperTest < ActiveSupport::TestCase 5 | module Admin 6 | class Engine < Rails::Engine 7 | isolate_namespace Admin 8 | end 9 | 10 | class Controller < ActionController::Base; end 11 | end 12 | 13 | module Admins 14 | class Engine < Rails::Engine 15 | isolate_namespace Admins 16 | end 17 | 18 | class Controller < ActionController::Base; end 19 | end 20 | 21 | class Controller < ActionController::Base; end 22 | 23 | def test_default_namespace 24 | engine_wrapper = EngineWrapper.new(Controller) 25 | assert_nil engine_wrapper.namespace 26 | end 27 | 28 | def test_default_namespaced? 29 | engine_wrapper = EngineWrapper.new(Controller) 30 | assert_equal false, engine_wrapper.namespaced? 31 | end 32 | 33 | def test_default_namespaced_path 34 | engine_wrapper = EngineWrapper.new(Controller) 35 | assert_equal [], engine_wrapper.namespaced_path 36 | end 37 | 38 | def test_default_root 39 | engine_wrapper = EngineWrapper.new(Controller) 40 | assert_equal Rails.application.root, engine_wrapper.root 41 | end 42 | 43 | def test_engine_namespace 44 | engine_wrapper = EngineWrapper.new(Admin::Controller) 45 | assert_equal Admin, engine_wrapper.namespace 46 | end 47 | 48 | def test_engine_namespaced? 49 | engine_wrapper = EngineWrapper.new(Admin::Controller) 50 | assert_equal true, engine_wrapper.namespaced? 51 | end 52 | 53 | def test_engine_namespaced_path 54 | engine_wrapper = EngineWrapper.new(Admin::Controller) 55 | assert_equal ["godmin", "engine_wrapper_test", "admin"], engine_wrapper.namespaced_path 56 | end 57 | 58 | def test_plural_engine_namespaced_path 59 | engine_wrapper = EngineWrapper.new(Admins::Controller) 60 | assert_equal ["godmin", "engine_wrapper_test", "admins"], engine_wrapper.namespaced_path 61 | end 62 | 63 | def test_engine_root 64 | engine_wrapper = EngineWrapper.new(Admin::Controller) 65 | assert_equal Admin::Engine.root, engine_wrapper.root 66 | end 67 | end 68 | end 69 | -------------------------------------------------------------------------------- /test/integration/batch_actions_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | class BatchActionsTest < ActionDispatch::IntegrationTest 4 | def test_batch_action 5 | Capybara.current_driver = Capybara.javascript_driver 6 | 7 | Article.create! title: "foo" 8 | Article.create! title: "bar" 9 | 10 | visit articles_path 11 | 12 | all("[data-behavior~=batch-actions-checkbox]").each(&:click) 13 | within "#actions" do 14 | click_link "Destroy" 15 | end 16 | 17 | assert_equal 200, page.status_code 18 | assert page.has_no_content? "foo" 19 | assert page.has_no_content? "bar" 20 | 21 | Capybara.use_default_driver 22 | end 23 | 24 | def test_batch_action_redirect 25 | Capybara.current_driver = Capybara.javascript_driver 26 | 27 | Article.create! title: "foo" 28 | 29 | visit articles_path(scope: :unpublished) 30 | 31 | all("[data-behavior~=batch-actions-checkbox]").each(&:click) 32 | within "#actions" do 33 | click_link "Publish" 34 | end 35 | 36 | assert_equal articles_path(scope: :published), current_path_with_params 37 | assert_equal 200, page.status_code 38 | 39 | Capybara.use_default_driver 40 | end 41 | 42 | def test_batch_action_scopes 43 | Capybara.current_driver = Capybara.javascript_driver 44 | 45 | Article.create! title: "foo" 46 | 47 | visit articles_path(scope: :unpublished) 48 | 49 | all("[data-behavior~=batch-actions-checkbox]").each(&:click) 50 | within "#actions" do 51 | assert page.has_content? "Publish" 52 | assert page.has_no_content? "Unpublish" 53 | end 54 | 55 | Capybara.use_default_driver 56 | end 57 | 58 | def test_batch_action_scopes_when_no_batch_actions 59 | Capybara.current_driver = Capybara.javascript_driver 60 | 61 | Article.create! title: "foo" 62 | 63 | visit articles_path(scope: :no_batch_actions) 64 | 65 | assert page.has_no_content?("Select all") 66 | assert page.has_no_css?("[data-behavior~=batch-actions-checkbox]") 67 | 68 | Capybara.use_default_driver 69 | end 70 | 71 | private 72 | 73 | def current_path_with_params 74 | "#{current_uri.path}?#{current_uri.query}" 75 | end 76 | 77 | def current_uri 78 | URI.parse(page.current_url) 79 | end 80 | end 81 | -------------------------------------------------------------------------------- /app/views/godmin/resource/_pagination.html.erb: -------------------------------------------------------------------------------- 1 |
    2 | <% if @resource_service.paginator.total_pages > 1 %> 3 | 26 | <% end %> 27 |
    28 | 41 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Code of Conduct 2 | 3 | As contributors and maintainers of this project, and in the interest of 4 | fostering an open and welcoming community, we pledge to respect all people who 5 | contribute through reporting issues, posting feature requests, updating 6 | documentation, submitting pull requests or patches, and other activities. 7 | 8 | We are committed to making participation in this project a harassment-free 9 | experience for everyone, regardless of level of experience, gender, gender 10 | identity and expression, sexual orientation, disability, personal appearance, 11 | body size, race, ethnicity, age, religion, or nationality. 12 | 13 | Examples of unacceptable behavior by participants include: 14 | 15 | * The use of sexualized language or imagery 16 | * Personal attacks 17 | * Trolling or insulting/derogatory comments 18 | * Public or private harassment 19 | * Publishing other's private information, such as physical or electronic 20 | addresses, without explicit permission 21 | * Other unethical or unprofessional conduct 22 | 23 | Project maintainers have the right and responsibility to remove, edit, or 24 | reject comments, commits, code, wiki edits, issues, and other contributions 25 | that are not aligned to this Code of Conduct, or to ban temporarily or 26 | permanently any contributor for other behaviors that they deem inappropriate, 27 | threatening, offensive, or harmful. 28 | 29 | By adopting this Code of Conduct, project maintainers commit themselves to 30 | fairly and consistently applying these principles to every aspect of managing 31 | this project. Project maintainers who do not follow or enforce the Code of 32 | Conduct may be permanently removed from the project team. 33 | 34 | This code of conduct applies both within project spaces and in public spaces 35 | when an individual is representing the project or its community. 36 | 37 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 38 | reported by contacting a project maintainer at info@varvet.se. All 39 | complaints will be reviewed and investigated and will result in a response that 40 | is deemed necessary and appropriate to the circumstances. Maintainers are 41 | obligated to maintain confidentiality with regard to the reporter of an 42 | incident. 43 | 44 | 45 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 46 | version 1.3.0, available at 47 | [http://contributor-covenant.org/version/1/3/0/][version] 48 | 49 | [homepage]: http://contributor-covenant.org 50 | [version]: http://contributor-covenant.org/version/1/3/0/ 51 | -------------------------------------------------------------------------------- /app/assets/javascripts/godmin/batch-actions.js: -------------------------------------------------------------------------------- 1 | window.Godmin = window.Godmin || {}; 2 | 3 | Godmin.BatchActions = (function() { 4 | var $container; 5 | var $selectAll; 6 | var $selectNone; 7 | 8 | function initialize() { 9 | $container = $('[data-behavior~=batch-actions-container]'); 10 | $selectAll = $container.find('[data-behavior~=batch-actions-select-all]'); 11 | $selectNone = $container.find('[data-behavior~=batch-actions-select-none]'); 12 | 13 | initializeEvents(); 14 | initializeState(); 15 | } 16 | 17 | function initializeEvents() { 18 | $container.find('[data-behavior~=batch-actions-select]').on('click', toggleCheckboxes); 19 | $container.find('[data-behavior~=batch-actions-checkbox-container]').on('click', toggleCheckbox); 20 | $container.find('[data-behavior~=batch-actions-checkbox]').on('change', toggleActions); 21 | $(document).delegate('[data-behavior~=batch-actions-action-link]', 'mousedown', triggerAction); 22 | } 23 | 24 | function initializeState() {} 25 | 26 | function setSelectToAll() { 27 | $selectAll.removeClass('hidden'); 28 | $selectNone.addClass('hidden'); 29 | } 30 | 31 | function setSelectToNone() { 32 | $selectAll.addClass('hidden'); 33 | $selectNone.removeClass('hidden'); 34 | } 35 | 36 | function checkedCheckboxes() { 37 | return $container.find('[data-behavior~=batch-actions-checkbox]:checked').map(function() { 38 | return this.id.match(/\d+/); 39 | }).toArray().join(','); 40 | } 41 | 42 | function toggleCheckbox(e) { 43 | if (this == e.target) { 44 | $(this).find('[data-behavior~=batch-actions-checkbox]').click(); 45 | } 46 | } 47 | 48 | function toggleCheckboxes(e) { 49 | e.preventDefault(); 50 | 51 | if (checkedCheckboxes().length > 0) { 52 | $container.find('[data-behavior~=batch-actions-checkbox]').prop('checked', false).trigger('change'); 53 | setSelectToAll(); 54 | } else { 55 | $container.find('[data-behavior~=batch-actions-checkbox]').prop('checked', true).trigger('change'); 56 | setSelectToNone(); 57 | } 58 | } 59 | 60 | function toggleActions() { 61 | if (checkedCheckboxes().length) { 62 | $('[data-behavior~=batch-actions-action-link]').removeClass('hidden'); 63 | setSelectToNone(); 64 | } else { 65 | $('[data-behavior~=batch-actions-action-link]').addClass('hidden'); 66 | setSelectToAll(); 67 | } 68 | } 69 | 70 | function triggerAction() { 71 | $(this).attr('href', $(this).attr('href') + '/' + checkedCheckboxes() + '?batch_action=' + $(this).data('value')); 72 | } 73 | 74 | return { 75 | initialize: initialize 76 | }; 77 | })(); 78 | 79 | $(function() { 80 | Godmin.BatchActions.initialize(); 81 | }); 82 | -------------------------------------------------------------------------------- /lib/godmin/resolver.rb: -------------------------------------------------------------------------------- 1 | require "action_view" 2 | require "action_view/template/resolver" 3 | 4 | module Godmin 5 | class Resolver < ::ActionView::FileSystemResolver 6 | def self.resolvers(controller_path, engine_wrapper) 7 | [ 8 | EngineResolver.new(controller_path, engine_wrapper), 9 | GodminResolver.new(controller_path, engine_wrapper) 10 | ] 11 | end 12 | 13 | def initialize(path, controller_path, engine_wrapper) 14 | super(path) 15 | @controller_path = controller_path 16 | @engine_wrapper = engine_wrapper 17 | end 18 | 19 | def find_templates(name, prefix, *args) 20 | templates = [] 21 | 22 | template_paths(prefix).each do |path| 23 | if templates.present? 24 | break 25 | else 26 | templates = super(name, path, *args) 27 | end 28 | end 29 | 30 | templates 31 | end 32 | end 33 | 34 | # Matches templates such as: 35 | # 36 | # { name: index, prefix: articles } => app/views/resource/index 37 | # { name: form, prefix: articles } => app/views/resource/_form 38 | # { name: title, prefix: columns } => app/views/resource/columns/_title 39 | class EngineResolver < Resolver 40 | def initialize(controller_path, engine_wrapper) 41 | super(File.join(engine_wrapper.root, "app/views"), controller_path, engine_wrapper) 42 | end 43 | 44 | def template_paths(prefix) 45 | [ 46 | resource_path_for_engine(prefix) 47 | ] 48 | end 49 | 50 | def resource_path_for_engine(prefix) 51 | prefix.sub(/\A#{@controller_path}/, File.join(@engine_wrapper.namespaced_path, "resource")).sub(/\A\//, "") 52 | end 53 | end 54 | 55 | # Matches templates such as: 56 | # 57 | # { name: index, prefix: articles } => godmin/app/views/godmin/resource/index 58 | # { name: form, prefix: articles } => godmin/app/views/godmin/resource/_form 59 | # { name: welcome, prefix: application } => godmin/app/views/godmin/application/welcome 60 | # { name: navigation, prefix: shared } => godmin/app/views/godmin/shared/navigation 61 | class GodminResolver < Resolver 62 | def initialize(controller_path, engine_wrapper) 63 | super(File.join(Godmin::Engine.root, "app/views/godmin"), controller_path, engine_wrapper) 64 | end 65 | 66 | def template_paths(prefix) 67 | [ 68 | default_path_for_godmin(prefix), 69 | resource_path_for_godmin(prefix) 70 | ] 71 | end 72 | 73 | def default_path_for_godmin(prefix) 74 | prefix.sub(/\A#{File.join(@engine_wrapper.namespaced_path)}/, "").sub(/\A\//, "") 75 | end 76 | 77 | def resource_path_for_godmin(prefix) 78 | prefix.sub(/\A#{@controller_path}/, "resource").sub(/\A\//, "") 79 | end 80 | end 81 | end 82 | -------------------------------------------------------------------------------- /lib/godmin/helpers/forms.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | module Helpers 3 | module Forms 4 | def form_for(record, options = {}, &block) 5 | super(record, { 6 | url: [*@resource_parents, record], 7 | builder: FormBuilders::FormBuilder, 8 | inline_errors: false 9 | }.merge(options), &block) 10 | end 11 | end 12 | end 13 | 14 | module FormBuilders 15 | class FormBuilder < BootstrapForm::FormBuilder 16 | def input(attribute, options = {}) 17 | case attribute_type(attribute) 18 | when :text 19 | text_area(attribute, options) 20 | when :boolean 21 | check_box(attribute, options) 22 | when :date 23 | date_field(attribute, options) 24 | when :datetime 25 | datetime_field(attribute, options) 26 | else 27 | text_field(attribute, options) 28 | end 29 | end 30 | 31 | def association(attribute, options = {}, html_options = {}) 32 | case association_type(attribute) 33 | when :belongs_to 34 | select("#{attribute}_id", association_collection_for_select(attribute), options, html_options.deep_merge( 35 | data: { behavior: "select-box" } 36 | )) 37 | else 38 | input(attribute, options) 39 | end 40 | end 41 | 42 | def date_field(attribute, options = {}) 43 | text_field(attribute, options.deep_merge( 44 | value: datetime_value(attribute, options, :datepicker), 45 | data: { behavior: "datepicker" } 46 | )) 47 | end 48 | 49 | def datetime_field(attribute, options = {}) 50 | text_field(attribute, options.deep_merge( 51 | value: datetime_value(attribute, options, :datetimepicker), 52 | data: { behavior: "datetimepicker" } 53 | )) 54 | end 55 | 56 | private 57 | 58 | def attribute_type(attribute) 59 | if @object.has_attribute?(attribute) 60 | @object.column_for_attribute(attribute).type 61 | end 62 | end 63 | 64 | def association_type(attribute) 65 | association_reflection(attribute).try(:macro) 66 | end 67 | 68 | def association_collection(attribute) 69 | association_reflection(attribute).try(:klass).try(:all) 70 | end 71 | 72 | def association_reflection(attribute) 73 | @object.class.reflect_on_association(attribute) 74 | end 75 | 76 | def association_collection_for_select(attribute) 77 | association_collection(attribute).map { |a| [a.to_s, a.id] } 78 | end 79 | 80 | def datetime_value(attribute, options, format) 81 | value = options[:value] || @object.send(attribute) 82 | value.try(:strftime, @template.translate_scoped("datetimepickers.#{format}")) 83 | end 84 | end 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /test/unit/paginator_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | module Godmin 4 | class PaginatorTest < ActiveSupport::TestCase 5 | def setup 6 | resources_class = Class.new do 7 | attr_reader :limit_param 8 | attr_reader :offset_param 9 | 10 | def count 11 | 50 12 | end 13 | 14 | def limit(limit_param) 15 | @limit_param = limit_param 16 | self 17 | end 18 | 19 | def offset(offset_param) 20 | @offset_param = offset_param 21 | self 22 | end 23 | end 24 | 25 | @resources = resources_class.new 26 | end 27 | 28 | def test_paginate 29 | paginator = Paginator.new(@resources, per_page: 10, current_page: 1) 30 | 31 | assert_equal @resources, paginator.paginate 32 | assert_equal 10, @resources.limit_param 33 | assert_equal 0, @resources.offset_param 34 | end 35 | 36 | def test_paginate_with_offset 37 | paginator = Paginator.new(@resources, per_page: 10, current_page: 2) 38 | 39 | assert_equal @resources, paginator.paginate 40 | assert_equal 10, @resources.limit_param 41 | assert_equal 10, @resources.offset_param 42 | end 43 | 44 | def test_current_page 45 | paginator = Paginator.new(nil, current_page: 2) 46 | assert_equal 2, paginator.current_page 47 | end 48 | 49 | def test_current_page_when_no_page 50 | paginator = Paginator.new(nil) 51 | assert_equal 1, paginator.current_page 52 | end 53 | 54 | def test_pages_when_pages_all_fit 55 | paginator = Paginator.new(@resources, per_page: 10, current_page: 1) 56 | assert_equal [1, 2, 3, 4, 5], paginator.pages 57 | end 58 | 59 | def test_pages_when_pages_dont_fit_and_on_first_page 60 | paginator = Paginator.new(@resources, per_page: 2, current_page: 1) 61 | assert_equal [1, 2, 3, 4, 5, 6, 7], paginator.pages 62 | end 63 | 64 | def test_pages_when_pages_dont_fit_and_on_middle_page 65 | paginator = Paginator.new(@resources, per_page: 2, current_page: 7) 66 | assert_equal [4, 5, 6, 7, 8, 9, 10], paginator.pages 67 | end 68 | 69 | def test_pages_when_pages_dont_fit_and_on_last_page 70 | paginator = Paginator.new(@resources, per_page: 2, current_page: 25) 71 | assert_equal [19, 20, 21, 22, 23, 24, 25], paginator.pages 72 | end 73 | 74 | def test_total_pages 75 | paginator = Paginator.new(@resources, per_page: 10) 76 | assert_equal 5, paginator.total_pages 77 | end 78 | 79 | def test_total_resources 80 | paginator = Paginator.new(@resources) 81 | assert_equal 50, paginator.total_resources 82 | end 83 | 84 | def test_total_resource_with_grouped_count 85 | resources_class = Class.new do 86 | def count 87 | { 1 => 1, 2 => 2 } 88 | end 89 | end 90 | 91 | paginator = Paginator.new(resources_class.new) 92 | assert_equal 2, paginator.total_resources 93 | end 94 | end 95 | end 96 | -------------------------------------------------------------------------------- /lib/godmin/resources/resource_service.rb: -------------------------------------------------------------------------------- 1 | require "godmin/resources/resource_service/associations" 2 | require "godmin/resources/resource_service/batch_actions" 3 | require "godmin/resources/resource_service/filters" 4 | require "godmin/resources/resource_service/ordering" 5 | require "godmin/resources/resource_service/pagination" 6 | require "godmin/resources/resource_service/scopes" 7 | 8 | module Godmin 9 | module Resources 10 | module ResourceService 11 | extend ActiveSupport::Concern 12 | 13 | include Associations 14 | include BatchActions 15 | include Filters 16 | include Ordering 17 | include Pagination 18 | include Scopes 19 | 20 | attr_reader :options 21 | 22 | def initialize(options = {}) 23 | @options = options 24 | end 25 | 26 | def resource_class 27 | self.class.name.chomp("Service").constantize 28 | end 29 | 30 | def resources_relation 31 | if options[:resource_parent].present? 32 | resource_class.where(options[:resource_parent].class.name.underscore => options[:resource_parent]) 33 | else 34 | resource_class.all 35 | end 36 | end 37 | 38 | def resources(params) 39 | apply_pagination( 40 | params[:page], apply_order( 41 | params[:order], apply_filters( 42 | params[:filter], apply_scope( 43 | params[:scope], resources_relation 44 | ) 45 | ) 46 | ) 47 | ) 48 | end 49 | 50 | def find_resource(id) 51 | resources_relation.find(id) 52 | end 53 | 54 | def build_resource(params) 55 | resources_relation.new(params) 56 | end 57 | 58 | def create_resource(resource) 59 | resource.save 60 | end 61 | 62 | def update_resource(resource, params) 63 | resource.update(params) 64 | end 65 | 66 | def destroy_resource(resource) 67 | resource.destroy 68 | end 69 | 70 | def attrs_for_index 71 | self.class.attrs_for_index 72 | end 73 | 74 | def attrs_for_show 75 | self.class.attrs_for_show 76 | end 77 | 78 | def attrs_for_form 79 | self.class.attrs_for_form 80 | end 81 | 82 | def attrs_for_export 83 | self.class.attrs_for_export 84 | end 85 | 86 | module ClassMethods 87 | def attrs_for_index(*attrs) 88 | @attrs_for_index = attrs if attrs.present? 89 | @attrs_for_index || [] 90 | end 91 | 92 | def attrs_for_show(*attrs) 93 | @attrs_for_show = attrs if attrs.present? 94 | @attrs_for_show || [] 95 | end 96 | 97 | def attrs_for_form(*attrs) 98 | @attrs_for_form = attrs if attrs.present? 99 | @attrs_for_form || [] 100 | end 101 | 102 | def attrs_for_export(*attrs) 103 | @attrs_for_export = attrs if attrs.present? 104 | @attrs_for_export || [] 105 | end 106 | end 107 | end 108 | end 109 | end 110 | -------------------------------------------------------------------------------- /test/generators/resource_generator_test.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | require "generators/godmin/resource/resource_generator" 3 | 4 | module Godmin 5 | class ResourceGeneratorTest < ::Rails::Generators::TestCase 6 | tests ResourceGenerator 7 | destination File.expand_path("../../tmp", __FILE__) 8 | setup :prepare_destination 9 | 10 | def test_resource_generator_in_standalone_install 11 | system "cd #{destination_root} && rails new . --skip-test --skip-spring --skip-bundle --skip-git --quiet" 12 | system "cd #{destination_root} && bin/rails generate godmin:install --quiet" 13 | system "cd #{destination_root} && bin/rails generate godmin:resource foo bar --quiet" 14 | 15 | assert_file "config/routes.rb", /resources :foos/ 16 | assert_file "app/views/shared/_navigation.html.erb", /<%= navbar_item Foo %>/ 17 | 18 | assert_file "app/controllers/foos_controller.rb" do |content| 19 | expected_content = <<-CONTENT.strip_heredoc 20 | class FoosController < ApplicationController 21 | include Godmin::Resources::ResourceController 22 | end 23 | CONTENT 24 | assert_match expected_content, content 25 | end 26 | 27 | assert_file "app/services/foo_service.rb" do |content| 28 | expected_content = <<-CONTENT.strip_heredoc 29 | class FooService 30 | include Godmin::Resources::ResourceService 31 | 32 | attrs_for_index :bar 33 | attrs_for_show :bar 34 | attrs_for_form :bar 35 | end 36 | CONTENT 37 | assert_match expected_content, content 38 | end 39 | end 40 | 41 | def test_resource_generator_in_engine_install 42 | system "cd #{destination_root} && rails new . --skip-test --skip-spring --skip-bundle --skip-git --quiet" 43 | system "cd #{destination_root} && bin/rails plugin new fakemin --mountable --quiet" 44 | system "cd #{destination_root} && fakemin/bin/rails generate godmin:install --quiet" 45 | system "cd #{destination_root} && fakemin/bin/rails generate godmin:resource foo bar --quiet" 46 | 47 | assert_file "fakemin/config/routes.rb", /resources :foos/ 48 | assert_file "fakemin/app/views/fakemin/shared/_navigation.html.erb", /<%= navbar_item Foo %>/ 49 | 50 | assert_file "fakemin/app/controllers/fakemin/foos_controller.rb" do |content| 51 | expected_content = <<-CONTENT.strip_heredoc 52 | module Fakemin 53 | class FoosController < ApplicationController 54 | include Godmin::Resources::ResourceController 55 | end 56 | end 57 | CONTENT 58 | assert_match expected_content, content 59 | end 60 | 61 | assert_file "fakemin/app/services/fakemin/foo_service.rb" do |content| 62 | expected_content = <<-CONTENT.strip_heredoc 63 | module Fakemin 64 | class FooService 65 | include Godmin::Resources::ResourceService 66 | 67 | attrs_for_index :bar 68 | attrs_for_show :bar 69 | attrs_for_form :bar 70 | end 71 | end 72 | CONTENT 73 | assert_match expected_content, content 74 | end 75 | end 76 | end 77 | end 78 | -------------------------------------------------------------------------------- /lib/godmin/helpers/filters.rb: -------------------------------------------------------------------------------- 1 | module Godmin 2 | module Helpers 3 | module Filters 4 | def filter_form(url: params.to_unsafe_h) 5 | bootstrap_form_tag url: url, method: :get, layout: :inline, builder: FormBuilders::FilterFormBuilder do |f| 6 | yield(f) 7 | end 8 | end 9 | end 10 | end 11 | 12 | module FormBuilders 13 | class FilterFormBuilder < BootstrapForm::FormBuilder 14 | # NOTE: use delete only userful in first time, because attribute has delete from object 15 | def filter_field(name, options, html_options = {}) 16 | html_option = options.slice :html 17 | html_options.merge!(html_option[:html]) if html_option.present? 18 | 19 | case options[:as] 20 | when :string 21 | string_filter_field(name, options, html_options) 22 | when :select 23 | select_filter_field(name, options, html_options) 24 | when :multiselect 25 | multiselect_filter_field(name, options, html_options) 26 | end 27 | end 28 | 29 | def string_filter_field(name, _options, html_options = {}) 30 | text_field( 31 | name, { 32 | name: "filter[#{name}]", 33 | label: @template.translate_scoped("filters.labels.#{name}", default: name.to_s.titleize), 34 | value: default_filter_value(name), 35 | placeholder: @template.translate_scoped("filters.labels.#{name}", default: name.to_s.titleize), 36 | wrapper_class: "filter" 37 | }.deep_merge(html_options) 38 | ) 39 | end 40 | 41 | def select_filter_field(name, options, html_options = {}) 42 | filter_select( 43 | name, options, { 44 | name: "filter[#{name}]", 45 | data: { 46 | placeholder: @template.translate_scoped("filters.select.placeholder.one") 47 | } 48 | }.deep_merge(html_options) 49 | ) 50 | end 51 | 52 | def multiselect_filter_field(name, options, html_options = {}) 53 | filter_select( 54 | name, { 55 | include_hidden: false 56 | }.deep_merge(options), { 57 | name: "filter[#{name}][]", 58 | multiple: true, 59 | data: { 60 | placeholder: @template.translate_scoped("filters.select.placeholder.many") 61 | } 62 | }.deep_merge(html_options) 63 | ) 64 | end 65 | 66 | def apply_filters_button 67 | submit @template.translate_scoped("filters.buttons.apply") 68 | end 69 | 70 | def clear_filters_button 71 | @template.link_to( 72 | @template.translate_scoped("filters.buttons.clear"), 73 | @template.url_for( 74 | @template.params.to_unsafe_h.slice(:scope, :order) 75 | ), 76 | class: "btn btn-default" 77 | ) 78 | end 79 | 80 | private 81 | 82 | def filter_select(name, options, html_options) 83 | unless options[:collection].is_a? Proc 84 | raise "A collection proc must be specified for select filters" 85 | end 86 | 87 | # We need to dup this here because we later delete some properties 88 | # from the hash. We should consider adding an additional options 89 | # param to separate filter params from select tag params. 90 | options = options.dup 91 | 92 | collection = options.delete(:collection).call 93 | 94 | choices = 95 | if collection.is_a? ActiveRecord::Relation 96 | @template.options_from_collection_for_select( 97 | collection, 98 | options.delete(:option_value), 99 | options.delete(:option_text), 100 | selected: default_filter_value(name) 101 | ) 102 | else 103 | @template.options_for_select( 104 | collection, 105 | selected: default_filter_value(name) 106 | ) 107 | end 108 | 109 | select( 110 | name, choices, { 111 | label: @template.translate_scoped("filters.labels.#{name}", default: name.to_s.titleize), 112 | include_hidden: true, 113 | include_blank: true 114 | }.deep_merge(options), { 115 | data: { behavior: "select-box" }, 116 | wrapper_class: "filter" 117 | }.deep_merge(html_options) 118 | ) 119 | end 120 | 121 | def default_filter_value(name) 122 | @template.params[:filter] ? @template.params[:filter][name] : nil 123 | end 124 | end 125 | end 126 | end 127 | -------------------------------------------------------------------------------- /lib/godmin/resources/resource_controller.rb: -------------------------------------------------------------------------------- 1 | require "godmin/helpers/batch_actions" 2 | require "godmin/helpers/filters" 3 | require "godmin/helpers/tables" 4 | require "godmin/resources/resource_controller/batch_actions" 5 | 6 | module Godmin 7 | module Resources 8 | module ResourceController 9 | extend ActiveSupport::Concern 10 | 11 | include BatchActions 12 | 13 | included do 14 | helper Godmin::Helpers::BatchActions 15 | helper Godmin::Helpers::Filters 16 | helper Godmin::Helpers::Tables 17 | 18 | before_action :set_resource_service 19 | before_action :set_resource_class 20 | before_action :set_resource_parents 21 | before_action :set_resources, only: :index 22 | before_action :set_resource, only: [:show, :new, :edit, :create, :update, :destroy] 23 | end 24 | 25 | def index 26 | respond_to do |format| 27 | format.html 28 | format.json 29 | format.csv 30 | end 31 | end 32 | 33 | def show 34 | respond_to do |format| 35 | format.html 36 | format.json 37 | end 38 | end 39 | 40 | def new; end 41 | 42 | def edit; end 43 | 44 | def create 45 | respond_to do |format| 46 | if @resource_service.create_resource(@resource) 47 | format.html { redirect_to redirect_after_create, notice: redirect_flash_message } 48 | format.json { render :show, status: :created, location: @resource } 49 | else 50 | format.html { render :edit } 51 | format.json { render json: @resource.errors, status: :unprocessable_entity } 52 | end 53 | end 54 | end 55 | 56 | def update 57 | respond_to do |format| 58 | if @resource_service.update_resource(@resource, resource_params) 59 | format.html { redirect_to redirect_after_update, notice: redirect_flash_message } 60 | format.json { render :show, status: :ok, location: @resource } 61 | else 62 | format.html { render :edit } 63 | format.json { render json: @resource.errors, status: :unprocessable_entity } 64 | end 65 | end 66 | end 67 | 68 | def destroy 69 | @resource_service.destroy_resource(@resource) 70 | 71 | respond_to do |format| 72 | format.html { redirect_to redirect_after_destroy, notice: redirect_flash_message } 73 | format.json { head :no_content } 74 | end 75 | end 76 | 77 | protected 78 | 79 | def set_resource_service 80 | @resource_service = resource_service 81 | end 82 | 83 | def set_resource_class 84 | @resource_class = resource_class 85 | end 86 | 87 | def set_resource_parents 88 | @resource_parents = resource_parents 89 | end 90 | 91 | def set_resources 92 | @resources = resources 93 | authorize(@resources) if authorization_enabled? 94 | end 95 | 96 | def set_resource 97 | @resource = resource 98 | authorize(@resource) if authorization_enabled? 99 | end 100 | 101 | def resource_service_class 102 | "#{controller_path.singularize}_service".classify.constantize 103 | end 104 | 105 | def resource_service 106 | resource_service = resource_service_class.new 107 | 108 | if authentication_enabled? 109 | resource_service.options[:admin_user] = admin_user 110 | end 111 | 112 | if resource_parents.present? 113 | resource_service.options[:resource_parent] = resource_parents.last 114 | end 115 | 116 | resource_service 117 | end 118 | 119 | def resource_class 120 | @resource_service.resource_class 121 | end 122 | 123 | def resource_parents 124 | params.to_unsafe_h.each_with_object([]) do |(name, value), parents| 125 | if name =~ /(.+)_id$/ 126 | parents << $1.classify.constantize.find(value) 127 | end 128 | end 129 | end 130 | 131 | def resources 132 | @resource_service.resources(params) 133 | end 134 | 135 | def resource 136 | if params[:id] 137 | @resource_service.find_resource(params[:id]) 138 | else 139 | case action_name 140 | when "create" 141 | @resource_service.build_resource(resource_params) 142 | when "new" 143 | @resource_service.build_resource(nil) 144 | end 145 | end 146 | end 147 | 148 | def resource_params 149 | params.require(@resource_class.model_name.param_key.to_sym).permit(resource_params_defaults) 150 | end 151 | 152 | def resource_params_defaults 153 | @resource_service.attrs_for_form.map do |attribute| 154 | association = @resource_class.reflect_on_association(attribute) 155 | 156 | if association && association.macro == :belongs_to 157 | association.foreign_key.to_sym 158 | else 159 | attribute 160 | end 161 | end 162 | end 163 | 164 | def redirect_after_create 165 | redirect_after_save 166 | end 167 | 168 | def redirect_after_update 169 | redirect_after_save 170 | end 171 | 172 | def redirect_after_save 173 | [:admin, *@resource_parents, @resource] 174 | end 175 | 176 | def redirect_after_destroy 177 | [:admin, *@resource_parents, resource_class.model_name.route_key.to_sym] 178 | end 179 | 180 | def redirect_flash_message 181 | translate_scoped("flash.#{action_name}", resource: @resource.class.model_name.human) 182 | end 183 | end 184 | end 185 | end 186 | -------------------------------------------------------------------------------- /vendor/assets/stylesheets/bootstrap-datetimepicker.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * Datetimepicker for Bootstrap v3 3 | //! version : 3.1.3 4 | * https://github.com/Eonasdan/bootstrap-datetimepicker/ 5 | */ 6 | .bootstrap-datetimepicker-widget { 7 | top: 0; 8 | left: 0; 9 | width: 250px; 10 | padding: 4px; 11 | margin-top: 1px; 12 | z-index: 99999 !important; 13 | border-radius: 4px; 14 | } 15 | .bootstrap-datetimepicker-widget.timepicker-sbs { 16 | width: 600px; 17 | } 18 | .bootstrap-datetimepicker-widget.bottom:before { 19 | content: ''; 20 | display: inline-block; 21 | border-left: 7px solid transparent; 22 | border-right: 7px solid transparent; 23 | border-bottom: 7px solid #ccc; 24 | border-bottom-color: rgba(0, 0, 0, 0.2); 25 | position: absolute; 26 | top: -7px; 27 | left: 7px; 28 | } 29 | .bootstrap-datetimepicker-widget.bottom:after { 30 | content: ''; 31 | display: inline-block; 32 | border-left: 6px solid transparent; 33 | border-right: 6px solid transparent; 34 | border-bottom: 6px solid white; 35 | position: absolute; 36 | top: -6px; 37 | left: 8px; 38 | } 39 | .bootstrap-datetimepicker-widget.top:before { 40 | content: ''; 41 | display: inline-block; 42 | border-left: 7px solid transparent; 43 | border-right: 7px solid transparent; 44 | border-top: 7px solid #ccc; 45 | border-top-color: rgba(0, 0, 0, 0.2); 46 | position: absolute; 47 | bottom: -7px; 48 | left: 6px; 49 | } 50 | .bootstrap-datetimepicker-widget.top:after { 51 | content: ''; 52 | display: inline-block; 53 | border-left: 6px solid transparent; 54 | border-right: 6px solid transparent; 55 | border-top: 6px solid white; 56 | position: absolute; 57 | bottom: -6px; 58 | left: 7px; 59 | } 60 | .bootstrap-datetimepicker-widget .dow { 61 | width: 14.2857%; 62 | } 63 | .bootstrap-datetimepicker-widget.pull-right:before { 64 | left: auto; 65 | right: 6px; 66 | } 67 | .bootstrap-datetimepicker-widget.pull-right:after { 68 | left: auto; 69 | right: 7px; 70 | } 71 | .bootstrap-datetimepicker-widget > ul { 72 | list-style-type: none; 73 | margin: 0; 74 | } 75 | .bootstrap-datetimepicker-widget a[data-action] { 76 | padding: 6px 0; 77 | } 78 | .bootstrap-datetimepicker-widget a[data-action]:active { 79 | box-shadow: none; 80 | } 81 | .bootstrap-datetimepicker-widget .timepicker-hour, 82 | .bootstrap-datetimepicker-widget .timepicker-minute, 83 | .bootstrap-datetimepicker-widget .timepicker-second { 84 | width: 54px; 85 | font-weight: bold; 86 | font-size: 1.2em; 87 | margin: 0; 88 | } 89 | .bootstrap-datetimepicker-widget button[data-action] { 90 | padding: 6px; 91 | } 92 | .bootstrap-datetimepicker-widget table[data-hour-format="12"] .separator { 93 | width: 4px; 94 | padding: 0; 95 | margin: 0; 96 | } 97 | .bootstrap-datetimepicker-widget .datepicker > div { 98 | display: none; 99 | } 100 | .bootstrap-datetimepicker-widget .picker-switch { 101 | text-align: center; 102 | } 103 | .bootstrap-datetimepicker-widget table { 104 | width: 100%; 105 | margin: 0; 106 | } 107 | .bootstrap-datetimepicker-widget td, 108 | .bootstrap-datetimepicker-widget th { 109 | text-align: center; 110 | border-radius: 4px; 111 | } 112 | .bootstrap-datetimepicker-widget td { 113 | height: 54px; 114 | line-height: 54px; 115 | width: 54px; 116 | } 117 | .bootstrap-datetimepicker-widget td.cw { 118 | font-size: 10px; 119 | height: 20px; 120 | line-height: 20px; 121 | color: #777777; 122 | } 123 | .bootstrap-datetimepicker-widget td.day { 124 | height: 20px; 125 | line-height: 20px; 126 | width: 20px; 127 | } 128 | .bootstrap-datetimepicker-widget td.day:hover, 129 | .bootstrap-datetimepicker-widget td.hour:hover, 130 | .bootstrap-datetimepicker-widget td.minute:hover, 131 | .bootstrap-datetimepicker-widget td.second:hover { 132 | background: #eeeeee; 133 | cursor: pointer; 134 | } 135 | .bootstrap-datetimepicker-widget td.old, 136 | .bootstrap-datetimepicker-widget td.new { 137 | color: #777777; 138 | } 139 | .bootstrap-datetimepicker-widget td.today { 140 | position: relative; 141 | } 142 | .bootstrap-datetimepicker-widget td.today:before { 143 | content: ''; 144 | display: inline-block; 145 | border-left: 7px solid transparent; 146 | border-bottom: 7px solid #428bca; 147 | border-top-color: rgba(0, 0, 0, 0.2); 148 | position: absolute; 149 | bottom: 4px; 150 | right: 4px; 151 | } 152 | .bootstrap-datetimepicker-widget td.active, 153 | .bootstrap-datetimepicker-widget td.active:hover { 154 | background-color: #428bca; 155 | color: #ffffff; 156 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 157 | } 158 | .bootstrap-datetimepicker-widget td.active.today:before { 159 | border-bottom-color: #fff; 160 | } 161 | .bootstrap-datetimepicker-widget td.disabled, 162 | .bootstrap-datetimepicker-widget td.disabled:hover { 163 | background: none; 164 | color: #777777; 165 | cursor: not-allowed; 166 | } 167 | .bootstrap-datetimepicker-widget td span { 168 | display: inline-block; 169 | width: 54px; 170 | height: 54px; 171 | line-height: 54px; 172 | margin: 2px 1.5px; 173 | cursor: pointer; 174 | border-radius: 4px; 175 | } 176 | .bootstrap-datetimepicker-widget td span:hover { 177 | background: #eeeeee; 178 | } 179 | .bootstrap-datetimepicker-widget td span.active { 180 | background-color: #428bca; 181 | color: #ffffff; 182 | text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25); 183 | } 184 | .bootstrap-datetimepicker-widget td span.old { 185 | color: #777777; 186 | } 187 | .bootstrap-datetimepicker-widget td span.disabled, 188 | .bootstrap-datetimepicker-widget td span.disabled:hover { 189 | background: none; 190 | color: #777777; 191 | cursor: not-allowed; 192 | } 193 | .bootstrap-datetimepicker-widget th { 194 | height: 20px; 195 | line-height: 20px; 196 | width: 20px; 197 | } 198 | .bootstrap-datetimepicker-widget th.picker-switch { 199 | width: 145px; 200 | } 201 | .bootstrap-datetimepicker-widget th.next, 202 | .bootstrap-datetimepicker-widget th.prev { 203 | font-size: 21px; 204 | } 205 | .bootstrap-datetimepicker-widget th.disabled, 206 | .bootstrap-datetimepicker-widget th.disabled:hover { 207 | background: none; 208 | color: #777777; 209 | cursor: not-allowed; 210 | } 211 | .bootstrap-datetimepicker-widget thead tr:first-child th { 212 | cursor: pointer; 213 | } 214 | .bootstrap-datetimepicker-widget thead tr:first-child th:hover { 215 | background: #eeeeee; 216 | } 217 | .input-group.date .input-group-addon span { 218 | display: block; 219 | cursor: pointer; 220 | width: 16px; 221 | height: 16px; 222 | } 223 | .bootstrap-datetimepicker-widget.left-oriented:before { 224 | left: auto; 225 | right: 6px; 226 | } 227 | .bootstrap-datetimepicker-widget.left-oriented:after { 228 | left: auto; 229 | right: 7px; 230 | } 231 | .bootstrap-datetimepicker-widget ul.list-unstyled li div.timepicker div.timepicker-picker table.table-condensed tbody > tr > td { 232 | padding: 0px !important; 233 | } 234 | @media screen and (max-width: 767px) { 235 | .bootstrap-datetimepicker-widget.timepicker-sbs { 236 | width: 283px; 237 | } 238 | } 239 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | ### 2.0.0 - Unreleased 4 | 5 | Features 6 | - Allow skipping authorization per action (https://github.com/varvet/godmin/pull/231) 7 | 8 | Bug fixes 9 | - Support namespaced models when generating resources (https://github.com/varvet/godmin/issues/181) 10 | 11 | Other 12 | - Drop support for Rails 4 (https://github.com/varvet/godmin/pull/239) 13 | - Better policy lookups for namespaced models (https://github.com/varvet/godmin/pull/180) 14 | - Use Pundit for authorization (https://github.com/varvet/godmin/pull/180) 15 | 16 | In order to upgrade 17 | - Upgrade to Rails 5 and Ruby 2.2.2 18 | - If using an admin engine, create a namespaced model for every resource, inheriting from the main app model 19 | - Replace any `authenticate_admin_user` with `authenticate` 20 | - Replace any `skip_before_action :authenticate_admin_user` with `prepend_before_action :disable_authentication` 21 | - Replace any `rescue_from NotAuthorizedError` with `rescue_from Pundit::NotAuthorizedError` 22 | 23 | ### 1.5.0 - 2017-02-17 24 | 25 | Features 26 | - Support for nested resources (https://github.com/varvet/godmin/pull/189) 27 | 28 | ### 1.4.0 - 2017-02-15 29 | 30 | Features 31 | - Support group queries in scopes and filters (https://github.com/varvet/godmin/pull/208) 32 | - Change color of remove buttons, so they're not grabbing all the attention (https://github.com/varvet/godmin/pull/212) 33 | 34 | Bug fixes 35 | - Fix permitted params in sessions controller to work with models other than `AdminUser` (https://github.com/varvet/godmin/pull/210) 36 | 37 | Other 38 | - Remove authentication alert (https://github.com/varvet/godmin/pull/207) 39 | - Add table caption for tests (https://github.com/varvet/godmin/pull/187) 40 | 41 | ### 1.3.1 - 2016-09-27 42 | 43 | Bug fixes 44 | - Fix FileSystemResolver issue (https://github.com/varvet/godmin/pull/202) 45 | 46 | Other 47 | - Update template for Rails 5 (https://github.com/varvet/godmin/commit/95e0a7917dd9767d77c3bfc876ebbf0a6036f347) 48 | 49 | ### 1.3.0 - 2016-07-11 50 | 51 | Features 52 | - Increased batch action checkbox click area (https://github.com/varvet/godmin/pull/183) 53 | - Adds titles to action links (https://github.com/varvet/godmin/pull/185) 54 | - Rails 5 support (https://github.com/varvet/godmin/pull/199) 55 | 56 | Bug fixes 57 | - Use translated title on login page (https://github.com/varvet/godmin/pull/195) 58 | - Hide batch action toggle when no batch action available (https://github.com/varvet/godmin/pull/197) 59 | - Remove hidden field for multiselect filters (https://github.com/varvet/godmin/pull/169) 60 | 61 | Other 62 | - Fixes a deprecation warning on Rails 4.2.5.1 (https://github.com/varvet/godmin/pull/188) 63 | - Adds caching partial overrides to increase table rendering speed (https://github.com/varvet/godmin/pull/184) 64 | 65 | ### 1.2.0 - 2016-02-02 66 | 67 | Features 68 | - Adds support for custom ordering of columns (https://github.com/varvet/godmin/pull/168) 69 | - Adds passing of options to association form helper (https://github.com/varvet/godmin/pull/172) 70 | - Adds passing of html options to association form helper (https://github.com/varvet/godmin/pull/176) 71 | 72 | Bug fixes 73 | - Fixes an issue with the template resolver and Rails 4.2.5.1 (https://github.com/varvet/godmin/pull/175) 74 | 75 | ### 1.1.0 - 2015-12-08 76 | 77 | Features 78 | - Adds locale for pt-BR (Brazilian Portuguese) (https://github.com/varvet/godmin/pull/141) 79 | - New sandbox template with with more examples (https://github.com/varvet/godmin/pull/135) 80 | - Permits belongs to association by default (https://github.com/varvet/godmin/pull/149) 81 | - Enables responsive design (https://github.com/varvet/godmin/pull/146) 82 | - Batch actions now receive a relation instead of an array (https://github.com/varvet/godmin/pull/158) 83 | 84 | Bug fixes 85 | - Fixes a bug that masked errors in templates with a template not found error (https://github.com/varvet/godmin/pull/142) 86 | - Fixes a namespace issue with the authentication generator (https://github.com/varvet/godmin/pull/150) 87 | 88 | ### 1.0.0 - 2015-11-13 89 | 90 | Release of 1.0.0 :tada: 91 | 92 | ### 0.12.4 - 2015-10-21 93 | 94 | Bug fixes 95 | - Fixes a bug which made it impossible to override the datetimepicker locale (https://github.com/varvet/godmin/issues/132) 96 | 97 | ### 0.12.3 - 2015-09-18 98 | 99 | Bug fixes 100 | - Adds support for plural engines (https://github.com/varvet/godmin/pull/128) 101 | - Remove turbolinks from application.js if present (https://github.com/varvet/godmin/issues/129) 102 | 103 | ### 0.12.2 - 2015-09-07 104 | 105 | Bug fixes 106 | - Fixes broken sign in page 107 | 108 | ### 0.12.1 - 2015-09-07 109 | 110 | Bug fixes 111 | - Fixes issue where column ordering on index table didn't work (https://github.com/varvet/godmin/issues/124) 112 | 113 | Other 114 | - Adds integration tests 115 | - Removes the namespace config in `initializers/godmin.rb` 116 | 117 | In order to upgrade 118 | - Remove the `initializers/godmin.rb` file 119 | 120 | ### 0.12.0 - 2015-06-30 121 | 122 | Features 123 | - Adds new navigation helpers for building a custom navbar (https://github.com/varvet/godmin/issues/54) 124 | 125 | Other 126 | - Removes the godmin router method 127 | 128 | In order to upgrade 129 | - Remove the `godmin do` block from the `config/routes.rb` file 130 | - Specify a root route if there is none already 131 | - Create a `shared/_navigation.html.erb` partial if there is none already 132 | 133 | Bug fixes 134 | - Fixes issue with authentication generator not modifying the application controller 135 | 136 | ### 0.11.2 - 2015-06-22 137 | 138 | Bug fixes 139 | - Fixes broken collection select helper 140 | 141 | ### 0.11.1 - 2015-05-20 142 | 143 | Features 144 | - Adds `destroy_resource` method to `ResourceService` 145 | - Adds query param to authorize 146 | - Adds authorization to batch actions (https://github.com/varvet/godmin/issues/33) 147 | - Adds show page (https://github.com/varvet/godmin/issues/77) 148 | - Adds option to change add text on dropdowns (https://github.com/varvet/godmin/pull/106) 149 | - Adds CSV export (https://github.com/varvet/godmin/issues/86) 150 | - JSON export can now be controlled using `attrs_for_export` or by overriding a jbuilder 151 | 152 | Bug fixes 153 | - Fixes a regression where filter labels were not translated 154 | 155 | ### 0.11.0 - 2015-04-13 156 | 157 | Other 158 | - Split resources into controllers and service objects (https://github.com/varvet/godmin/pull/79) 159 | - Renames the following modules: 160 | - Godmin::Application -> Godmin::ApplicationController 161 | - Godmin::Resource -> Godmin::Resources::ResourceController 162 | - Godmin::Sessions -> Godmin::SessionsController 163 | 164 | ### 0.10.3 - 2015-02-18 165 | 166 | Bug fixes 167 | - Adds the possibility to pass options to the `date_field` and `datetime_field` form helpers 168 | 169 | ### 0.10.2 - 2015-02-16 170 | 171 | Bug fixes 172 | - Fixes standard resource params for multi-word models 173 | 174 | ### 0.10.1 - 2015-02-13 175 | 176 | Bug fixes 177 | - Fixes multi-select selectize issue (https://github.com/varvet/godmin/issues/71) 178 | 179 | ### 0.10.0 - 2015-02-11 180 | 181 | Features 182 | - Shows the number of items in each scope in the scope tab (https://github.com/varvet/godmin/issues/16) 183 | - Two new overridable methods for resources: `build_resource` and `find_resource` 184 | - Translatable title (https://github.com/varvet/godmin/issues/17) 185 | 186 | Bug fixes 187 | - Fixes a bug where the wrong template would be picked (https://github.com/varvet/godmin/issues/39) 188 | - Fixes a bug so the resolver works with namespaces templates. 189 | - Fixes an autoloading issue (https://github.com/varvet/godmin/issues/60) 190 | - Godmin rescues `NotAuthorizedError` and returns a 403 Forbidden HTTP status. 191 | 192 | Other 193 | - Cleaned up generators (https://github.com/varvet/godmin/issues/28) 194 | - Restructured the locale files a bit 195 | 196 | ### 0.9.9 - 2015-01-23 197 | 198 | Features 199 | - Bump bootstrap to 3.3.3 200 | - Extracted button actions partial 201 | 202 | ### 0.9.8 - 2015-01-12 203 | 204 | Bug fixes 205 | - Created resources are now properly scoped by `resources_relation` 206 | - Fixes broken signin form 207 | 208 | ### 0.9.7 - 2015-01-07 209 | 210 | Features 211 | - Support for Rails 4.2 212 | - New form system (https://github.com/varvet/godmin/pull/50) 213 | 214 | ### 0.9.6 - 2014-12-18 215 | 216 | Features 217 | - Bundled [datetimepicker](https://github.com/Eonasdan/bootstrap-datetimepicker/) 218 | - Exposed JavaScript API 219 | 220 | Notes 221 | - You must now require godmin in application.js and application.css 222 | - You can no longer use the `select-tag` class to initialize a select box 223 | 224 | ### 0.9.5 - 2014-12-15 225 | 226 | Bug fixes 227 | - Fixes Godmin::FormBuilder issue 228 | 229 | ### 0.9.4 - 2014-12-15 230 | 231 | Features 232 | - Added Godmin::FormBuilder 233 | 234 | ### 0.9.3 - 2014-12-10 235 | 236 | Bug fixes 237 | - Pagination offset fix 238 | 239 | ### 0.9.2 - 2014-12-09 240 | 241 | Features 242 | - Replaces select2 with [selectize](http://brianreavis.github.io/selectize.js/) 243 | - Adds flash messages (https://github.com/varvet/godmin/issues/26) 244 | - Adds redirect hooks (https://github.com/varvet/godmin/issues/27) 245 | - Replaces kaminari 246 | 247 | Bug fixes 248 | - Form fallbacks to regular input instead of association. (https://github.com/varvet/godmin/issues/18) 249 | - Install generator adds `require "godmin"` if it is installed in an engine. 250 | - Fixes default permitted params to work with multiword models. 251 | 252 | ### 0.9.1 - 2014-11-18 253 | 254 | Bug fixes 255 | - Removed rails executable from /bin folder. 256 | 257 | ### 0.9.0 - 2014-11-17 258 | -------------------------------------------------------------------------------- /template.rb: -------------------------------------------------------------------------------- 1 | require "active_support/all" 2 | 3 | def install_standalone 4 | set_ruby_version 5 | 6 | gem "godmin", "1.5.0" 7 | 8 | after_bundle do 9 | create_database 10 | 11 | generate_models 12 | 13 | generate("godmin:install") 14 | generate("godmin:resource", "article") 15 | generate("godmin:resource", "author") 16 | 17 | modify_rakefile 18 | modify_routes 19 | modify_locales 20 | modify_models 21 | modify_author_service 22 | modify_article_controller 23 | modify_article_service 24 | 25 | migrate_and_seed 26 | end 27 | end 28 | 29 | def install_engine 30 | set_ruby_version 31 | 32 | run_ruby_script("bin/rails plugin new admin --mountable") 33 | 34 | gsub_file "admin/admin.gemspec", "TODO: ", "" 35 | gsub_file "admin/admin.gemspec", "TODO", "" 36 | 37 | inject_into_file "admin/admin.gemspec", before: /^end/ do 38 | <<-END.strip_heredoc.indent(2) 39 | s.add_dependency "godmin", "~> 1.5.0" 40 | END 41 | end 42 | 43 | gem "admin", path: "admin" 44 | 45 | after_bundle do 46 | create_database 47 | 48 | generate_models 49 | 50 | run_ruby_script("admin/bin/rails g godmin:install") 51 | run_ruby_script("admin/bin/rails g godmin:resource article") 52 | run_ruby_script("admin/bin/rails g godmin:resource author") 53 | 54 | inject_into_file "config/routes.rb", before: /^end/ do 55 | <<-END.strip_heredoc.indent(2) 56 | mount Admin::Engine, at: "admin" 57 | END 58 | end 59 | 60 | modify_rakefile 61 | modify_routes("admin") 62 | modify_locales 63 | modify_models 64 | modify_author_service("admin") 65 | modify_article_controller("admin") 66 | modify_article_service("admin") 67 | 68 | migrate_and_seed 69 | end 70 | end 71 | 72 | def set_ruby_version 73 | prepend_to_file "Gemfile" do 74 | "ruby '2.3.5'\n" 75 | end 76 | end 77 | 78 | def create_database 79 | rake("db:drop") 80 | rake("db:create") 81 | end 82 | 83 | def generate_models 84 | generate(:model, "author name:string") 85 | generate(:model, "article title:string body:text author:references published:boolean published_at:datetime") 86 | 87 | gsub_file Dir.glob("db/migrate/*_create_articles.rb").first, "t.boolean :published", "t.boolean :published, default: false" 88 | 89 | append_to_file "db/seeds.rb" do 90 | <<-END.strip_heredoc 91 | def title 92 | 5.times.map { lorem.sample }.join(" ").capitalize 93 | end 94 | 95 | def lorem 96 | "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.".gsub(/[.,]/, "").downcase.split(" ") 97 | end 98 | 99 | def author 100 | Author.all.sample 101 | end 102 | 103 | def published 104 | [true, true, false].sample 105 | end 106 | 107 | def published_at 108 | Time.now - [0, 1, 2, 3, 4, 5].sample.days 109 | end 110 | 111 | ["Lorem Ipsum", "Magna Aliqua", "Commodo Consequat"].each do |name| 112 | Author.create name: name 113 | end 114 | 115 | 35.times do |i| 116 | Article.create! title: title, author: author, published: published, published_at: published_at 117 | end 118 | END 119 | end 120 | end 121 | 122 | def modify_rakefile 123 | append_to_file "RakeFile" do 124 | <<-END.strip_heredoc 125 | 126 | namespace :sandbox do 127 | desc "Reseed the database" 128 | task reseed: :environment do 129 | Rake::Task["sandbox:reset"].invoke 130 | Rake::Task["db:environment:set"].invoke 131 | Rake::Task["db:schema:load"].invoke 132 | Rake::Task["db:seed"].invoke 133 | end 134 | 135 | desc "Reset the database" 136 | task reset: :environment do 137 | ActiveRecord::Base.connection.tables.each do |table| 138 | if table != "schema_migrations" 139 | query = "DROP TABLE IF EXISTS \#{table} CASCADE;" 140 | ActiveRecord::Base.connection.execute(query) 141 | end 142 | end 143 | end 144 | end 145 | END 146 | end 147 | end 148 | 149 | def modify_locales 150 | append_to_file "config/locales/en.yml" do 151 | <<-END.strip_heredoc.indent(2) 152 | 153 | activerecord: 154 | models: 155 | article: 156 | one: Article 157 | other: Articles 158 | author: 159 | one: Author 160 | other: Authors 161 | END 162 | end 163 | end 164 | 165 | def modify_routes(namespace = nil) 166 | routes_file = 167 | if namespace 168 | "admin/config/routes.rb" 169 | else 170 | "config/routes.rb" 171 | end 172 | 173 | gsub_file routes_file, "application#welcome", "articles#index" 174 | end 175 | 176 | def modify_models 177 | inject_into_file "app/models/article.rb", before: "end" do 178 | <<-END.strip_heredoc.indent(2) 179 | 180 | def to_s 181 | title 182 | end 183 | END 184 | end 185 | 186 | inject_into_file "app/models/author.rb", before: "end" do 187 | <<-END.strip_heredoc.indent(2) 188 | def to_s 189 | name 190 | end 191 | END 192 | end 193 | end 194 | 195 | def modify_article_controller(namespace = nil) 196 | articles_controller = 197 | if namespace 198 | "admin/app/controllers/admin/articles_controller.rb" 199 | else 200 | "app/controllers/articles_controller.rb" 201 | end 202 | 203 | inject_into_file articles_controller, after: "Godmin::Resources::ResourceController\n" do 204 | <<-END.strip_heredoc.indent(namespace ? 4 : 2) 205 | 206 | private 207 | 208 | def redirect_after_batch_action_unpublish 209 | articles_path(scope: :unpublished) 210 | end 211 | 212 | def redirect_after_batch_action_publish 213 | articles_path(scope: :published) 214 | end 215 | END 216 | end 217 | end 218 | 219 | def modify_article_service(namespace = nil) 220 | article_service = 221 | if namespace 222 | "admin/app/services/admin/article_service.rb" 223 | else 224 | "app/services/article_service.rb" 225 | end 226 | 227 | gsub_file article_service, "attrs_for_index", "attrs_for_index :title, :author, :published_at" 228 | gsub_file article_service, "attrs_for_show", "attrs_for_show :title, :body, :author, :published, :published_at" 229 | gsub_file article_service, "attrs_for_form", "attrs_for_form :title, :body, :author, :published, :published_at" 230 | 231 | inject_into_file article_service, after: "attrs_for_form :title, :body, :author, :published, :published_at \n" do 232 | <<-END.strip_heredoc.indent(namespace ? 4 : 2) 233 | attrs_for_export :id, :title, :author, :published, :published_at 234 | 235 | scope :unpublished 236 | scope :published 237 | 238 | def scope_unpublished(articles) 239 | articles.where(published: false) 240 | end 241 | 242 | def scope_published(articles) 243 | articles.where(published: true) 244 | end 245 | 246 | filter :title 247 | filter :author, as: :select, collection: -> { Author.all }, option_text: "name" 248 | 249 | def filter_title(articles, value) 250 | articles.where("title LIKE ?", "%\#{value}%") 251 | end 252 | 253 | def filter_author(articles, value) 254 | articles.where(author: value) 255 | end 256 | 257 | batch_action :unpublish, except: [:unpublished] 258 | batch_action :publish, except: [:published] 259 | batch_action :destroy, confirm: true 260 | 261 | def batch_action_unpublish(articles) 262 | articles.update_all(published: false) 263 | end 264 | 265 | def batch_action_publish(articles) 266 | articles.update_all(published: true) 267 | end 268 | 269 | def batch_action_destroy(articles) 270 | articles.destroy_all 271 | end 272 | 273 | def per_page 274 | 15 275 | end 276 | END 277 | end 278 | end 279 | 280 | def modify_author_service(namespace = nil) 281 | author_service = 282 | if namespace 283 | "admin/app/services/admin/author_service.rb" 284 | else 285 | "app/services/author_service.rb" 286 | end 287 | 288 | gsub_file author_service, "attrs_for_index", "attrs_for_index :name" 289 | gsub_file author_service, "attrs_for_show", "attrs_for_show :name" 290 | gsub_file author_service, "attrs_for_form", "attrs_for_form :name" 291 | 292 | inject_into_file author_service, after: "attrs_for_form :name \n" do 293 | <<-END.strip_heredoc.indent(namespace ? 4 : 2) 294 | attrs_for_export :id, :name 295 | 296 | filter :name 297 | 298 | def filter_name(authors, value) 299 | authors.where("name LIKE ?", "%\#{value}%") 300 | end 301 | 302 | batch_action :destroy, confirm: true 303 | 304 | def batch_action_destroy(authors) 305 | authors.each { |a| a.destroy } 306 | end 307 | END 308 | end 309 | end 310 | 311 | def migrate_and_seed 312 | rake("db:migrate") 313 | rake("db:seed") 314 | end 315 | 316 | with_engine = "--with-engine" 317 | without_engine = "--without-engine" 318 | 319 | if ARGV.count > (ARGV - [with_engine, without_engine]).count 320 | if ARGV.include? with_engine 321 | install_engine 322 | elsif ARGV.include? without_engine 323 | install_standalone 324 | end 325 | else 326 | if yes?("Place godmin in admin engine?") 327 | install_engine 328 | else 329 | install_standalone 330 | end 331 | end 332 | --------------------------------------------------------------------------------