symphonia 2.1.7
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +674 -0
- data/README.md +16 -0
- data/Rakefile +44 -0
- data/app/assets/images/bg-checker.png +0 -0
- data/app/assets/images/bullet.gif +0 -0
- data/app/assets/images/close.png +0 -0
- data/app/assets/images/loading.gif +0 -0
- data/app/assets/images/next.png +0 -0
- data/app/assets/images/prev.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/autogrow/plugin.js +232 -0
- data/app/assets/javascripts/ckeditor/plugins/autogrow/samples/autogrow.html +102 -0
- data/app/assets/javascripts/ckeditor/plugins/image_chooser/icons/addimage.png +0 -0
- data/app/assets/javascripts/ckeditor/plugins/image_chooser/plugin.js +15 -0
- data/app/assets/javascripts/symphonia/Sortable.js +1249 -0
- data/app/assets/javascripts/symphonia/_core.js +45 -0
- data/app/assets/javascripts/symphonia/application.js.erb +147 -0
- data/app/assets/javascripts/symphonia/filters.js +44 -0
- data/app/assets/javascripts/symphonia/symphonia_bootstrap_dialog.js +136 -0
- data/app/assets/javascripts/symphonia/symphonia_ckeditor.js +55 -0
- data/app/assets/stylesheets/symphonia/application.css +4 -0
- data/app/assets/stylesheets/symphonia/basic.scss +218 -0
- data/app/assets/stylesheets/symphonia/filters.scss +19 -0
- data/app/assets/stylesheets/symphonia/symphonia_bootstrap.scss +56 -0
- data/app/channels/application_cable/channel.rb +5 -0
- data/app/controllers/symphonia/accounts_controller.rb +169 -0
- data/app/controllers/symphonia/admin_controller.rb +22 -0
- data/app/controllers/symphonia/api_controller.rb +64 -0
- data/app/controllers/symphonia/application_controller.rb +8 -0
- data/app/controllers/symphonia/attachments_controller.rb +37 -0
- data/app/controllers/symphonia/filters_controller.rb +23 -0
- data/app/controllers/symphonia/images_controller.rb +16 -0
- data/app/controllers/symphonia/login_controller.rb +80 -0
- data/app/controllers/symphonia/roles_controller.rb +100 -0
- data/app/controllers/symphonia/user_sessions_controller.rb +16 -0
- data/app/controllers/symphonia/users_controller.rb +168 -0
- data/app/helpers/symphonia/application_helper.rb +422 -0
- data/app/helpers/symphonia/bootstrap_modal_helper.rb +59 -0
- data/app/helpers/symphonia/form_helper.rb +50 -0
- data/app/helpers/symphonia/renderer_helper.rb +85 -0
- data/app/mailers/symphonia/application_mailer.rb +6 -0
- data/app/mailers/symphonia/notifier.rb +42 -0
- data/app/models/symphonia/admin_module.rb +18 -0
- data/app/models/symphonia/application_record.rb +14 -0
- data/app/models/symphonia/attachment.rb +16 -0
- data/app/models/symphonia/common_file.rb +9 -0
- data/app/models/symphonia/email_preference.rb +5 -0
- data/app/models/symphonia/image.rb +46 -0
- data/app/models/symphonia/preference.rb +12 -0
- data/app/models/symphonia/role.rb +55 -0
- data/app/models/symphonia/swagger/error_model.rb +19 -0
- data/app/models/symphonia/swagger/responses.rb +27 -0
- data/app/models/symphonia/user.rb +141 -0
- data/app/models/symphonia/user_session.rb +19 -0
- data/app/views/common/403.html.erb +5 -0
- data/app/views/common/404.html.erb +2 -0
- data/app/views/layouts/symphonia/_modal.html.erb +19 -0
- data/app/views/layouts/symphonia/_query.html.erb +51 -0
- data/app/views/layouts/symphonia/application.html.erb +45 -0
- data/app/views/layouts/symphonia/application.pdf.erb +15 -0
- data/app/views/layouts/symphonia/mailer.html.erb +13 -0
- data/app/views/symphonia/accounts/_detail.html.erb +65 -0
- data/app/views/symphonia/accounts/_form.html.erb +14 -0
- data/app/views/symphonia/accounts/edit.html.erb +9 -0
- data/app/views/symphonia/accounts/edit.js.erb +5 -0
- data/app/views/symphonia/accounts/lost_password.html.erb +6 -0
- data/app/views/symphonia/accounts/lost_password.js.erb +3 -0
- data/app/views/symphonia/accounts/new_activation.html.erb +11 -0
- data/app/views/symphonia/accounts/new_activation.js.erb +6 -0
- data/app/views/symphonia/accounts/register.html.erb +20 -0
- data/app/views/symphonia/accounts/reset_password.html.erb +18 -0
- data/app/views/symphonia/accounts/update.js.erb +1 -0
- data/app/views/symphonia/admin/index.html.erb +15 -0
- data/app/views/symphonia/attachments/destroy.js.erb +1 -0
- data/app/views/symphonia/common/_editable_images_grid.html.erb +12 -0
- data/app/views/symphonia/common/_filters.html.erb +23 -0
- data/app/views/symphonia/common/_locale_chooser.html.erb +16 -0
- data/app/views/symphonia/common/_share_links.html.erb +5 -0
- data/app/views/symphonia/filters/options.html.erb +36 -0
- data/app/views/symphonia/filters/options.js.erb +9 -0
- data/app/views/symphonia/filters/table.html.erb +21 -0
- data/app/views/symphonia/login/_form.html.erb +19 -0
- data/app/views/symphonia/login/new.html.erb +11 -0
- data/app/views/symphonia/login_sessions/new.html.erb +1 -0
- data/app/views/symphonia/notifier/activation_user.html.erb +7 -0
- data/app/views/symphonia/notifier/activation_user.text.erb +3 -0
- data/app/views/symphonia/notifier/reset_password_user.html.erb +7 -0
- data/app/views/symphonia/notifier/reset_password_user.text.erb +3 -0
- data/app/views/symphonia/notifier/test_mail.html.erb +2 -0
- data/app/views/symphonia/notifier/test_mail.text.erb +3 -0
- data/app/views/symphonia/notifier/user_change_to_active.html.erb +3 -0
- data/app/views/symphonia/notifier/user_change_to_active.text.erb +1 -0
- data/app/views/symphonia/notifier/user_registered.html.erb +13 -0
- data/app/views/symphonia/notifier/user_registered.text.erb +8 -0
- data/app/views/symphonia/roles/_form.html.erb +30 -0
- data/app/views/symphonia/roles/edit.html.erb +5 -0
- data/app/views/symphonia/roles/index.html.erb +6 -0
- data/app/views/symphonia/roles/new.html.erb +4 -0
- data/app/views/symphonia/roles/show.html.erb +5 -0
- data/app/views/symphonia/users/_form.html.erb +13 -0
- data/app/views/symphonia/users/edit.html.erb +26 -0
- data/app/views/symphonia/users/edit.js.erb +3 -0
- data/app/views/symphonia/users/edit_current.html.erb +7 -0
- data/app/views/symphonia/users/index.html.erb +5 -0
- data/app/views/symphonia/users/new.html.erb +8 -0
- data/app/views/symphonia/users/show.html.erb +63 -0
- data/config/locales/cs.yml +233 -0
- data/config/locales/en.yml +47 -0
- data/config/routes.rb +52 -0
- data/db/migrate/20130714140500_create_users.rb +49 -0
- data/db/migrate/20130714140501_create_roles.rb +16 -0
- data/db/migrate/20130714140502_create_preferences.rb +26 -0
- data/db/migrate/20130828175114_create_attachments.rb +20 -0
- data/db/migrate/20141213204351_create_admin_modules.rb +20 -0
- data/db/seeds.rb +12 -0
- data/lib/generators/symphonia/entity_controller/entity_controller_generator.rb +48 -0
- data/lib/generators/symphonia/entity_controller/templates/controller.rb +100 -0
- data/lib/generators/symphonia/query/query_generator.rb +37 -0
- data/lib/generators/symphonia/setup/setup_generator.rb +52 -0
- data/lib/generators/symphonia/setup/templates/404.html +26 -0
- data/lib/generators/symphonia/setup/templates/500.html +37 -0
- data/lib/generators/symphonia/setup/templates/Gemfile +18 -0
- data/lib/generators/symphonia/setup/templates/base_layout.html.erb +46 -0
- data/lib/generators/symphonia/setup/templates/design.scss +4 -0
- data/lib/generators/symphonia/setup/templates/settings.rb +65 -0
- data/lib/generators/symphonia/setup/templates/spec_helper.rb +18 -0
- data/lib/symphonia/action_cable/connection.rb +31 -0
- data/lib/symphonia/admin_constraint.rb +9 -0
- data/lib/symphonia/attachable.rb +35 -0
- data/lib/symphonia/base_controller.rb +96 -0
- data/lib/symphonia/bootstrap_link_render.rb +69 -0
- data/lib/symphonia/controller_extensions.rb +200 -0
- data/lib/symphonia/engine.rb +137 -0
- data/lib/symphonia/form_builder.rb +137 -0
- data/lib/symphonia/menu_manager.rb +23 -0
- data/lib/symphonia/model_attributes/attribute.rb +137 -0
- data/lib/symphonia/model_attributes.rb +102 -0
- data/lib/symphonia/model_filters/base.rb +82 -0
- data/lib/symphonia/model_filters/boolean_filter.rb +26 -0
- data/lib/symphonia/model_filters/date_filter.rb +81 -0
- data/lib/symphonia/model_filters/integer_filter.rb +18 -0
- data/lib/symphonia/model_filters/select_filter.rb +48 -0
- data/lib/symphonia/model_filters/string_filter.rb +18 -0
- data/lib/symphonia/model_filters.rb +10 -0
- data/lib/symphonia/object.rb +31 -0
- data/lib/symphonia/permissions.rb +93 -0
- data/lib/symphonia/query.rb +275 -0
- data/lib/symphonia/query_columns/attribute_column.rb +43 -0
- data/lib/symphonia/query_columns/generic_column.rb +165 -0
- data/lib/symphonia/query_columns.rb +8 -0
- data/lib/symphonia/spec_helper.rb +4 -0
- data/lib/symphonia/user_management.rb +58 -0
- data/lib/symphonia/version.rb +4 -0
- data/lib/symphonia.rb +20 -0
- data/spec/controllers/account_controller_spec.rb +90 -0
- data/spec/controllers/admin_controller_spec.rb +35 -0
- data/spec/controllers/api_controller_spec.rb +9 -0
- data/spec/controllers/filters_controller_spec.rb +35 -0
- data/spec/controllers/images_controller_spec.rb +5 -0
- data/spec/controllers/login_controller_spec.rb +20 -0
- data/spec/controllers/roles_controller_spec.rb +12 -0
- data/spec/controllers/users_controller_spec.rb +47 -0
- data/spec/factories/factories.rb +52 -0
- data/spec/helpers/symphonia/application_helper_spec.rb +62 -0
- data/spec/mailers/previews/symphonia/notifier_preview.rb +27 -0
- data/spec/mailers/symphonia/notifier_spec.rb +76 -0
- data/spec/models/attachment_spec.rb +22 -0
- data/spec/models/query/attribute_spec.rb +8 -0
- data/spec/models/query/symphonia_query_spec.rb +70 -0
- data/spec/models/role_spec.rb +13 -0
- data/spec/models/user_spec.rb +10 -0
- data/spec/rails_helper.rb +13 -0
- data/spec/requests/accounts_spec.rb +118 -0
- data/spec/requests/attachments_controller_spec.rb +23 -0
- data/spec/requests/login_spec.rb +26 -0
- data/spec/requests/roles_spec.rb +10 -0
- data/spec/requests/users_spec.rb +50 -0
- data/spec/spec_helper.rb +101 -0
- data/spec/support/common_file.txt +2 -0
- data/spec/support/query.rb +36 -0
- data/spec/support/shared.rb +62 -0
- data/spec/support/shared_controllers.rb +31 -0
- data/spec/support/stub_users.rb +32 -0
- data/spec/support/symphonia.jpg +0 -0
- data/spec/support/wait_for_ajax.rb +15 -0
- data/spec/version_spec.rb +5 -0
- data/spec/views/filters/options.html.erb_spec.rb +14 -0
- metadata +697 -0
@@ -0,0 +1,275 @@
|
|
1
|
+
module Symphonia
|
2
|
+
class SymphoniaQueryNotRegistered < StandardError
|
3
|
+
end
|
4
|
+
class FilterNotFound < StandardError
|
5
|
+
end
|
6
|
+
|
7
|
+
class Query
|
8
|
+
attr_reader :set_filter, :controller
|
9
|
+
attr_writer :entity_scope, :filters
|
10
|
+
attr_accessor :disable_search, :active_filters, :options, :default_filters
|
11
|
+
attr_accessor :operators
|
12
|
+
|
13
|
+
class_attribute :model
|
14
|
+
|
15
|
+
# @param [nil, String] sort
|
16
|
+
# @param [Hash] filters
|
17
|
+
# @param [nil, Array] columns
|
18
|
+
def initialize(c = nil, sort: "", filters: {}, columns: nil, **options)
|
19
|
+
if c
|
20
|
+
ActiveSupport::Deprecation.warn("initialize allow only hash. #{c} - update your controller:")
|
21
|
+
#
|
22
|
+
# model.query.new sort: "default sort", filters: {default: filter}, columns: [columns, names]
|
23
|
+
# @query.from_params params
|
24
|
+
#
|
25
|
+
end
|
26
|
+
if options[:default_order]
|
27
|
+
ActiveSupport::Deprecation.warn("default_order is deprecated, use just `sort`")
|
28
|
+
end
|
29
|
+
@active_filters = {}
|
30
|
+
@default_sort = sort
|
31
|
+
@default_filters = filters
|
32
|
+
@default_column_names = columns
|
33
|
+
@options = options
|
34
|
+
end
|
35
|
+
|
36
|
+
def from_params(params)
|
37
|
+
@sort_col = params[:sort]
|
38
|
+
@sort_dir = params[:direction]
|
39
|
+
self.entity_scope = entity.like(@q) if (@q = params[:q].presence)
|
40
|
+
if (@set_filter = params[:set_filter].to_boolean)
|
41
|
+
setup_filters(params)
|
42
|
+
end
|
43
|
+
setup_columns(params)
|
44
|
+
end
|
45
|
+
|
46
|
+
protected
|
47
|
+
|
48
|
+
def setup_filters(params)
|
49
|
+
available_filters.each do |filter|
|
50
|
+
if (q = params[filter.name])
|
51
|
+
if q.blank?
|
52
|
+
active_filters.delete(filter.name)
|
53
|
+
else
|
54
|
+
operators.delete(filter.name)
|
55
|
+
active_filters[filter.name] = q.try(:downcase) || q
|
56
|
+
if (o = params[:o])
|
57
|
+
operators[filter.name] = o[filter.name]
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def setup_columns(params)
|
65
|
+
if (c = params[:column_names]).present?
|
66
|
+
self.column_names = c
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
public
|
71
|
+
|
72
|
+
def sort_column
|
73
|
+
c, d = @default_sort.split(':')
|
74
|
+
c ||= available_columns[:name]&.name
|
75
|
+
@sort_table ||= SortableTable::SortTable.new(available_columns.collect { |_n, c| c.sort_definition }.compact,
|
76
|
+
default_column: c&.to_sym,
|
77
|
+
default_direction: d&.to_sym)
|
78
|
+
@sort_column ||= @sort_table.sort_column(@sort_col, @sort_dir)
|
79
|
+
rescue StandardError
|
80
|
+
raise "Incorrect default column: '#{options[:default_sort]}'"
|
81
|
+
end
|
82
|
+
|
83
|
+
def search?
|
84
|
+
disable_search.nil? && model.respond_to?(:like)
|
85
|
+
end
|
86
|
+
|
87
|
+
def activate_filter(name, value)
|
88
|
+
@active_filters[name] ||= value.downcase
|
89
|
+
end
|
90
|
+
|
91
|
+
alias add_filter activate_filter
|
92
|
+
|
93
|
+
def entity
|
94
|
+
model
|
95
|
+
end
|
96
|
+
|
97
|
+
def column_names=(names)
|
98
|
+
@column_names = (names.is_a?(Array) ? names : names.to_s.split('|')).map(&:to_sym)
|
99
|
+
end
|
100
|
+
|
101
|
+
def column_names
|
102
|
+
@column_names ||= default_column_names
|
103
|
+
end
|
104
|
+
|
105
|
+
def columns(all = false)
|
106
|
+
return available_columns.values if all || Array(column_names).blank?
|
107
|
+
|
108
|
+
@columns ||= column_names.map { |c| available_columns[c] }.compact
|
109
|
+
end
|
110
|
+
|
111
|
+
def render_filters
|
112
|
+
(available_filters | filters).each do |filter|
|
113
|
+
yield filter
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
def entity_scope
|
118
|
+
@entity_scope || model
|
119
|
+
end
|
120
|
+
|
121
|
+
def filters
|
122
|
+
setup_filters(default_filters) unless @set_filter
|
123
|
+
@filters ||= available_filters.select do |filter|
|
124
|
+
if (q = active_filters[filter.name])
|
125
|
+
operators && (filter.operator = operators[filter.name])
|
126
|
+
filter.value = q
|
127
|
+
end
|
128
|
+
end
|
129
|
+
@filters
|
130
|
+
end
|
131
|
+
|
132
|
+
def scope
|
133
|
+
opts = columns.each_with_object(includes: [], preload: [], joins: []) do |var, mem|
|
134
|
+
mem[:includes].concat var.includes
|
135
|
+
mem[:preload].concat var.preload
|
136
|
+
mem[:joins].concat var.joins
|
137
|
+
end
|
138
|
+
x = entity_scope.includes(opts[:includes].uniq).preload(opts[:preload].uniq).joins(opts[:joins].uniq)
|
139
|
+
filters.each do |filter|
|
140
|
+
x = filter.apply(x)
|
141
|
+
end
|
142
|
+
x
|
143
|
+
end
|
144
|
+
|
145
|
+
def entities
|
146
|
+
return @entities unless @entities.nil?
|
147
|
+
|
148
|
+
order = sort_column.order
|
149
|
+
@entities = scope.order(order.is_a?(String) && Arel.sql(order) || order)
|
150
|
+
end
|
151
|
+
|
152
|
+
def entity_count
|
153
|
+
entities.reorder(nil).count
|
154
|
+
end
|
155
|
+
|
156
|
+
def summarize_columns
|
157
|
+
columns.select(&:summarize?)
|
158
|
+
end
|
159
|
+
|
160
|
+
def to_partial_path
|
161
|
+
'symphonia/common/query'
|
162
|
+
end
|
163
|
+
|
164
|
+
def to_params(force = false)
|
165
|
+
if @q
|
166
|
+
{ q: @q }
|
167
|
+
elsif @set_filter || force
|
168
|
+
filters.each_with_object(set_filter: 1) { |var, mem| f, v = var; mem[f.send(:form_field_name).to_sym] = v; }
|
169
|
+
else
|
170
|
+
{}
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
def inspect
|
175
|
+
"#<#{self.class.name} set_filter=#{@set_filter} >"
|
176
|
+
end
|
177
|
+
|
178
|
+
|
179
|
+
def available_filters
|
180
|
+
self.active_filters ||= {}
|
181
|
+
self.operators ||= {}
|
182
|
+
|
183
|
+
if @available_filters.nil?
|
184
|
+
@available_filters = []
|
185
|
+
register_filters
|
186
|
+
end
|
187
|
+
@available_filters
|
188
|
+
end
|
189
|
+
|
190
|
+
private
|
191
|
+
|
192
|
+
# @param [Symbol] filter_type
|
193
|
+
# @param [Symphonia::ModelAttributes::Attribute] attribute
|
194
|
+
# @param [Hash] options
|
195
|
+
def register_filter(filter_type, attribute, options = {})
|
196
|
+
@available_filters ||= []
|
197
|
+
klass = "Symphonia::ModelFilters::#{filter_type.to_s.classify}Filter".safe_constantize
|
198
|
+
return if klass.nil?
|
199
|
+
|
200
|
+
filter = klass.new(attribute, self, options)
|
201
|
+
yield filter if block_given?
|
202
|
+
@available_filters << filter
|
203
|
+
end
|
204
|
+
|
205
|
+
# def default_filter
|
206
|
+
# {}
|
207
|
+
# end
|
208
|
+
|
209
|
+
def register_filters
|
210
|
+
model.registered_attributes.each do |_name, attribute|
|
211
|
+
register_filter(attribute.filter, attribute) if attribute.filter
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
def available_columns
|
216
|
+
if @available_columns.nil?
|
217
|
+
@available_columns = {}
|
218
|
+
register_columns
|
219
|
+
else
|
220
|
+
@available_columns
|
221
|
+
end
|
222
|
+
@available_columns
|
223
|
+
end
|
224
|
+
|
225
|
+
def default_column_names
|
226
|
+
if @default_column_names.nil?
|
227
|
+
@default_column_names = []
|
228
|
+
available_columns.each { |k, v| v.default? && @default_column_names << k }
|
229
|
+
end
|
230
|
+
@default_column_names
|
231
|
+
end
|
232
|
+
|
233
|
+
def add_column(_type, name, options = {})
|
234
|
+
@available_columns[name] = "Symphonia::QueryColumns::#{_type.to_s.classify}Column".constantize.new(name, self, options)
|
235
|
+
end
|
236
|
+
|
237
|
+
# aliases
|
238
|
+
|
239
|
+
def add_generic_column(name, options = {})
|
240
|
+
add_column(:generic, name, options)
|
241
|
+
end
|
242
|
+
|
243
|
+
def add_text_column(name, options = {})
|
244
|
+
add_column(:text, name, options)
|
245
|
+
end
|
246
|
+
|
247
|
+
def add_price_column(name, options = {})
|
248
|
+
add_column(:price, name, options)
|
249
|
+
end
|
250
|
+
|
251
|
+
def add_date_column(name, options = {})
|
252
|
+
add_column(:date, name, options)
|
253
|
+
end
|
254
|
+
|
255
|
+
def add_boolean_column(name, options = {})
|
256
|
+
add_column(:boolean, name, options)
|
257
|
+
end
|
258
|
+
#
|
259
|
+
def add_attribute_column(attribute, options = {})
|
260
|
+
@available_columns[attribute.name] = ::Symphonia::QueryColumns::AttributeColumn.new(attribute, self, options)
|
261
|
+
end
|
262
|
+
|
263
|
+
protected
|
264
|
+
|
265
|
+
def session_key(name)
|
266
|
+
[self.class.name.hash, name].join('-')
|
267
|
+
end
|
268
|
+
|
269
|
+
def register_columns
|
270
|
+
model.registered_attributes.values.each do |attribute|
|
271
|
+
add_attribute_column(attribute)
|
272
|
+
end
|
273
|
+
end
|
274
|
+
end
|
275
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Symphonia
|
2
|
+
module QueryColumns
|
3
|
+
class AttributeColumn < GenericColumn
|
4
|
+
|
5
|
+
attr_reader :attribute
|
6
|
+
attr_accessor :no_header, :sort_options
|
7
|
+
attr_writer :header, :sort
|
8
|
+
|
9
|
+
delegate :default?, :title, to: :attribute
|
10
|
+
|
11
|
+
def initialize(attribute, query, options={})
|
12
|
+
@attribute = attribute
|
13
|
+
@name = attribute.name
|
14
|
+
@query = query
|
15
|
+
@model = query.model
|
16
|
+
@options = options
|
17
|
+
|
18
|
+
@includes = options.delete(:includes) || reference? && [@name] || []
|
19
|
+
@preload = options.delete(:preload) || reference? && [@name] || []
|
20
|
+
@joins = Array(options.delete(:joins)) || []
|
21
|
+
@sort_definition = attribute.sort_column
|
22
|
+
@sort = attribute.sort?
|
23
|
+
end
|
24
|
+
|
25
|
+
def reference?
|
26
|
+
@attribute.is_a?(Symphonia::ModelAttributes::ReferenceAttribute)
|
27
|
+
end
|
28
|
+
|
29
|
+
# def header(table_header_tag)
|
30
|
+
# if @header === false
|
31
|
+
# table_header_tag.th(@name, '', sort: false)
|
32
|
+
# else
|
33
|
+
# table_header_tag.th(@name, title, {sort: @sort.nil?, sort_options: (sort_options || {}).merge({column: sort_column_name})})
|
34
|
+
# end
|
35
|
+
# end
|
36
|
+
|
37
|
+
def format_value(view, entity)
|
38
|
+
@attribute.format(view, entity)
|
39
|
+
end
|
40
|
+
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,165 @@
|
|
1
|
+
module Symphonia
|
2
|
+
module QueryColumns
|
3
|
+
class GenericColumn
|
4
|
+
|
5
|
+
attr_reader :name, :query, :model, :includes, :preload, :joins
|
6
|
+
attr_writer :sort_column_name, :sort_definition
|
7
|
+
attr_accessor :sort, :header, :default
|
8
|
+
|
9
|
+
def initialize(name, klass = nil, options={})
|
10
|
+
::ActiveSupport::Deprecation.warn 'Maybe this class is over...'
|
11
|
+
@name = name
|
12
|
+
if klass.is_a? Symphonia::Query
|
13
|
+
@query = klass
|
14
|
+
@model = @query.model
|
15
|
+
elsif klass < ActiveRecord::Base
|
16
|
+
@model = klass
|
17
|
+
else # default fallback
|
18
|
+
@model = ActiveRecord::Base
|
19
|
+
end
|
20
|
+
@options = options
|
21
|
+
|
22
|
+
# @sort_options = @options.delete(:sort_options) || {}
|
23
|
+
self.header = @options.delete(:header).nil?
|
24
|
+
self.sort = @options.delete(:sort).nil?
|
25
|
+
|
26
|
+
@includes = Array(options.delete(:includes)) || []
|
27
|
+
@preload = Array(options.delete(:preload)) || []
|
28
|
+
@joins = Array(options.delete(:joins)) || []
|
29
|
+
end
|
30
|
+
|
31
|
+
def summarize?
|
32
|
+
false
|
33
|
+
end
|
34
|
+
|
35
|
+
def header?
|
36
|
+
!(@header === false)
|
37
|
+
end
|
38
|
+
|
39
|
+
def sort?
|
40
|
+
!(@sort === false)
|
41
|
+
end
|
42
|
+
|
43
|
+
def default?
|
44
|
+
!!@default
|
45
|
+
end
|
46
|
+
|
47
|
+
def header(view)
|
48
|
+
::ActiveSupport::Deprecation.warn 'Header method is deprecated -> use app/helpers/symphonia/renderer_helper.rb:59'
|
49
|
+
if @no_header || @header === false
|
50
|
+
title
|
51
|
+
else
|
52
|
+
view.symphonia_query_sort_tag(self)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def sort_definition
|
57
|
+
return nil unless sort?
|
58
|
+
@sort_definition ||= SortableTable::SortColumnCustomDefinition.new(@name, {asc: "#{sort_column_name} asc", desc: "#{sort_column_name} desc"})
|
59
|
+
end
|
60
|
+
|
61
|
+
def title
|
62
|
+
::ActiveSupport::Deprecation.warn 'method is deprecated'
|
63
|
+
@model.human_attribute_name(@name, @options[:i18n] || {})
|
64
|
+
end
|
65
|
+
|
66
|
+
def sort_options
|
67
|
+
@sort_options
|
68
|
+
end
|
69
|
+
|
70
|
+
def value(entity)
|
71
|
+
entity.send(name)
|
72
|
+
end
|
73
|
+
|
74
|
+
def format_value(view, entity)
|
75
|
+
if @options[:formatter].is_a?(Proc)
|
76
|
+
@options[:formatter].call(view, entity).to_s
|
77
|
+
else
|
78
|
+
case @options[:with_link]
|
79
|
+
when :entity, true
|
80
|
+
view.link_to(value(entity).to_s, entity)
|
81
|
+
when :value
|
82
|
+
view.link_to(value(entity).to_s, value(entity))
|
83
|
+
else
|
84
|
+
value(entity).to_s
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def sum_value(view)
|
90
|
+
format_value(view, OpenStruct.new(name => sum))
|
91
|
+
end
|
92
|
+
|
93
|
+
def sum
|
94
|
+
return nil unless summarize?
|
95
|
+
@sum ||= query.entities.sum(name)
|
96
|
+
end
|
97
|
+
|
98
|
+
def css_classes
|
99
|
+
@name.to_s
|
100
|
+
end
|
101
|
+
|
102
|
+
def inspect
|
103
|
+
"#<#{self.class.name} name='#{name}' options=#{@options.inspect}>"
|
104
|
+
end
|
105
|
+
|
106
|
+
private
|
107
|
+
|
108
|
+
def t(*args)
|
109
|
+
I18n.translate(*args)
|
110
|
+
end
|
111
|
+
|
112
|
+
def l(*args)
|
113
|
+
I18n.localize(*args)
|
114
|
+
end
|
115
|
+
|
116
|
+
def sort_column_name
|
117
|
+
@sort_column_name.presence || [@model.table_name, @name].join('.')
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
class TextColumn < GenericColumn
|
122
|
+
|
123
|
+
def format_value(view, entity)
|
124
|
+
view.format_text(super(view, entity))
|
125
|
+
end
|
126
|
+
|
127
|
+
end
|
128
|
+
|
129
|
+
class NumberColumn < GenericColumn
|
130
|
+
|
131
|
+
def summarize?
|
132
|
+
true
|
133
|
+
end
|
134
|
+
|
135
|
+
end
|
136
|
+
|
137
|
+
class PriceColumn < NumberColumn
|
138
|
+
|
139
|
+
def format_value(view, entity)
|
140
|
+
view.format_price(super(view, entity))
|
141
|
+
end
|
142
|
+
|
143
|
+
end
|
144
|
+
|
145
|
+
class DateColumn < GenericColumn
|
146
|
+
|
147
|
+
def format_value(view, entity)
|
148
|
+
value(entity) && l(value(entity), format: @options[:format])
|
149
|
+
end
|
150
|
+
|
151
|
+
end
|
152
|
+
|
153
|
+
class BooleanColumn < GenericColumn
|
154
|
+
|
155
|
+
def format_value(view, entity)
|
156
|
+
if value(entity)
|
157
|
+
view.fa_icon('true', title: t(:true))
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
end
|
162
|
+
|
163
|
+
end
|
164
|
+
|
165
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
module Symphonia
|
2
|
+
module UserManagement
|
3
|
+
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
included do
|
7
|
+
|
8
|
+
enum status: { active: 1, pending: 5, archived: 9 }
|
9
|
+
|
10
|
+
scope :sorted, -> { order(:first_name, :last_name) }
|
11
|
+
|
12
|
+
# validates :mail, uniqueness: true, format: { with: Authlogic::Regex::EMAIL_NONASCII }
|
13
|
+
# validates :password, presence: true, if: ->(u){ u.new_record? }
|
14
|
+
|
15
|
+
validates :login, :mail, :first_name, :last_name, presence: true
|
16
|
+
|
17
|
+
def self.current=(entity)
|
18
|
+
Rails.logger.info("* #{entity.class.name}: #{entity.login} id: #{entity.id}")
|
19
|
+
Thread.current[:"current_#{name.underscore}"] = entity
|
20
|
+
end
|
21
|
+
|
22
|
+
def self.current
|
23
|
+
Thread.current[:"current_#{name.underscore}"] ||= "#{self}::Anonymous".constantize.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def name
|
27
|
+
"#{first_name} #{last_name}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def status_new?
|
31
|
+
pending?
|
32
|
+
end
|
33
|
+
|
34
|
+
def lock?
|
35
|
+
!active?
|
36
|
+
end
|
37
|
+
|
38
|
+
alias_method :archive?, :lock?
|
39
|
+
|
40
|
+
def archive!
|
41
|
+
archived!
|
42
|
+
end
|
43
|
+
|
44
|
+
def unarchive!
|
45
|
+
active!
|
46
|
+
end
|
47
|
+
|
48
|
+
alias_method :activate!, :unarchive!
|
49
|
+
|
50
|
+
def css_classes
|
51
|
+
css = ''
|
52
|
+
css << ' -active' if self.active?
|
53
|
+
css << ' lock' if self.lock?
|
54
|
+
css
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
data/lib/symphonia.rb
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
# Dir[File.dirname(__FILE__) + '/symphonia/*.rb'].each {|file| require file }
|
2
|
+
module Symphonia
|
3
|
+
include ::ActiveSupport::Configurable
|
4
|
+
|
5
|
+
autoload :BaseController, 'symphonia/base_controller'
|
6
|
+
autoload :ControllerExtensions, 'symphonia/controller_extensions'
|
7
|
+
autoload :FormBuilder, 'symphonia/form_builder'
|
8
|
+
autoload :BootstrapLinkRender, 'symphonia/bootstrap_link_render'
|
9
|
+
autoload :Query, 'symphonia/query'
|
10
|
+
autoload :ModelAttributes, 'symphonia/model_attributes'
|
11
|
+
autoload :ModelFilters, 'symphonia/model_filters'
|
12
|
+
autoload :Attachable, 'symphonia/attachable'
|
13
|
+
autoload :QueryColumns, 'symphonia/query_columns'
|
14
|
+
|
15
|
+
module ActionCable
|
16
|
+
autoload :Connection, 'symphonia/action_cable/connection'
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
require 'symphonia/engine'
|
@@ -0,0 +1,90 @@
|
|
1
|
+
module Symphonia
|
2
|
+
RSpec.describe AccountsController do
|
3
|
+
|
4
|
+
include ActiveJob::TestHelper
|
5
|
+
|
6
|
+
routes { Symphonia::Engine.routes }
|
7
|
+
|
8
|
+
let(:regular_user) { FactoryBot.create(:user) }
|
9
|
+
|
10
|
+
context 'logged', logged: true do
|
11
|
+
context '#update' do
|
12
|
+
|
13
|
+
it 'valid' do
|
14
|
+
put :update, params: { user: { login: Faker::Internet.user_name } }
|
15
|
+
expect(response).to redirect_to account_path
|
16
|
+
end
|
17
|
+
|
18
|
+
it 'invalid' do
|
19
|
+
put :update, params: { user: { login: '' } }
|
20
|
+
expect(response).to have_http_status :success
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
|
27
|
+
context '#resend_activation' do
|
28
|
+
|
29
|
+
let(:email) { Faker::Internet.email }
|
30
|
+
subject { get :resend_activation, params: { mail: email } }
|
31
|
+
|
32
|
+
it 'non exist user' do
|
33
|
+
is_expected.to redirect_to :root
|
34
|
+
expect(flash[:error]).not_to be :blank
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'active user' do
|
38
|
+
_user = FactoryBot.create(:user, email: email, status: 'active')
|
39
|
+
is_expected.to redirect_to :root
|
40
|
+
expect(flash[:error]).not_to be :blank
|
41
|
+
end
|
42
|
+
|
43
|
+
it 'pending user' do
|
44
|
+
user = FactoryBot.create(:user, email: email, status: 'pending')
|
45
|
+
token = user.perishable_token.dup
|
46
|
+
|
47
|
+
expect { get :resend_activation, params: { mail: email } }.to have_enqueued_job.on_queue('mailers')
|
48
|
+
expect(response).to have_http_status :redirect
|
49
|
+
|
50
|
+
expect(user.reload.perishable_token).not_to eq token
|
51
|
+
expect(flash[:error]).to be_nil
|
52
|
+
expect(flash[:notice]).not_to be :blank
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
context '#activation' do
|
57
|
+
let(:token) { SecureRandom.hex(3) }
|
58
|
+
subject { get :activation, params: { activation_code: token } }
|
59
|
+
|
60
|
+
it 'valid token' do
|
61
|
+
user = FactoryBot.create(:user, status: 'pending')
|
62
|
+
user.update_columns(perishable_token: token)
|
63
|
+
is_expected.to redirect_to :login
|
64
|
+
expect(user.reload.status).to eq 'active'
|
65
|
+
end
|
66
|
+
|
67
|
+
it 'invalid token' do
|
68
|
+
is_expected.to redirect_to :root
|
69
|
+
expect(flash[:error]).not_to be :blank
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
context '#lost_password' do
|
74
|
+
|
75
|
+
it 'with mail' do
|
76
|
+
user = FactoryBot.create(:user, status: 'active')
|
77
|
+
|
78
|
+
expect { post :lost_password, params: { mail: user.email } }.to have_enqueued_job.on_queue('mailers')
|
79
|
+
expect(flash[:error]).to be_nil
|
80
|
+
end
|
81
|
+
|
82
|
+
it 'with inactive user' do
|
83
|
+
user = FactoryBot.create(:user, status: 'pending')
|
84
|
+
expect { post :lost_password, params: { mail: user.email } }.not_to have_enqueued_job.on_queue('mailers')
|
85
|
+
expect(flash[:error]).not_to be :blank
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
end
|
90
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Symphonia
|
2
|
+
RSpec.describe AdminController do
|
3
|
+
routes { Symphonia::Engine.routes }
|
4
|
+
|
5
|
+
context 'no user' do
|
6
|
+
|
7
|
+
it '#index' do
|
8
|
+
get :index
|
9
|
+
expect(response).to redirect_to login_path
|
10
|
+
end
|
11
|
+
|
12
|
+
it '#test_mail' do
|
13
|
+
get :test_mail
|
14
|
+
expect(response).to redirect_to login_path
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
context 'logged in', logged: true do
|
20
|
+
include ActiveJob::TestHelper
|
21
|
+
|
22
|
+
it '#index' do
|
23
|
+
get :index
|
24
|
+
expect(response).to have_http_status :success
|
25
|
+
end
|
26
|
+
|
27
|
+
it '#test_mail' do
|
28
|
+
Rails.application.config.action_mailer.delivery_method = :test
|
29
|
+
expect { get :test_mail }.to have_enqueued_job.on_queue('mailers')
|
30
|
+
expect(response).to have_http_status :redirect
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|