├── 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 |
4 | <% errors.full_messages.each do |error| %>
5 | <%= error %>
6 | <% end %>
7 |
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 | <%= @resource_class.human_attribute_name(attr) %>
7 | <%= column_value(@resource, attr) %>
8 |
9 | <% end %>
10 |
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 |
4 | <% @resource_service.scope_map.each do |name, options| %>
5 | ">
6 | <%= link_to url_for(params.to_unsafe_h.merge(scope: name, only_path: true).except(:page)) do %>
7 | <%= translate_scoped("scopes.labels.#{name.to_s.underscore}", default: name.to_s.titleize) %>
8 | (<%= @resource_service.scope_count(name) %>)
9 | <% end %>
10 |
11 | <% end %>
12 |
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 |
4 | <%= translate_scoped("actions.export") %>
5 |
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 |
2 |
3 | <% if @resource_parents %>
4 | <% @resource_parents.each do |parent| %>
5 |
6 | <%= link_to parent.class.model_name.human(count: 2), [:admin, parent.class] %>
7 |
8 |
9 | <%= link_to parent.to_s, [:admin, parent] %>
10 |
11 | <% end %>
12 | <% end %>
13 | <% if action_name == "index" %>
14 |
15 | <%= @resource_class.model_name.human(count: 2) %>
16 |
17 | <% else %>
18 |
19 | <%= link_to @resource_class.model_name.human(count: 2), [:admin, *@resource_parents, @resource_class] %>
20 |
21 |
22 | <% if @resource.new_record? %>
23 | <%= t("helpers.submit.create", model: @resource_class.model_name.human) %>
24 | <% else %>
25 | <%= @resource.to_s %>
26 | <% end %>
27 |
28 | <% if @resource.persisted? %>
29 | <%= render partial: "breadcrumb_actions" %>
30 | <% end %>
31 | <% end %>
32 |
33 |
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 |
3 |
4 |
13 |
14 |
15 | <%= render partial: File.join(engine_wrapper.namespaced_path, "shared/navigation") %>
16 |
17 |
18 | <%= render partial: File.join(engine_wrapper.namespaced_path, "shared/navigation_aside") %>
19 |
20 |
21 |
22 |
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 |
4 | <%= @resource_class.model_name.human(count: 2) %>
5 |
6 |
7 |
8 | <% if @resource_service.include_batch_actions? %>
9 |
10 | <% end %>
11 | <% @resource_service.attrs_for_index.each do |attr| %>
12 |
13 | <%= column_header attr %>
14 |
15 | <% end %>
16 |
17 |
18 |
19 |
20 | <% @resources.each do |resource| %>
21 | ">
22 | <% if @resource_service.include_batch_actions? %>
23 |
24 | <%= check_box_tag "batch_action[items][#{resource.id}]", nil, nil,
25 | data: { behavior: "batch-actions-checkbox" } %>
26 |
27 | <% end %>
28 | <% @resource_service.attrs_for_index.each do |attr| %>
29 |
30 | <%= column_value(resource, attr) %>
31 |
32 | <% end %>
33 |
34 | <%= render partial: "#{controller_path}/columns/actions", locals: { resource: resource } %>
35 |
36 |
37 | <% end %>
38 |
39 |
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 |
2 |
3 | <%= translate_scoped("actions.label") %>
4 |
5 |
23 |
24 |
25 | <% if @resource_service.has_many_map.present? %>
26 |
27 |
28 | <%= translate_scoped("associations.label") %>
29 |
30 |
40 |
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 |
4 |
25 |
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 |
--------------------------------------------------------------------------------