sofav 0.2.0

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