sofav 0.2.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 (71) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +35 -0
  4. data/Rakefile +34 -0
  5. data/app/builders/admin/form_builder.rb +250 -0
  6. data/app/controllers/application_controller.rb +23 -0
  7. data/app/decorators/base_decorator.rb +172 -0
  8. data/app/helpers/admin/core_helper.rb +237 -0
  9. data/app/helpers/admin/show_view_helper.rb +69 -0
  10. data/app/helpers/admin/tags_helper.rb +104 -0
  11. data/app/services/active_record/search_service.rb +121 -0
  12. data/app/views/admin/application/_breadcrumb_nav.html.erb +10 -0
  13. data/app/views/admin/application/_collection.html.erb +79 -0
  14. data/app/views/admin/application/_save_js.js.erb +20 -0
  15. data/app/views/admin/application/_sidebar.html.erb +62 -0
  16. data/app/views/admin/application/_topbar.html.erb +32 -0
  17. data/app/views/admin/application/actions/charge.html.erb +3 -0
  18. data/app/views/admin/application/actions/destroy.html.erb +5 -0
  19. data/app/views/admin/application/actions/edit.html.erb +1 -0
  20. data/app/views/admin/application/actions/edit_sights.html.erb +1 -0
  21. data/app/views/admin/application/actions/feedback.html.erb +4 -0
  22. data/app/views/admin/application/actions/index_charge.html.erb +1 -0
  23. data/app/views/admin/application/actions/index_destroy.html.erb +1 -0
  24. data/app/views/admin/application/actions/index_edit.html.erb +1 -0
  25. data/app/views/admin/application/actions/index_edit_sights.html.erb +1 -0
  26. data/app/views/admin/application/actions/index_show.html.erb +1 -0
  27. data/app/views/admin/application/actions/new.html.erb +1 -0
  28. data/app/views/admin/application/create.js.erb +1 -0
  29. data/app/views/admin/application/destroy.js.erb +6 -0
  30. data/app/views/admin/application/edit.html.erb +18 -0
  31. data/app/views/admin/application/index.html.erb +23 -0
  32. data/app/views/admin/application/new.html.erb +18 -0
  33. data/app/views/admin/application/partials/_map_pos_picker.html.erb +101 -0
  34. data/app/views/admin/application/partials/_sort_script.html.erb +32 -0
  35. data/app/views/admin/application/show.html.erb +16 -0
  36. data/app/views/admin/application/update.js.erb +1 -0
  37. data/app/views/admin/dashboard/index.html.erb +8 -0
  38. data/app/views/layouts/admin.html.erb +30 -0
  39. data/config/locales/devise.en.yml +64 -0
  40. data/config/locales/devise.zh-CN.yml +120 -0
  41. data/config/locales/doorkeeper.en.yml +124 -0
  42. data/config/locales/doorkeeper.zh-CN.yml +132 -0
  43. data/config/locales/en.yml +23 -0
  44. data/config/locales/enumerize/defaults.zh-CN.yml +7 -0
  45. data/config/locales/kaminari.zh-CN.yml +17 -0
  46. data/config/locales/rails.zh-CN.yml +204 -0
  47. data/config/locales/views/actions.zh-CN.yml +21 -0
  48. data/config/locales/views/attributes.zh-CN.yml +15 -0
  49. data/config/locales/views/breadcrumb.zh-CN.yml +5 -0
  50. data/config/locales/views/common.zh-CN.yml +7 -0
  51. data/config/locales/views/enums.zh-CN.yml +13 -0
  52. data/config/locales/views/profiles.zh-CN.yml +3 -0
  53. data/lib/generators/sofav/USAGE +0 -0
  54. data/lib/generators/sofav/sofav_generator.rb +50 -0
  55. data/lib/generators/sofav/templates/activerecord.zh-CN.yml +4 -0
  56. data/lib/generators/sofav/templates/attribute_types.zh-CN.yml +2 -0
  57. data/lib/sofav.rb +6 -0
  58. data/lib/sofav/decorator.rb +47 -0
  59. data/lib/sofav/local.rb +52 -0
  60. data/lib/sofav/version.rb +3 -0
  61. data/lib/tasks/sofa_tasks.rake +4 -0
  62. data/vendor/assets/javascripts/admin/here.js +0 -0
  63. data/vendor/assets/javascripts/bootbox.js +1020 -0
  64. data/vendor/assets/javascripts/bootstrap.js +2377 -0
  65. data/vendor/assets/javascripts/jquery-ui.js +5169 -0
  66. data/vendor/assets/javascripts/select2.js +5725 -0
  67. data/vendor/assets/stylesheets/bootstrap.css +6800 -0
  68. data/vendor/assets/stylesheets/bootstrap.css.map +1 -0
  69. data/vendor/assets/stylesheets/jquery-ui.css +453 -0
  70. data/vendor/assets/stylesheets/select2.css +484 -0
  71. metadata +211 -0
