ceo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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