Skip to content

Commit

Permalink
Nested resources (#189)
Browse files Browse the repository at this point in the history
  • Loading branch information
jensljungblad authored Feb 17, 2017
1 parent 337d1bf commit 17415f2
Show file tree
Hide file tree
Showing 26 changed files with 250 additions and 42 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
# Changelog

### Unreleased
Features
- Support for nested resources (https://github.com/varvet/godmin/pull/189)

### 1.4.0 - 2017-02-15
Features
- Support group queries in scopes and filters (https://github.com/varvet/godmin/pull/208)
Expand Down
29 changes: 27 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Godmin differs from tools like [ActiveAdmin](http://activeadmin.info/) and [Rail
- [Redirecting](#redirecting)
- [Pagination](#pagination)
- [Exporting](#exporting)
- [Nested resources](#nested-resources)
- [Views](#views)
- [Forms](#forms)
- [Navigation](#navigation)
Expand Down Expand Up @@ -510,7 +511,7 @@ If you wish to change the number of resources per page, you can override the `pe

```ruby
class ArticlesService
include Godmin::Resources::Service
include Godmin::Resources::ResourceService

def per_page
50
Expand All @@ -524,12 +525,36 @@ The `attrs_for_export` method in the service object makes it possible to mark at

```ruby
class ArticlesService
include Godmin::Resources::Service
include Godmin::Resources::ResourceService

attrs_for_export :id, :title, :created_at, :updated_at
end
```

### Nested resources

Nested resources can be implemented by nesting your routes:

```ruby
resources :blogs do
resources :blog_posts
end
```

This will set up scoping of the nested resource as well as correct links in the breadcrumb.

If you want to add a link to the nested resource from the parent's show and edit pages, you can add the following to the service object:

```ruby
class BlogService
include Godmin::Resources::ResourceService

has_many :blog_posts
end
```

Otherwise, simply add links as you see fit using partial overrides.

## Views

It's easy to override view templates and partials in Godmin, both globally and per resource. All you have to do is place a file with an identical name in your `app/views` directory. For instance, to override the `godmin/resource/index.html.erb` template for all resources, place a file under `app/views/resource/index.html.erb`. If you only wish to override it for articles, place it instead under `app/views/articles/index.html.erb`.
Expand Down
36 changes: 12 additions & 24 deletions app/views/godmin/resource/_breadcrumb.html.erb
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
<div id="breadcrumb">
<ol class="breadcrumb">
<% if @resource_parents %>
<% @resource_parents.each do |parent| %>
<li>
<%= link_to parent.class.model_name.human(count: 2), parent.class %>
</li>
<li>
<%= link_to parent.to_s, parent %>
</li>
<% end %>
<% end %>
<% if action_name == "index" %>
<li class="active">
<%= @resource_class.model_name.human(count: 2) %>
</li>
<% else %>
<li>
<%= link_to @resource_class.model_name.human(count: 2), @resource_class %>
<%= link_to @resource_class.model_name.human(count: 2), [*@resource_parents, @resource_class] %>
</li>
<li class="active">
<% if @resource.new_record? %>
Expand All @@ -16,29 +26,7 @@
<% end %>
</li>
<% if @resource.persisted? %>
<li class="dropdown pull-right">
<a href="#" data-toggle="dropdown" role="button">
<%= translate_scoped("actions.label") %> <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<% if policy(@resource).show? && action_name != "show" %>
<li>
<%= link_to translate_scoped("actions.show"), @resource %>
</li>
<% end %>
<% if policy(@resource).edit? && action_name != "edit" %>
<li>
<%= link_to translate_scoped("actions.edit"), [:edit, @resource] %>
</li>
<% end %>
<% if policy(@resource).destroy? %>
<li>
<%= link_to translate_scoped("actions.destroy"), @resource, method: :delete,
data: { confirm: translate_scoped("actions.confirm_message") } %>
</li>
<% end %>
</ul>
</li>
<%= render partial: "breadcrumb_actions" %>
<% end %>
<% end %>
</ol>
Expand Down
41 changes: 41 additions & 0 deletions app/views/godmin/resource/_breadcrumb_actions.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
<li class="dropdown pull-right">
<a href="#" data-toggle="dropdown">
<%= translate_scoped("actions.label") %> <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<% if policy(@resource).show? && action_name != "show" %>
<li>
<%= link_to translate_scoped("actions.show"), [*@resource_parents, @resource] %>
</li>
<% end %>
<% if policy(@resource).edit? && action_name != "edit" %>
<li>
<%= link_to translate_scoped("actions.edit"), [:edit, *@resource_parents, @resource] %>
</li>
<% end %>
<% if policy(@resource).destroy? %>
<li>
<%= link_to translate_scoped("actions.destroy"), [*@resource_parents, @resource], method: :delete,
data: { confirm: translate_scoped("actions.confirm_message") } %>
</li>
<% end %>
</ul>
</li>

<% if @resource_service.has_many_map.present? %>
<li class="dropdown pull-right">
<a href="#" data-toggle="dropdown">
<%= translate_scoped("associations.label") %> <span class="caret"></span>
</a>
<ul class="dropdown-menu">
<% @resource_service.has_many_map.each do |name, options| %>
<% if policy(options[:class_name].constantize).index? %>
<li>
<%= link_to(options[:class_name].constantize.model_name.human(count: 2),
send("#{@resource_class.name.underscore}_#{name}_path", @resource)) %>
</li>
<% end %>
<% end %>
</ul>
</li>
<% end %>
2 changes: 1 addition & 1 deletion app/views/godmin/resource/_button_actions.html.erb
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
<% if policy(@resource_service.build_resource({})).new? %>
<%= link_to t("helpers.submit.create", model: @resource_class.model_name.human), [:new, @resource_class.model_name.singular_route_key], class: "btn btn-default" %>
<%= link_to t("helpers.submit.create", model: @resource_class.model_name.human), [:new, *@resource_parents, @resource_class.model_name.singular_route_key], class: "btn btn-default" %>
<% end %>
6 changes: 3 additions & 3 deletions app/views/godmin/resource/columns/_actions.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,23 @@
<% if policy(resource).show? %>
<%= link_to(
translate_scoped("actions.show"),
resource,
[*@resource_parents, resource],
class: "btn btn-default",
title: translate_scoped("actions.show_title", resource: resource)
) %>
<% end %>
<% if policy(resource).edit? %>
<%= link_to(
translate_scoped("actions.edit"),
[:edit, resource],
[:edit, *@resource_parents, resource],
class: "btn btn-default",
title: translate_scoped("actions.edit_title", resource: resource)
) %>
<% end %>
<% if policy(resource).destroy? %>
<%= link_to(
translate_scoped("actions.destroy"),
resource,
[*@resource_parents, resource],
method: :delete,
class: "btn btn-default",
title: translate_scoped("actions.destroy_title", resource: resource),
Expand Down
2 changes: 1 addition & 1 deletion app/views/godmin/resource/show.html.erb
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<%= render partial: "breadcrumb" %>

<table class="table table-striped">
<table class="table">
<% @resource_service.attrs_for_show.each do |attr| %>
<tr>
<th><%= @resource_class.human_attribute_name(attr) %></th>
Expand Down
2 changes: 2 additions & 0 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ en:
confirm_message: Are you sure?
export: Export
export_as: As
associations:
label: Nested resources
sessions:
sign_in: Sign in
sign_out: Sign out
Expand Down
2 changes: 2 additions & 0 deletions config/locales/pt-BR.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ pt-BR:
confirm_message: Você tem certeza?
export: Exportar
export_as: Como
associations:
label: Nested resources
sessions:
sign_in: Entrar
sign_out: Sair
Expand Down
2 changes: 2 additions & 0 deletions config/locales/sv.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ sv:
confirm_message: Är du säker?
export: Exportera
export_as: Som
associations:
label: Nästlade resurser
sessions:
sign_in: Logga in
sign_out: Logga ut
Expand Down
2 changes: 1 addition & 1 deletion lib/godmin/helpers/batch_actions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ def batch_action_link(name, options)

link_to(
translate_scoped("batch_actions.labels.#{name}", default: name.to_s.titleize),
@resource_class,
[*@resource_parents, @resource_class],
method: :patch,
class: "btn btn-default hidden",
data: {
Expand Down
6 changes: 5 additions & 1 deletion lib/godmin/helpers/forms.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@ module Godmin
module Helpers
module Forms
def form_for(record, options = {}, &block)
super(record, { builder: FormBuilders::FormBuilder, inline_errors: false }.merge(options), &block)
super(record, {
url: [*@resource_parents, record],
builder: FormBuilders::FormBuilder,
inline_errors: false
}.merge(options), &block)
end
end
end
Expand Down
29 changes: 24 additions & 5 deletions lib/godmin/resources/resource_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ module ResourceController

before_action :set_resource_service
before_action :set_resource_class
before_action :set_resource_parents
before_action :set_resources, only: :index
before_action :set_resource, only: [:show, :new, :edit, :create, :update, :destroy]
end
Expand Down Expand Up @@ -83,6 +84,10 @@ def set_resource_class
@resource_class = resource_class
end

def set_resource_parents
@resource_parents = resource_parents
end

def set_resources
@resources = resources
authorize(@resources) if authorization_enabled?
Expand All @@ -98,17 +103,31 @@ def resource_service_class
end

def resource_service
resource_service = resource_service_class.new

if authentication_enabled?
resource_service_class.new(admin_user: admin_user)
else
resource_service_class.new
resource_service.options[:admin_user] = admin_user
end

if resource_parents.present?
resource_service.options[:resource_parent] = resource_parents.last
end

resource_service
end

def resource_class
@resource_service.resource_class
end

def resource_parents
params.to_unsafe_h.each_with_object([]) do |(name, value), parents|
if name =~ /(.+)_id$/
parents << $1.classify.constantize.find(value)
end
end
end

def resources
@resource_service.resources(params)
end
Expand Down Expand Up @@ -151,11 +170,11 @@ def redirect_after_update
end

def redirect_after_save
@resource
[*@resource_parents, @resource]
end

def redirect_after_destroy
resource_class.model_name.route_key.to_sym
[*@resource_parents, resource_class.model_name.route_key.to_sym]
end

def redirect_flash_message
Expand Down
9 changes: 7 additions & 2 deletions lib/godmin/resources/resource_service.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
require "godmin/resources/resource_service/associations"
require "godmin/resources/resource_service/batch_actions"
require "godmin/resources/resource_service/filters"
require "godmin/resources/resource_service/ordering"
Expand All @@ -9,6 +10,7 @@ module Resources
module ResourceService
extend ActiveSupport::Concern

include Associations
include BatchActions
include Filters
include Ordering
Expand All @@ -21,7 +23,6 @@ def initialize(options = {})
@options = options
end

# TODO: should this raise its own error?
def resource_class
@options[:resource_class] || resource_class_name.constantize
end
Expand All @@ -31,7 +32,11 @@ def resource_class_name
end

def resources_relation
resource_class.all
if options[:resource_parent].present?
resource_class.where(options[:resource_parent].class.name.underscore => options[:resource_parent])
else
resource_class.all
end
end

def resources(params)
Expand Down
23 changes: 23 additions & 0 deletions lib/godmin/resources/resource_service/associations.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module Godmin
module Resources
module ResourceService
module Associations
extend ActiveSupport::Concern

delegate :has_many_map, to: "self.class"

module ClassMethods
def has_many_map
@has_many_map ||= {}
end

def has_many(attr, options = {})
has_many_map[attr] = {
class_name: attr.to_s.singularize.classify
}.merge(options)
end
end
end
end
end
end
3 changes: 3 additions & 0 deletions test/dummy/app/controllers/comments_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
class CommentsController < ApplicationController
include Godmin::Resources::ResourceController
end
1 change: 1 addition & 0 deletions test/dummy/app/models/article.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class Article < ActiveRecord::Base
belongs_to :admin_user
has_many :comments

def non_orderable_column
"Not orderable"
Expand Down
7 changes: 7 additions & 0 deletions test/dummy/app/models/comment.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Comment < ActiveRecord::Base
belongs_to :article

def to_s
title
end
end
Loading

0 comments on commit 17415f2

Please sign in to comment.