@@ -0,0 +1,237 @@
1
+ module Admin
2
+ module CoreHelper
3
+ # BREADCRUMB_PREFIX_ACTION = %w{new edit}.freeze
4
+ # BREADCRUMB_SUFFIX_ACTION = %w{index show}.freeze
5
+ #
6
+ # %i{model decorator current_studio records record}.map { |method_name|
7
+ # delegate method_name, to: :controller
8
+ # }
9
+ #
10
+ # def full_controller_name
11
+ # params[:controller]
12
+ # end
13
+ #
14
+ # def full_controller_name_human
15
+ # if controller_name == 'settings'
16
+ # t("breadcrumb.#{full_controller_name}")
17
+ # else
18
+ # [t("breadcrumb.#{full_controller_name}"), '系统'].join
19
+ # end
20
+ # end
21
+ #
22
+ # def full_controller_link
23
+ # full_controller_name.sub('admin', '#!')
24
+ # end
25
+ #
26
+ # def default_action_name_human
27
+ # if BREADCRUMB_PREFIX_ACTION.include?(action_name)
28
+ # [t("actions.#{action_name}"), t("breadcrumb.#{full_controller_name}")].join
29
+ # elsif BREADCRUMB_SUFFIX_ACTION.include?(action_name)
30
+ # [t("breadcrumb.#{full_controller_name}"), t("actions.#{action_name}")].join
31
+ # else
32
+ # '默认导航'
33
+ # end
34
+ # end
35
+ #
36
+ # def full_action_name_human
37
+ # t("breadcrumb.#{full_controller_name}/#{action_name}", default: default_action_name_human)
38
+ # end
39
+ #
40
+ # # 添加对象按钮
41
+ # def new_model_tag(target_model = nil)
42
+ # model_name = (target_model || controller_name).singularize
43
+ # model_human_name = [t('actions.new'), t("activerecord.models.#{model_name}")].join
44
+ # target_link = ['#!', target_model || controller_name, :new].join('/')
45
+ # content_tag(:div, class: 'pull-right btn-groups') do
46
+ # link_to(model_human_name, target_link, class: 'btn btn-primary clearfix')
47
+ # end
48
+ # end
49
+ #
50
+ # # 筛选栏
51
+ # def filters_tag(target_model = nil, filters_name = nil)
52
+ # (_model = target_model || model).blank? and return
53
+ # (_decorator = _model.decorator).blank? and return
54
+ #
55
+ # filters = Array(_decorator.try(filters_name || :index_filters))
56
+ # filters.collect { |filter_key, filter_name|
57
+ # content_tag(:div, class: 'pull-left dropdown filters') do
58
+ # html = []
59
+ # html << button_tag(type: 'button', name: filter_key, class: 'btn btn-default',
60
+ # data: {toggle: 'dropdown'},
61
+ # aria: {haspopup: true, expanded: false}) do
62
+ # [filter_name, content_tag(:span, nil, class: 'caret')].join.html_safe
63
+ # end
64
+ #
65
+ # html << content_tag(:ul, class: 'dropdown-menu') do
66
+ # _decorator.filter_for(filter_key)
67
+ # end
68
+ # html.join.html_safe
69
+ # end
70
+ # }.join.html_safe
71
+ # end
72
+
73
+ # # 搜索栏
74
+ # def search_tag(target_model = nil, search_name = nil)
75
+ # (_model = target_model || model).nil? and return
76
+ # (_decorator = _model.decorator).blank? and return
77
+ # (searches = _decorator.try(search_name || :index_searches)).blank? and return
78
+ #
79
+ # html = []
80
+ # name, placeholder, action = searches.values_at(:name, :placeholder, :action)
81
+ # form_tag(action, class: 'pull-right', method: :get, enforce_utf8: false) do
82
+ # html << text_field_tag(name, params[name], placeholder: placeholder)
83
+ # html << button_tag(type: nil, name: nil) do
84
+ # content_tag(:i, nil, class: 'glyphicon glyphicon-search')
85
+ # end
86
+ # html.join.html_safe
87
+ # end
88
+ # end
89
+
90
+ # # 行数据的操作列表
91
+ # def render_actions(record)
92
+ # (_decorate = record.try(:decorate)).blank? and return
93
+ # (_decorate.try(:manage_actions) || {}).collect { |action|
94
+ # render("admin/shared/actions/#{action}", record: record)
95
+ # }.join.html_safe
96
+ # end
97
+ #
98
+ # # 列表栏表头
99
+ # def table_thead_tag(target_model = nil)
100
+ # (_model = target_model || model).blank? and return
101
+ # (_decorator = _model.decorator).blank? and return
102
+ # (list_fields = _decorator.try(:list_fields)).blank? and return
103
+ #
104
+ # content_tag(:thead) do
105
+ # content_tag(:tr) do
106
+ # ths = list_fields.collect { |field| content_tag(:th, _model.human_attribute_name(field)) }
107
+ # ths << content_tag(:th, t('action')) if _decorator.try(:class_actions)
108
+ # ths.join.html_safe
109
+ # end
110
+ # end
111
+ # end
112
+ #
113
+ # # 列表页数据内容
114
+ # def table_tbody_tag(target_records = nil)
115
+ # (_records = target_records || records).blank? and return
116
+ #
117
+ # trs = []
118
+ # content_tag(:tbody) do
119
+ # _records.each do |record|
120
+ # _decorate = record.decorate
121
+ #
122
+ # trs << content_tag(:tr) do
123
+ # tds = _decorate.try(:list_fields).collect { |field| content_tag(:td, record.human_field(field)) }
124
+ # tds << content_tag(:td, render_actions(record)) if _decorate.try(:manage_actions)
125
+ # tds.join.html_safe
126
+ # end
127
+ # end
128
+ # trs.join.html_safe
129
+ # end
130
+ # end
131
+ #
132
+ # # 数据列表
133
+ # def records_list_tag(target_records = nil)
134
+ # content_tag(:table, class: 'table table-striped') do
135
+ # _records = target_records || records
136
+ # target_model = _records.class.to_s.deconstantize.safe_constantize
137
+ # html = [table_thead_tag(target_model)]
138
+ # html << table_tbody_tag(_records) if _records.present?
139
+ # html.join.html_safe
140
+ # end
141
+ # end
142
+ #
143
+ # # 翻页控件
144
+ # def paginate_tag(target_records = nil)
145
+ # paginate(target_records || records, theme: 'admin')
146
+ # end
147
+
148
+ # 详情页操作按钮
149
+ def show_manage_actions_tag(target_record = nil)
150
+ _record = target_record || record
151
+ (_decorate = _record.try(:decorate)).blank? and return
152
+ (_decorate.try(:manage_actions) || {}).collect { |action|
153
+ render("admin/shared/actions/#{action}", record: record)
154
+ }.join.html_safe
155
+ end
156
+
157
+ # 页面表单元素列表
158
+ def form_fields_list_tag(form_builder, target_fields, target_model = nil)
159
+ (_model = target_model || model).blank? and return
160
+ (_decorator = _model.decorator).blank? and return
161
+ (_fields = _decorator.try(target_fields)).blank? and return
162
+
163
+ if _fields.is_a?(Hash)
164
+ _fields.collect { |profile, items|
165
+ _html = []
166
+ _html << content_tag(:h3, t("profiles.#{profile}"), class: 'lead clearfix')
167
+ _html << content_tag(:ul, class: 'row list-unstyled') do
168
+ items.collect { |field, partial, options|
169
+ render "admin/shared/fields/#{partial}", {f: form_builder, field: field, options: options || {}}
170
+ }.join.html_safe
171
+ end
172
+ _html.join.html_safe
173
+ }.join.html_safe
174
+ elsif _fields.is_a?(Array)
175
+ _fields.collect { |field, partial, options|
176
+ render "admin/shared/fields/#{partial}", {f: form_builder, field: field, options: options || {}}
177
+ }.join.html_safe
178
+ else
179
+ end
180
+ end
181
+
182
+ # 添加页面表单元素列表
183
+ def new_fields_list_tag(form_builder, target_model = nil)
184
+ form_fields_list_tag(form_builder, :new_fields, target_model)
185
+ end
186
+
187
+ # 编辑页面表单元素列表
188
+ def edit_fields_list_tag(form_builder, target_model = nil)
189
+ form_fields_list_tag(form_builder, :edit_fields, target_model)
190
+ end
191
+
192
+ # 表单页脚
193
+ def form_footer_tag(form_builder)
194
+ html = []
195
+ html << content_tag(:h3, nil, class: 'lead clearfix')
196
+ html << content_tag(:div, class: 'row') do
197
+ content_tag(:div, class: 'col-sm-12') do
198
+ _html = []
199
+ _html << form_builder.button(type: 'submit', name: nil, class: 'btn-icon btn btn-info') do
200
+ '<i class="glyphicon glyphicon-check"></i><span>提交</span>'.html_safe
201
+ end
202
+ _html << form_builder.button(type: 'reset', name: nil, class: 'btn-icon btn btn-warning') do
203
+ '<i class="glyphicon glyphicon-repeat"></i><span>重置</span>'.html_safe
204
+ end
205
+ _html.join.html_safe
206
+ end
207
+ end
208
+ html.join.html_safe
209
+ end
210
+
211
+ # # 返回列表
212
+ # def back_to_list_tag(target_model = nil)
213
+ # (_model = target_model || model).blank? and return
214
+ # link_to [:admin, _model] do
215
+ # ['<i class="fa fa-angle-double-left"></i>', t('action.back_to_list')].join(' ').html_safe
216
+ # end
217
+ # end
218
+
219
+ # # 详情页表内容
220
+ # def show_table_tbody_tag(record, target_fields = nil)
221
+ # _model, _fields = record.class, target_fields || :show_fields
222
+ # (_decorator = _model.decorator).blank? and return
223
+ #
224
+ # trs = []
225
+ # content_tag(:tbody) do
226
+ # _decorator.try(_fields).each do |field|
227
+ # trs << content_tag(:tr) do
228
+ # [content_tag(:td, _model.human_attribute_name(field), class: 'mnw150', width: '150px'),
229
+ # content_tag(:td, record.human_field(field))].join.html_safe
230
+ # end
231
+ # end
232
+ #
233
+ # trs.join.html_safe
234
+ # end
235
+ # end
236
+ end
237
+ end
@@ -0,0 +1,69 @@
1
+ module Admin
2
+ module ShowViewHelper
3
+ delegate :model, to: :@template
4
+
5
+ def build_view_for(attribute_name)
6
+ fields_hash = decorator.try(attribute_name) || {}
7
+ profiles = fields_hash.fetch(:profiles, [])
8
+
9
+ form_fields = if profiles.present?
10
+ profiles.collect { |profile|
11
+ fields = fields_hash.fetch(profile.to_sym, {})
12
+ content = []
13
+ content << content_tag(:h3, I18n.t("profiles.#{profile}", default: profile), class: 'lead clearfix')
14
+ content << build_fields_for(decorator, fields)
15
+ content.join.html_safe
16
+ }.join.html_safe
17
+ else
18
+ build_fields_for(decorator, fields_hash)
19
+ end
20
+
21
+ [build_action_buttons, form_fields].join.html_safe
22
+ end
23
+
24
+ def build_action_buttons
25
+ edit_btn = Array(decorate.class.permitted_instance_methods_for(action_name)).collect { |action|
26
+ render_to_string("actions/#{action}", locals: {record: decorate.object}, layout: false)
27
+ }.join("\n").html_safe
28
+
29
+ # edit_btn = link_to t('actions.edit'), [scope, record, action: 'edit'], class: 'btn btn-primary'
30
+ content_tag(:div, edit_btn, class: 'pull-right')
31
+ end
32
+
33
+ def build_fields_for(decorator, fields)
34
+ content_tag(:div, class: 'row detail-info clearfix') do
35
+ content_tag(:ul, class: 'col-sm-12 col-md-12 list-unstyled') do
36
+ fields.each do |field_name|
37
+ options = decorator.try(:attribute_type_for, field_name, true) || {}
38
+ concat(render_view(field_name, options))
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ def render_view(method, options = {})
45
+ field_wrapper(method, options) do
46
+ decorate.human(method)
47
+ end
48
+ end
49
+
50
+ def field_wrapper(method, options = {}, &block)
51
+ label_options = options.extract!(:label_class, :required, :block)
52
+ is_block = label_options[:block]
53
+ options[:class] ||= 'form-control'
54
+ options.merge!(id: '_form_id_'.concat(method))
55
+
56
+ content_tag(:li, class: is_block ? 'col-sm-12 col-md-12' : 'col-sm-6 col-md-6') do
57
+ content = [content_tag(:b, [model.human_attribute_name(method), ":"].join, default: method)]
58
+ if is_block
59
+ content << content_tag(:p, yield(block))
60
+ else
61
+ content << content_tag(:span, yield(block))
62
+ end
63
+ content.join.html_safe
64
+ end
65
+
66
+ end
67
+
68
+ end
69
+ end
@@ -0,0 +1,104 @@
1
+ module Admin
2
+ module TagsHelper
3
+ def breadcrumb_tag(controller, action_name)
4
+ tag_1 = link_to(controller.controller_name_human, url_for(controller: controller_path))
5
+ tag_2 = link_to(controller.action_name_human(action_name), 'javascript:;', class: 'active')
6
+ tag_1.concat(tag_2)
7
+ end
8
+
9
+ # 添加对象按钮
10
+ # TODO 添加权限
11
+ def new_model_tag(klass)
12
+ name = [t('actions.new'), klass.model_name.human].join
13
+ url = url_for(controller: klass.model_name.route_key, action: 'new')
14
+ html_options = {class: 'btn btn-primary clearfix'}
15
+ link_to(name, url, html_options)
16
+ end
17
+
18
+ def filter_tag(decorator, filter_key, filter_name)
19
+ button_text = [filter_name, content_tag(:span, nil, class: 'caret')].join("\n").html_safe
20
+ button_options = {type: 'button', name: filter_key}
21
+ button_html_options = {class: 'btn btn-default btn-sm', data: {toggle: 'dropdown'}, aria: {haspopup: true, expanded: false}}
22
+
23
+ content_tag(:div, class: 'pull-left dropdown filters') do
24
+ concat(button_tag(button_text, button_options.merge(button_html_options)))
25
+ concat(content_tag(:ul, decorator.filter_for(filter_key), class: 'dropdown-menu'))
26
+ end
27
+ end
28
+
29
+ # 筛选栏
30
+ def collection_filters_tag(decorator, filters_name)
31
+ Array(decorator.try(filters_name)).each do |filter_key, filter_name|
32
+ concat(filter_tag(decorator, filter_key, filter_name))
33
+ end
34
+
35
+ # 这里要返回nil,因为用的each 有额外的返回值
36
+ nil
37
+ end
38
+
39
+ # 搜索栏
40
+ def collection_search_tag(decorator, search_name)
41
+ (search_options = decorator.try(search_name)).blank? and return
42
+
43
+ name, placeholder, action = search_options.values_at(:name, :placeholder, :action)
44
+ form_tag(action, class: 'pull-right collection_search', method: :get, enforce_utf8: false) do
45
+ concat(text_field_tag(name, params[name], placeholder: placeholder))
46
+ concat(button_tag(content_tag(:i, nil, class: 'glyphicon glyphicon-search'), type: nil, name: nil))
47
+ end
48
+ end
49
+
50
+ # 列表栏表头
51
+ def table_thead_tag(model, decorator, attribute_name)
52
+ (collection_attributes = decorator.try(attribute_name)).blank? and return
53
+
54
+ content_tag(:thead) do
55
+ content_tag(:tr) do
56
+ collection_attributes.map { |field| concat(content_tag(:th, model.human_attribute_name(field))) }
57
+ concat(content_tag(:th, t('action'))) if decorator.permitted_instance_methods_for(action_name)
58
+ end
59
+ end
60
+ end
61
+
62
+ def render_class_actions(decorator)
63
+ Array(decorator.permitted_class_methods_for(action_name)).collect { |action|
64
+ render_to_string("actions/#{action}", layout: false)
65
+ }.join("\n").html_safe
66
+ end
67
+
68
+ # 列表页操作按钮
69
+ def render_instance_actions(decorate)
70
+ Array(decorate.class.permitted_instance_methods_for(action_name)).collect { |action|
71
+ render_to_string("actions/index_#{action}", locals: {record: decorate.object}, layout: false)
72
+ }.join("\n").html_safe
73
+ end
74
+
75
+ # 列表页数据内容
76
+ def table_tbody_tag(records, decorator, attribute_name, options)
77
+ per_page = options.delete(:per_page)
78
+ content_tag(:tbody) do
79
+ records.each_with_index do |record, index|
80
+ decorate = decorator.new(record)
81
+
82
+ tds = Array(decorate.try(attribute_name)).collect do |field|
83
+ td_val = field == '#' ? (( records.current_page - 1 ) * (per_page || records.default_per_page)) + index + 1 : decorate.human(field)
84
+ content_tag(:td, td_val)
85
+ end.join("\n").html_safe
86
+ tds.concat(content_tag(:td, render_instance_actions(decorate))) if decorator.permitted_instance_methods_for(action_name)
87
+ concat(content_tag(:tr, tds, 'data-id' => record.id))
88
+ end
89
+ end
90
+ end
91
+
92
+ # 列表table
93
+ def collection_table_tag(records, decorator, attribute_name, options = {})
94
+ content_tag(:table, class: 'table table-hover', id: decorator.model.name.downcase.pluralize) do
95
+ concat(table_thead_tag(records.model, decorator, attribute_name))
96
+ concat(table_tbody_tag(records, decorator, attribute_name, options))
97
+ concat(hidden_field_tag(:per_page, records.limit_value))
98
+ concat(hidden_field_tag(:current_page, records.current_page))
99
+ concat(hidden_field_tag(:is_last_page, records.last_page?))
100
+ concat(hidden_field_tag(:total_pages, records.total_pages))
101
+ end
102
+ end
103
+ end
104
+ end
@@ -0,0 +1,121 @@
1
+ module ActiveRecord
2
+ class SearchService
3
+ class Search
4
+ attr_reader :search_in, :param_key, :fuzzy, :value
5
+
6
+ def initialize(params, search)
7
+ @search_in = search[:in] || []
8
+ @param_key = search[:param_key]
9
+ @fuzzy = search[:fuzzy]
10
+ @value = params.delete(param_key)
11
+ end
12
+
13
+ def apply(relation)
14
+ return relation if value.blank?
15
+
16
+ values = []
17
+ if fuzzy
18
+ query = search_in.collect do |attr|
19
+ # if attr.include? '.'
20
+ # relation = relation.joins(attr.split('.').first.singularize)
21
+ # end
22
+
23
+ if relation.model.columns_hash[attr]&.array
24
+ values << value.to_s.downcase
25
+ "? = ANY (#{attr})"
26
+ else
27
+ values << "%#{value.to_s.downcase}%"
28
+ "#{attr} iLIKE ?"
29
+ end
30
+ end.join(' OR ')
31
+ else
32
+ query = search_in.collect do |attr|
33
+ # if attr.include? '.'
34
+ # relation = relation.joins(attr.split('.').first.singularize)
35
+ # end
36
+
37
+ if relation.model.columns_hash[attr]&.array
38
+ "? = ANY (#{attr})"
39
+ else
40
+ "lower(#{attr}) = ?"
41
+ end
42
+ end.join(' OR ')
43
+ values = [value.to_s.downcase] * search_in.count
44
+ end
45
+
46
+ relation.where(query, *values)
47
+ end
48
+ end
49
+
50
+ class Filter
51
+ attr_reader :filters
52
+
53
+ def initialize(filters)
54
+ @filters = filters
55
+ end
56
+
57
+ def apply(relation)
58
+ filters.present? ? relation.where(filters) : relation
59
+ end
60
+ end
61
+
62
+ class Order
63
+ attr_reader :attribute, :direction, :order_str
64
+
65
+ def initialize(attribute, direction, order_str)
66
+ @attribute = attribute
67
+ @direction = direction
68
+ @order_str = order_str
69
+ end
70
+
71
+ def apply(relation)
72
+ if order_str.present?
73
+ relation.order(order_str)
74
+ elsif attribute.present?
75
+ relation.order(attribute => direction)
76
+ else
77
+ relation
78
+ end
79
+ end
80
+ end
81
+
82
+ class Paginate
83
+ attr_reader :page, :per_page
84
+
85
+ def initialize(page, per_page)
86
+ @page = page
87
+ @per_page = per_page
88
+ end
89
+
90
+ def apply(relation)
91
+ relation.page(page).per(per_page)
92
+ end
93
+ end
94
+
95
+ attr_reader :model, :params, :options, :search, :filters, :order, :paginate
96
+
97
+ def initialize(model, original_params, options = {})
98
+ @model = model
99
+ @params = original_params.deep_dup.with_indifferent_access
100
+ @options = options
101
+
102
+ _order_str = options.delete(:order_str) || params.delete(:order_str)
103
+ _order = options.delete(:order) || params.delete(:order) || 'id'
104
+ _direction = options.delete(:direction) || params.delete(:direction) || 'desc'
105
+ _per_page = options.delete(:per_page) || params.delete(:per_page)
106
+
107
+ @search = Search.new(params, options.delete(:search) || {})
108
+ @filters = Filter.new(params.delete(:filters) || {})
109
+ @order = Order.new(_order, _direction, _order_str)
110
+ @paginate = Paginate.new(params.delete(:page), _per_page)
111
+ end
112
+
113
+ def run
114
+ resources = model.where(params.slice(*model.column_names.collect(&:to_sym))).where(options)
115
+ [search, filters, order, paginate].each do |operation|
116
+ resources = operation.apply(resources)
117
+ end
118
+ resources
119
+ end
120
+ end
121
+ end