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.
Files changed (79) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +10 -0
  4. data/Rakefile +28 -0
  5. data/app/controllers/admin/admin_controller.rb +212 -0
  6. data/app/views/admin/edit.html.erb +0 -0
  7. data/app/views/admin/index.html.erb +71 -0
  8. data/app/views/admin/new.html.erb +0 -0
  9. data/app/views/admin/show.html.erb +0 -0
  10. data/lib/ceo/engine.rb +4 -0
  11. data/lib/ceo/iterator.rb +154 -0
  12. data/lib/ceo/paginator.rb +118 -0
  13. data/lib/ceo/rails/routes.rb +25 -0
  14. data/lib/ceo/version.rb +3 -0
  15. data/lib/ceo.rb +8 -0
  16. data/lib/tasks/ceo_tasks.rake +4 -0
  17. data/test/ceo/iterator_test.rb +86 -0
  18. data/test/ceo/paginator_test.rb +85 -0
  19. data/test/dummy/README.rdoc +28 -0
  20. data/test/dummy/Rakefile +6 -0
  21. data/test/dummy/app/assets/javascripts/application.js +13 -0
  22. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  23. data/test/dummy/app/controllers/admin/apples_controller.rb +5 -0
  24. data/test/dummy/app/controllers/application_controller.rb +5 -0
  25. data/test/dummy/app/helpers/application_helper.rb +2 -0
  26. data/test/dummy/app/models/apple.rb +3 -0
  27. data/test/dummy/app/models/fruit.rb +9 -0
  28. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  29. data/test/dummy/bin/bundle +3 -0
  30. data/test/dummy/bin/rails +4 -0
  31. data/test/dummy/bin/rake +4 -0
  32. data/test/dummy/bin/setup +29 -0
  33. data/test/dummy/config/application.rb +26 -0
  34. data/test/dummy/config/boot.rb +5 -0
  35. data/test/dummy/config/database.yml +85 -0
  36. data/test/dummy/config/environment.rb +5 -0
  37. data/test/dummy/config/environments/development.rb +41 -0
  38. data/test/dummy/config/environments/production.rb +79 -0
  39. data/test/dummy/config/environments/test.rb +42 -0
  40. data/test/dummy/config/initializers/assets.rb +11 -0
  41. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  42. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  43. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  44. data/test/dummy/config/initializers/inflections.rb +16 -0
  45. data/test/dummy/config/initializers/mime_types.rb +4 -0
  46. data/test/dummy/config/initializers/session_store.rb +3 -0
  47. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  48. data/test/dummy/config/locales/en.yml +23 -0
  49. data/test/dummy/config/routes.rb +3 -0
  50. data/test/dummy/config/secrets.yml +22 -0
  51. data/test/dummy/config.ru +4 -0
  52. data/test/dummy/db/migrate/20151102015027_create_apple.rb +8 -0
  53. data/test/dummy/db/migrate/20151102015821_create_fruit.rb +7 -0
  54. data/test/dummy/db/schema.rb +28 -0
  55. data/test/dummy/log/test.log +1192 -0
  56. data/test/dummy/public/404.html +67 -0
  57. data/test/dummy/public/422.html +67 -0
  58. data/test/dummy/public/500.html +66 -0
  59. data/test/dummy/public/favicon.ico +0 -0
  60. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/06kswGuJvJV_rYCqtNdY_TVqyyiO-Nc9uzEuTWcBpOY.cache +0 -0
  61. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/4Vm2vRc1fj79xCggJkbHDJF2Kpm_o-pNLOleLmn6j_Q.cache +1 -0
  62. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/5Lly_CA8DZvPhQV2jDQx-Y6P_y3Ygra9t5jfSlGhHDA.cache +2 -0
  63. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/9jPCqzZvmeFf31Rz8y3OEo8OQXEHVcwmLgkx0tXs-o8.cache +1 -0
  64. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/LV2itqrs2h4qPkoykMmUzFNXnrnP6eYavPakxsoCd2c.cache +0 -0
  65. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/OI6uxGcnsKavdWTtwDAasU3wPx8QXhzBgV0X2n1KjMQ.cache +3 -0
  66. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Y1Puv07bBLaKxwbahcNBPleCiIkZ7ml1ZqVTzSsohok.cache +0 -0
  67. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/Z9sIdO4ajBUc2xhSbp9_7_TVK8n1QyzBmd6dM78m1i4.cache +0 -0
  68. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/bLvD_U80aJ4Hft-NW5UqC57Y4akx1dmQKVChOvhJ6nk.cache +1 -0
  69. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/hZi1k6tpxxCGYxRe7zY74ItcOI8gZrREOpGuA8JSpGg.cache +3 -0
  70. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/o2kqwqoUQ3gkgncZO1IWdVRzFD0wCSQ-HyL62cINFOU.cache +1 -0
  71. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/pEhaat2KBd5SrT7szC_8R1_6hK17FTpvoRFkmCRSD3M.cache +2 -0
  72. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/pQIgTfLmEPykNamzxdqBww21SMT7YlZlZGy6hgQ6eVE.cache +1 -0
  73. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/rksuu3VMeG2nasZGMWbMVl6YJdK2ldjSP1M9BQQfR3k.cache +0 -0
  74. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/s3lnq8MH-ewGFaFM4MednFWSQj56iBgrSkvq2J9XCSc.cache +1 -0
  75. data/test/dummy/tmp/cache/assets/test/sprockets/v3.0/td9wUl9SLRnSSgE2ZK_VqCzLxTkFiCW50KkOhE916Wo.cache +1 -0
  76. data/test/dummy/tmp/restart.txt +0 -0
  77. data/test/routes_test.rb +27 -0
  78. data/test/test_helper.rb +44 -0
  79. 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
@@ -0,0 +1,4 @@
1
+ module CEO
2
+ class Engine < Rails::Engine
3
+ end
4
+ end
@@ -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
@@ -0,0 +1,3 @@
1
+ module CEO
2
+ VERSION = '0.1.0'
3
+ end
data/lib/ceo.rb ADDED
@@ -0,0 +1,8 @@
1
+ require 'rails'
2
+ require 'ceo/engine'
3
+ require 'ceo/rails/routes'
4
+ require 'ceo/paginator'
5
+ require 'ceo/iterator'
6
+
7
+ module CEO
8
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :ceo do
3
+ # # Task goes here
4
+ # end