ceo 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|
+
[](https://gemnasium.com/littlelines/ceo)
|
|
6
|
+
[](https://rubygems.org/gems/ceo)
|
|
7
|
+
[](http://littlelines.mit-license.org)
|
|
8
|
+
[](https://travis-ci.org/littlelines/ceo)
|
|
9
|
+
[](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