ceo 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +10 -0
- data/Rakefile +28 -0
- data/app/controllers/admin/admin_controller.rb +212 -0
- data/app/views/admin/edit.html.erb +0 -0
- data/app/views/admin/index.html.erb +71 -0
- data/app/views/admin/new.html.erb +0 -0
- data/app/views/admin/show.html.erb +0 -0
- data/lib/ceo/engine.rb +4 -0
- data/lib/ceo/iterator.rb +154 -0
- data/lib/ceo/paginator.rb +118 -0
- data/lib/ceo/rails/routes.rb +25 -0
- data/lib/ceo/version.rb +3 -0
- data/lib/ceo.rb +8 -0
- data/lib/tasks/ceo_tasks.rake +4 -0
- data/test/ceo/iterator_test.rb +86 -0
- data/test/ceo/paginator_test.rb +85 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/admin/apples_controller.rb +5 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/models/apple.rb +3 -0
- data/test/dummy/app/models/fruit.rb +9 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config/application.rb +26 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +85 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +3 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/db/migrate/20151102015027_create_apple.rb +8 -0
- data/test/dummy/db/migrate/20151102015821_create_fruit.rb +7 -0
- data/test/dummy/db/schema.rb +28 -0
- data/test/dummy/log/test.log +1192 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/06kswGuJvJV_rYCqtNdY_TVqyyiO-Nc9uzEuTWcBpOY.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/4Vm2vRc1fj79xCggJkbHDJF2Kpm_o-pNLOleLmn6j_Q.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/5Lly_CA8DZvPhQV2jDQx-Y6P_y3Ygra9t5jfSlGhHDA.cache +2 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/9jPCqzZvmeFf31Rz8y3OEo8OQXEHVcwmLgkx0tXs-o8.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/LV2itqrs2h4qPkoykMmUzFNXnrnP6eYavPakxsoCd2c.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/OI6uxGcnsKavdWTtwDAasU3wPx8QXhzBgV0X2n1KjMQ.cache +3 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Y1Puv07bBLaKxwbahcNBPleCiIkZ7ml1ZqVTzSsohok.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Z9sIdO4ajBUc2xhSbp9_7_TVK8n1QyzBmd6dM78m1i4.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/bLvD_U80aJ4Hft-NW5UqC57Y4akx1dmQKVChOvhJ6nk.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/hZi1k6tpxxCGYxRe7zY74ItcOI8gZrREOpGuA8JSpGg.cache +3 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/o2kqwqoUQ3gkgncZO1IWdVRzFD0wCSQ-HyL62cINFOU.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/pEhaat2KBd5SrT7szC_8R1_6hK17FTpvoRFkmCRSD3M.cache +2 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/pQIgTfLmEPykNamzxdqBww21SMT7YlZlZGy6hgQ6eVE.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/rksuu3VMeG2nasZGMWbMVl6YJdK2ldjSP1M9BQQfR3k.cache +0 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/s3lnq8MH-ewGFaFM4MednFWSQj56iBgrSkvq2J9XCSc.cache +1 -0
- data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/td9wUl9SLRnSSgE2ZK_VqCzLxTkFiCW50KkOhE916Wo.cache +1 -0
- data/test/dummy/tmp/restart.txt +0 -0
- data/test/routes_test.rb +27 -0
- data/test/test_helper.rb +44 -0
- metadata +253 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 2eb7569c41149c06952565d2c06c67421b0e7d54
|
4
|
+
data.tar.gz: 7a4917ca99e37707b935ac219d4a3677f790efcb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 0c5b9119fef0659195d1481e226a5e1227a5edb3daa885d53c1dde7f6f7050ba21eed00f6464aaa76b917dda1dc7b52982b95bd5d1eaec2173bca1f3000b204c
|
7
|
+
data.tar.gz: 13c55c6ecbb45d24e33ca94ad28fc15ffce00638fe31ff53f0bc269cd0a819267d41b619997198a79abb19ac6941f5b14d81b49f76bf130b3fe397688c85d2cc
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2015 Jesse Herrick and Littlelines, LLC.
|
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.
|
data/README.md
ADDED
@@ -0,0 +1,10 @@
|
|
1
|
+
CEO
|
2
|
+
---
|
3
|
+
|
4
|
+
> Your app's chief executive.
|
5
|
+
[![Dependency Status](http://img.shields.io/gemnasium/littlelines/ceo.svg)](https://gemnasium.com/littlelines/ceo)
|
6
|
+
[![Gem Version](http://img.shields.io/gem/v/ceo.svg)](https://rubygems.org/gems/ceo)
|
7
|
+
[![License](http://img.shields.io/:license-mit-blue.svg)](http://littlelines.mit-license.org)
|
8
|
+
[![Build Status](https://travis-ci.org/littlelines/ceo.svg?branch=master)](https://travis-ci.org/littlelines/ceo)
|
9
|
+
[![Code Climate](https://codeclimate.com/github/littlelines/ceo/badges/gpa.svg)](https://codeclimate.com/github/littlelines/ceo)
|
10
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,28 @@
|
|
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 = 'Ceo'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
Bundler::GemHelper.install_tasks
|
18
|
+
|
19
|
+
require 'rake/testtask'
|
20
|
+
|
21
|
+
Rake::TestTask.new(:test) do |t|
|
22
|
+
t.libs << 'lib'
|
23
|
+
t.libs << 'test'
|
24
|
+
t.pattern = 'test/**/*_test.rb'
|
25
|
+
t.verbose = false
|
26
|
+
end
|
27
|
+
|
28
|
+
task default: :test
|
@@ -0,0 +1,212 @@
|
|
1
|
+
# Public: A base controller for admin pages.
|
2
|
+
#
|
3
|
+
# Routing:
|
4
|
+
# Routing can be dple `resource` route.
|
5
|
+
class Admin::AdminController < ApplicationController
|
6
|
+
before_action :find_thing, only: [:show, :edit, :update, :destroy]
|
7
|
+
|
8
|
+
helper_method :thing_path
|
9
|
+
helper_method :things_path
|
10
|
+
helper_method :new_thing_path
|
11
|
+
helper_method :edit_thing_path
|
12
|
+
|
13
|
+
# GET /things
|
14
|
+
# Public: Indexes all things in the model.
|
15
|
+
#
|
16
|
+
# Uses pagination by default.
|
17
|
+
#
|
18
|
+
# Sets several variables and renders to a view.
|
19
|
+
def index(options = {})
|
20
|
+
options[:query] ||= []
|
21
|
+
@page = (params[:page] || 1).to_i
|
22
|
+
@route_name ||= @controller_name ||= controller_name
|
23
|
+
|
24
|
+
@iterator = CEO::Iterator.new(
|
25
|
+
thing,
|
26
|
+
query: options[:query],
|
27
|
+
page: @page,
|
28
|
+
per_page: options.fetch(:per_page, 20),
|
29
|
+
filters: {
|
30
|
+
only: options[:only],
|
31
|
+
except: options[:except]
|
32
|
+
}
|
33
|
+
)
|
34
|
+
|
35
|
+
@things = @iterator.all || []
|
36
|
+
@total_pages = @iterator.total_pages
|
37
|
+
|
38
|
+
@model_name ||= thing.to_s.underscore
|
39
|
+
@human_model = @model_name.humanize
|
40
|
+
@title = thing.to_s.titleize.pluralize
|
41
|
+
render 'admin/index'
|
42
|
+
end
|
43
|
+
|
44
|
+
# GET /:things/:id
|
45
|
+
def show(options = {})
|
46
|
+
@thing_model = thing
|
47
|
+
if options[:query]
|
48
|
+
query_out = {}
|
49
|
+
iterator = CEO::Iterator.new(thing)
|
50
|
+
options[:query].each do |q|
|
51
|
+
query_out.merge! iterator.query_eval(@thing, q)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
filtered_keys = CEO::Iterator.filter(keys(@thing), options)
|
56
|
+
@thing_attrs = @thing.attributes.select do |k, _|
|
57
|
+
filtered_keys.include? k
|
58
|
+
end
|
59
|
+
|
60
|
+
@thing_attrs.transform_keys! { |k| CEO::Iterator.acronymize(k) }
|
61
|
+
@thing_attrs.merge! query_out if query_out
|
62
|
+
|
63
|
+
render 'admin/show'
|
64
|
+
end
|
65
|
+
|
66
|
+
# GET /:things/new
|
67
|
+
def new(options = { only: [], except: [:id, :created_at, :updated_at], required: [] })
|
68
|
+
@thing_model = thing
|
69
|
+
@thing = @thing_model.new
|
70
|
+
@attrs = CEO::Iterator.filter(@thing.attributes.keys, options)
|
71
|
+
@route_name ||= @controller_name ||= controller_name
|
72
|
+
@index_route = send(:"admin_#{@route_name}_path")
|
73
|
+
|
74
|
+
render 'admin/new'
|
75
|
+
end
|
76
|
+
|
77
|
+
# GET /:things/edit
|
78
|
+
#
|
79
|
+
# options - hash of options
|
80
|
+
# only - filter attributes to edit by whitelist
|
81
|
+
# except - filter attributes to edit by blacklist (has some preset defaults that you probably don't want)
|
82
|
+
def edit(options = { only: [], except: [:id, :created_at, :updated_at], required: [] })
|
83
|
+
@required_keys = options[:required]
|
84
|
+
@attrs = CEO::Iterator.filter(@thing.attributes.keys, options)
|
85
|
+
@model = @thing.model_name.name.constantize
|
86
|
+
@route_name ||= @controller_name ||= controller_name
|
87
|
+
@index_route = send(:"admin_#{@route_name}_path")
|
88
|
+
|
89
|
+
render 'admin/edit'
|
90
|
+
end
|
91
|
+
|
92
|
+
# POST /:things
|
93
|
+
def create
|
94
|
+
@thing = thing.new(thing_params)
|
95
|
+
@route_name ||= @controller_name ||= controller_name
|
96
|
+
|
97
|
+
if @thing.save
|
98
|
+
flash[:notice] = "#{thing} successfully created."
|
99
|
+
redirect_to things_path(@route_name), notice: "#{thing.to_s.titleize} successfully created."
|
100
|
+
else
|
101
|
+
render 'admin/new'
|
102
|
+
end
|
103
|
+
end
|
104
|
+
|
105
|
+
# PATCH/PUT /:things/:id
|
106
|
+
def update
|
107
|
+
@route_name ||= @controller_name ||= controller_name
|
108
|
+
|
109
|
+
if @thing.update(thing_params)
|
110
|
+
redirect_to things_path(@route_name), notice: "#{@controller_name.titleize} successfully updated."
|
111
|
+
else
|
112
|
+
render 'admin/edit'
|
113
|
+
end
|
114
|
+
end
|
115
|
+
|
116
|
+
# DELETE /:things/:id
|
117
|
+
def destroy
|
118
|
+
@thing.destroy
|
119
|
+
|
120
|
+
flash[:notice] = "#{thing} #{@thing.id} was successfully destroyed."
|
121
|
+
redirect_to action: :index
|
122
|
+
end
|
123
|
+
|
124
|
+
private
|
125
|
+
|
126
|
+
# Internal: Finds the ActiveRecord object of the current controller.
|
127
|
+
#
|
128
|
+
# Returns an ActiveRecord class.
|
129
|
+
def thing
|
130
|
+
controller_name.classify.constantize
|
131
|
+
end
|
132
|
+
|
133
|
+
# Private: Permits all model-defined parameters.
|
134
|
+
#
|
135
|
+
# Retuns a hash of parameters.
|
136
|
+
def thing_params
|
137
|
+
@params ||= thing.new.attributes.keys.map(&:to_sym)
|
138
|
+
params.require(thing.name.underscore.to_sym).permit(
|
139
|
+
@params,
|
140
|
+
:page
|
141
|
+
)
|
142
|
+
end
|
143
|
+
|
144
|
+
# Private: Finds an ActiveRecord object by id.
|
145
|
+
#
|
146
|
+
# Sets a variable `@thing`, which contains the object.
|
147
|
+
def find_thing
|
148
|
+
@thing = thing.find(params[:id])
|
149
|
+
end
|
150
|
+
|
151
|
+
# Private: Returns keys for a given model.
|
152
|
+
#
|
153
|
+
# Arguments:
|
154
|
+
# - model: the model object to get the keys from
|
155
|
+
#
|
156
|
+
# Returns an array of keys.
|
157
|
+
def keys(model)
|
158
|
+
model.attributes.keys
|
159
|
+
end
|
160
|
+
|
161
|
+
# Private: Returns the path of the thing.
|
162
|
+
#
|
163
|
+
# object - An instance of a model.
|
164
|
+
#
|
165
|
+
# Returns the path of the thing.
|
166
|
+
def thing_path(model, object)
|
167
|
+
id = object['ID'] || object['id']
|
168
|
+
send(:"admin_#{model}_path", id: id)
|
169
|
+
end
|
170
|
+
|
171
|
+
# Private: Returns the index path of a model.
|
172
|
+
#
|
173
|
+
# object - An instance of a model.
|
174
|
+
#
|
175
|
+
# Returns the path of many things.
|
176
|
+
def things_path(object)
|
177
|
+
object ||= @route_name || controller_name
|
178
|
+
send(:"admin_#{object.pluralize}_path")
|
179
|
+
end
|
180
|
+
|
181
|
+
# Private
|
182
|
+
#
|
183
|
+
# model - The model name.
|
184
|
+
# object - An instance of a model.
|
185
|
+
#
|
186
|
+
# Returns the edit path of a model.
|
187
|
+
def edit_thing_path(model, object)
|
188
|
+
id = object['ID'] || object['id']
|
189
|
+
send(:"edit_admin_#{model}_path", id: id)
|
190
|
+
end
|
191
|
+
|
192
|
+
# Private
|
193
|
+
#
|
194
|
+
# model - The model name.
|
195
|
+
# object - An instance of a model.
|
196
|
+
#
|
197
|
+
# Returns the new path of a model.
|
198
|
+
def new_thing_path(model, object)
|
199
|
+
id = object['ID'] || object['id']
|
200
|
+
send(:"new_admin_#{model}_path", id: id)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Private
|
204
|
+
#
|
205
|
+
# model - The model name.
|
206
|
+
# page - The page number.
|
207
|
+
#
|
208
|
+
# Returns the paginated path of an object.
|
209
|
+
def page_thing_path(model, page)
|
210
|
+
send(:"page_admin_#{model}_path", page: page)
|
211
|
+
end
|
212
|
+
end
|
File without changes
|
@@ -0,0 +1,71 @@
|
|
1
|
+
<h1><%= @title %></h1>
|
2
|
+
|
3
|
+
<table>
|
4
|
+
<thead>
|
5
|
+
<tr>
|
6
|
+
<% if @things.blank? %>
|
7
|
+
<%= "There are no #{@human_model.downcase.pluralize} in the database." %>
|
8
|
+
<% else %>
|
9
|
+
<% @things.first.each_key do |heading| %>
|
10
|
+
<th>
|
11
|
+
<%= heading %>
|
12
|
+
</th>
|
13
|
+
<% end %>
|
14
|
+
<% end %>
|
15
|
+
</tr>
|
16
|
+
</thead>
|
17
|
+
|
18
|
+
<tbody>
|
19
|
+
<% @things.each do |thing| %>
|
20
|
+
<% thing.each do |heading, value| %>
|
21
|
+
<td label="<%= heading %>: ">
|
22
|
+
<%= value %>
|
23
|
+
</td>
|
24
|
+
|
25
|
+
<div class="actions">
|
26
|
+
<td><%= link_to("Edit #{@title}", edit_thing_path(@model_name, thing)) %></td>
|
27
|
+
<td><%= link_to("Show #{@title}", thing_path(@model_name, thing)) %></td>
|
28
|
+
</div>
|
29
|
+
<% end %>
|
30
|
+
<% end %>
|
31
|
+
</tbody>
|
32
|
+
</table>
|
33
|
+
|
34
|
+
<nav class="navigation" role="navigation">
|
35
|
+
<ul>
|
36
|
+
# First Page
|
37
|
+
<% unless @page == 1 %>
|
38
|
+
<li>
|
39
|
+
<%= link_to('1', thing_page_path(@model_name, @page - 1)) %>
|
40
|
+
</li>
|
41
|
+
<% end %>
|
42
|
+
|
43
|
+
# Previous Page
|
44
|
+
<% if @page > 1 %>
|
45
|
+
<li>
|
46
|
+
<%= link_to("#{page - 1}", thing_page_path(@model_name, @page - 1)) %>
|
47
|
+
</li>
|
48
|
+
<% end %>
|
49
|
+
|
50
|
+
# Current Page
|
51
|
+
<% unless @total_page == 1 %>
|
52
|
+
<li>
|
53
|
+
<a><%= @page %></a>
|
54
|
+
</li>
|
55
|
+
<% end %>
|
56
|
+
|
57
|
+
# Next Page
|
58
|
+
<% if @page < @total_pages %>
|
59
|
+
<li>
|
60
|
+
link_to("#{page + 1}", thing_page_path(@model_name, @page + 1))
|
61
|
+
</li>
|
62
|
+
<% end %>
|
63
|
+
|
64
|
+
# Last Page
|
65
|
+
<% unless @page == @total_pages %>
|
66
|
+
<li>
|
67
|
+
link_to("#{@total_pages}", thing_page_path(@model_name, @total_pages))
|
68
|
+
</li>
|
69
|
+
<% end %>
|
70
|
+
</ul>
|
71
|
+
</nav>
|
File without changes
|
File without changes
|
data/lib/ceo/engine.rb
ADDED
data/lib/ceo/iterator.rb
ADDED
@@ -0,0 +1,154 @@
|
|
1
|
+
# Public: Iterator for listing out things.
|
2
|
+
module CEO
|
3
|
+
class Iterator
|
4
|
+
attr_reader :current_page, :total_pages
|
5
|
+
|
6
|
+
# model - The model to perform queries on.
|
7
|
+
# options -
|
8
|
+
# query - An array of nested queries.
|
9
|
+
# filters - A hash of filters (only/except).
|
10
|
+
def initialize(model, options = {})
|
11
|
+
@model = model
|
12
|
+
@options = options
|
13
|
+
@queries = @options[:query] || []
|
14
|
+
@filters = @options[:filters] || {}
|
15
|
+
end
|
16
|
+
|
17
|
+
# Public: Iterates through all parts.
|
18
|
+
#
|
19
|
+
# Yields or returns an Enumerator.
|
20
|
+
def each
|
21
|
+
if block_yielded?
|
22
|
+
all.each do |thing|
|
23
|
+
yield(thing)
|
24
|
+
end
|
25
|
+
else
|
26
|
+
all.to_enum
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Public: Returns a hash of titleized attributes mapped to their values.
|
31
|
+
#
|
32
|
+
# Uses pagination.
|
33
|
+
#
|
34
|
+
# options -
|
35
|
+
# current_page - currently paginated page
|
36
|
+
# per_page - # of things to list per page
|
37
|
+
#
|
38
|
+
# Returns a paginated hash of data.
|
39
|
+
def all
|
40
|
+
attribute_maps = [] # [{...}, {...}]
|
41
|
+
@pages = CEO::Paginator.new(
|
42
|
+
@model,
|
43
|
+
current_page: current_page,
|
44
|
+
per_page: @options.fetch(:per_page, 20)
|
45
|
+
)
|
46
|
+
|
47
|
+
self.total_pages = @pages.total_pages
|
48
|
+
|
49
|
+
@pages.each do |thing|
|
50
|
+
attr_object = {}
|
51
|
+
|
52
|
+
# TODO: Make all of this into a method.
|
53
|
+
# map first-level values to a hash
|
54
|
+
keys.each do |key|
|
55
|
+
attr_object[self.class.acronymize(key)] = thing[key.to_s]
|
56
|
+
end
|
57
|
+
|
58
|
+
# map nested values to a hash
|
59
|
+
@queries.each do |query|
|
60
|
+
attr_object.merge! query_eval(thing, query)
|
61
|
+
end
|
62
|
+
attribute_maps << attr_object
|
63
|
+
end
|
64
|
+
attribute_maps
|
65
|
+
end
|
66
|
+
|
67
|
+
# { 'Country Name' => 'South Korea' }
|
68
|
+
def query_eval(scope, query)
|
69
|
+
query_parts = query.split('.')
|
70
|
+
if query_parts.length > 2
|
71
|
+
title = self.class.acronymize(query_parts[-2..-1].join(' '))
|
72
|
+
resp = 'None' if scope.instance_eval(query_parts[0]).nil? || scope.instance_eval(query_parts[0..1].join('.')).nil?
|
73
|
+
elsif query_parts[-1] == 'name'
|
74
|
+
title = self.class.acronymize(query_parts.join(' '))
|
75
|
+
resp = 'None' if scope.instance_eval(query_parts[0]).nil?
|
76
|
+
else
|
77
|
+
title = self.class.acronymize query_parts[-1]
|
78
|
+
resp = 'None' if scope.instance_eval(query_parts[0]).nil?
|
79
|
+
end
|
80
|
+
|
81
|
+
resp = scope.instance_eval(query) unless resp == 'None'
|
82
|
+
{ title => resp }
|
83
|
+
end
|
84
|
+
|
85
|
+
# Public: Filters an enum based on a hash of params.
|
86
|
+
#
|
87
|
+
# things - An enum of things to filter.
|
88
|
+
# filters - A hash of filters (ALLOWED: [:only, :except]).
|
89
|
+
#
|
90
|
+
# Since 'only' and 'except' are mutually exclusive, 'only'
|
91
|
+
# will be prefered over 'except'.
|
92
|
+
#
|
93
|
+
# Returns a hash of filtered keys.
|
94
|
+
def self.filter(things, filters)
|
95
|
+
return self.only(things, filters[:only]) unless filters[:only].nil? || filters[:only].empty?
|
96
|
+
return self.except(things, filters[:except]) unless filters[:except].nil? || filters[:except].empty?
|
97
|
+
|
98
|
+
return things # if nothing to filter, just return the things
|
99
|
+
end
|
100
|
+
|
101
|
+
# Public: Blacklists keys based on an array.
|
102
|
+
#
|
103
|
+
# blacklist - An array of keys that are not allowed.
|
104
|
+
#
|
105
|
+
# Examples
|
106
|
+
#
|
107
|
+
# blacklist = [:this, :that]
|
108
|
+
# except([:this, :that, :here, :now], blacklist)
|
109
|
+
# # => [:here, :now]
|
110
|
+
#
|
111
|
+
# Returns a filtered hash of keys.
|
112
|
+
def self.except(things, blacklist)
|
113
|
+
blacklist = blacklist.map(&:to_s)
|
114
|
+
things = things.map(&:to_s)
|
115
|
+
things.select { |thing| !blacklist.include? thing }
|
116
|
+
end
|
117
|
+
|
118
|
+
# Public: Whitelists keys based on an array.
|
119
|
+
#
|
120
|
+
# whitelist - An array of keys that are only allowed.
|
121
|
+
#
|
122
|
+
# Examples
|
123
|
+
#
|
124
|
+
# whitelist = [:this, :that]
|
125
|
+
# only([:this, :that, :here, :now], whitelist)
|
126
|
+
# # => [:this, :that]
|
127
|
+
#
|
128
|
+
# Returns a filtered hash of keys.
|
129
|
+
def self.only(things, whitelist)
|
130
|
+
whitelist = whitelist.map(&:to_s)
|
131
|
+
things = things.map(&:to_s)
|
132
|
+
things.select { |thing| whitelist.include? thing }
|
133
|
+
end
|
134
|
+
|
135
|
+
def current_page
|
136
|
+
(@options[:current_page] || 1).to_i
|
137
|
+
end
|
138
|
+
|
139
|
+
# Public: Titleizes normal stuff, but upcases acronyms.
|
140
|
+
def self.acronymize(key)
|
141
|
+
parsed_key = key.to_s.titleize
|
142
|
+
parsed_key = parsed_key.gsub 'Id', 'ID'
|
143
|
+
parsed_key.gsub 'Iata Code', 'IATA'
|
144
|
+
end
|
145
|
+
|
146
|
+
private
|
147
|
+
|
148
|
+
attr_writer :total_pages
|
149
|
+
|
150
|
+
def keys
|
151
|
+
singleton_class.send(:filter, @model.new.attributes.keys, @filters)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
@@ -0,0 +1,118 @@
|
|
1
|
+
module CEO
|
2
|
+
# Class to Paginate over active record scopes
|
3
|
+
#
|
4
|
+
# Ex:
|
5
|
+
#
|
6
|
+
# Routes:
|
7
|
+
#
|
8
|
+
# resources :users do
|
9
|
+
# collection do
|
10
|
+
# get 'page/:page', action: 'index', as: 'page', constraints: { page: /\d+/ }
|
11
|
+
# end
|
12
|
+
# en
|
13
|
+
#
|
14
|
+
# Controller:
|
15
|
+
#
|
16
|
+
# def index
|
17
|
+
# @users = Paginator.new(
|
18
|
+
# User.limit(100).order(name: :asc),
|
19
|
+
# current_page: params.fetch(:page, 1).to_i,
|
20
|
+
# per_page: 10
|
21
|
+
# )
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
#
|
25
|
+
# View with `paginate` helper:
|
26
|
+
#
|
27
|
+
# .row
|
28
|
+
# .large-12.columns
|
29
|
+
# = paginate @users, :admin_users_path, intermediate_pages: 6
|
30
|
+
#
|
31
|
+
#
|
32
|
+
# Simple View:
|
33
|
+
#
|
34
|
+
# - if @order_paginator.has_previous?
|
35
|
+
# = link_to 'previous page', orders_path(page: @order_paginator.previous_page)
|
36
|
+
#
|
37
|
+
# - if @order_paginator.has_next?
|
38
|
+
# = link_to 'next page', orders_path(page: @order_paginator.next_page)
|
39
|
+
#
|
40
|
+
class Paginator
|
41
|
+
include Enumerable
|
42
|
+
|
43
|
+
attr_accessor :scope, :current_page, :per_page
|
44
|
+
|
45
|
+
# Constructor
|
46
|
+
#
|
47
|
+
# scope - The ActiveRecord scope to page over
|
48
|
+
# current_page - The Number of the current page. >= 1
|
49
|
+
# per_page - The number of records per page
|
50
|
+
#
|
51
|
+
def initialize(scope, options)
|
52
|
+
@current_page = options.fetch :current_page
|
53
|
+
@per_page = options.fetch :per_page
|
54
|
+
@scope = scope
|
55
|
+
end
|
56
|
+
|
57
|
+
def has_previous?
|
58
|
+
current_page.to_i > 1
|
59
|
+
end
|
60
|
+
|
61
|
+
def has_next?
|
62
|
+
paged_scope.count == per_page
|
63
|
+
end
|
64
|
+
|
65
|
+
def multiple_pages?
|
66
|
+
total_pages.to_i > 1
|
67
|
+
end
|
68
|
+
|
69
|
+
def next_page
|
70
|
+
current_page.to_i + 1
|
71
|
+
end
|
72
|
+
|
73
|
+
def previous_page
|
74
|
+
if current_page.to_i - 1 > 0
|
75
|
+
current_page.to_i - 1
|
76
|
+
else
|
77
|
+
current_page.to_i
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def each
|
82
|
+
paged_scope.each{|record| yield record }
|
83
|
+
end
|
84
|
+
|
85
|
+
def is_current_page?(page_number)
|
86
|
+
current_page.to_i == page_number.to_i
|
87
|
+
end
|
88
|
+
|
89
|
+
def intermediate_pages(max = 5)
|
90
|
+
floor = current_page - (max.to_f / 2).floor
|
91
|
+
ceil = current_page + (max.to_f / 2).ceil
|
92
|
+
floor = 1 if floor <= 0
|
93
|
+
ceil = total_pages if ceil > total_pages
|
94
|
+
|
95
|
+
floor.upto(ceil).collect{|page_num| page_num }
|
96
|
+
end
|
97
|
+
|
98
|
+
def total_results
|
99
|
+
@total_results ||= scope.count
|
100
|
+
end
|
101
|
+
|
102
|
+
def total_pages
|
103
|
+
@total_pages ||= (total_results.to_f / per_page).ceil
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def paging_offset
|
109
|
+
return unless current_page
|
110
|
+
|
111
|
+
(current_page.to_i - 1).abs * per_page
|
112
|
+
end
|
113
|
+
|
114
|
+
def paged_scope
|
115
|
+
scope.offset(paging_offset).limit(per_page)
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'action_dispatch/routing'
|
2
|
+
require 'active_support/concern'
|
3
|
+
|
4
|
+
module ActionDispatch::Routing
|
5
|
+
class Mapper
|
6
|
+
# Public: Generates default admin CRUD routes for resources.
|
7
|
+
#
|
8
|
+
# Returns a route.
|
9
|
+
def admin_for(*rsrcs)
|
10
|
+
rsrcs.map!(&:to_sym)
|
11
|
+
|
12
|
+
concern :pageable do
|
13
|
+
collection do
|
14
|
+
get '/page/:page', action: :index, as: 'page'
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
namespace :admin do
|
19
|
+
rsrcs.each do |r|
|
20
|
+
resources r, concerns: :pageable
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
data/lib/ceo/version.rb
ADDED
data/lib/ceo.rb
ADDED