motor-admin 0.1.102 → 0.2.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e4dc0135509e3cbc5042ef05c51b87bf6edfb0b268e441797d382ec223d62d54
4
- data.tar.gz: a8c2b647964b33e2c14924b68a3a28cfc0a45c0bfaabedd5d5e661632a7754f8
3
+ metadata.gz: 7adc26b7ddd5925fb9fd1aebcb3c39fd34e82641f53f379e524df989630533de
4
+ data.tar.gz: 59de79e99d4694a42cfebdf58bdad6cb1e486bdb12a13d726829949e7e7791ce
5
5
  SHA512:
6
- metadata.gz: e9145465842ca373d59012efc2556dd5f29a422babc3b2a833dc6bbb1a17a6a7f766a36d2145f0d4e533fe9c977462dfd21a1240a2ca05223f316b0e76be3d00
7
- data.tar.gz: c23426c0b44429b7998b6708bec88e9a8d9c50a051b0338615b98cbfc2639891dcc8f5af606d002613e97afb8e5d1b750675aa8853fdf8a35f1caaac36d03ed2
6
+ metadata.gz: da9f42328e648604b9adf63124e5c1d6d5f0a3e78d554bede10acdafef4bd14711b2404e5cc89b9d10db093637a9c9cb83af829f33d69dea94550016f80426ea
7
+ data.tar.gz: 3f3cb870e4126692d2e9e1edc584dc01c458b57bb9d805e90ddb9446d02b2b594368607d07d1f1b529ad66490749e7eb7a2bab78495f681d5889a045fc9c99f5
@@ -5,6 +5,7 @@ module Motor
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  INSTANCE_VARIABLE_NAME = 'resource'
8
+ ASSOCIATION_INSTANCE_VARIABLE_NAME = 'associated_resource'
8
9
 
9
10
  included do
10
11
  before_action :load_and_authorize_resource
@@ -26,7 +27,7 @@ module Motor
26
27
  def load_and_authorize_resource
27
28
  options = {
28
29
  class: resource_class,
29
- parent: false,
30
+ parent: params[:association].present?,
30
31
  instance_name: INSTANCE_VARIABLE_NAME
31
32
  }
32
33
 
@@ -57,7 +58,7 @@ module Motor
57
58
  parent: false,
58
59
  through: :resource,
59
60
  through_association: params[:association].to_sym,
60
- instance_name: INSTANCE_VARIABLE_NAME
61
+ instance_name: params[:action] == 'create' ? ASSOCIATION_INSTANCE_VARIABLE_NAME : INSTANCE_VARIABLE_NAME
61
62
  ).load_and_authorize_resource
62
63
  else
63
64
  render json: { message: 'Unknown association' }, status: :not_found
@@ -21,7 +21,19 @@ module Motor
21
21
  end
22
22
 
23
23
  def create
24
- @resource.save!
24
+ if @associated_resource
25
+ if @resource_class.reflections[params[:association]].through_reflection?
26
+ @associated_resource.save!
27
+
28
+ @resource = @associated_resource
29
+ else
30
+ @resource.public_send(params[:association].to_sym).create!(@associated_resource.attributes) do |resource|
31
+ @resource = resource
32
+ end
33
+ end
34
+ else
35
+ @resource.save!
36
+ end
25
37
 
26
38
  render json: { data: Motor::ApiQuery::BuildJson.call(@resource, params, current_ability) }
27
39
  rescue ActiveRecord::RecordInvalid
@@ -61,7 +73,7 @@ module Motor
61
73
 
62
74
  def resource_params
63
75
  if params[:data].present?
64
- params.require(:data).except(resource_class.primary_key).permit!
76
+ params.require(:data).permit!
65
77
  else
66
78
  {}
67
79
  end
@@ -4,7 +4,9 @@ module Motor
4
4
  class UiController < ApplicationController
5
5
  layout 'motor/application'
6
6
 
7
- helper_method :current_user, :current_ability
7
+ helper_method :current_user, :current_ability, :cache_keys
8
+
9
+ before_action :set_i18n_locale
8
10
 
9
11
  def index
10
12
  render_ui
@@ -27,5 +29,15 @@ module Motor
27
29
 
28
30
  render :show
29
31
  end
32
+
33
+ def set_i18n_locale
34
+ configs = Motor::Configs::LoadFromCache.load_configs(cache_key: cache_keys[:configs])
35
+
36
+ I18n.locale = configs.find { |c| c.key == 'language' }&.value || I18n.locale
37
+ end
38
+
39
+ def cache_keys
40
+ @cache_keys ||= Configs::LoadFromCache.load_cache_keys
41
+ end
30
42
  end
31
43
  end
@@ -26,7 +26,7 @@ module Motor
26
26
  end
27
27
 
28
28
  def from_address
29
- from = ENV['MOTOR_ALERTS_FROM_ADDRESS'].presence
29
+ from = ENV['MOTOR_ALERTS_FROM_ADDRESS'].presence || ENV['MOTOR_EMAIL_ADDRESS'].presence
30
30
 
31
31
  from ||= application_mailer_default_from
32
32
  from ||= mailer_config_from_address
@@ -1 +1 @@
1
- <%= raw(Motor::Configs::BuildUiAppTag.call(current_user, current_ability)) %>
1
+ <%= raw(Motor::Configs::BuildUiAppTag.call(current_user, current_ability, cache_keys: cache_keys)) %>
@@ -82,9 +82,12 @@ en:
82
82
  field_is_required: '%{field} is required'
83
83
  field_list_cant_be_empty: "%{field} list can't be empty"
84
84
  field_must_be_exactly_in_length: '%{field} must be exactly %{length} in length'
85
+ field_must_be_less_in_length: '%{field} must be less than %{length} in length'
86
+ field_must_be_more_in_length: '%{field} must be more than %{length} in length'
85
87
  field_name: Field name
86
88
  field_value_does_not_match_pattern: '%{field} value does not match %{pattern}'
87
89
  file: File
90
+ filter: Filter
88
91
  filters: Filters
89
92
  form: Form
90
93
  form_has_been_saved: Form has been saved!
@@ -108,7 +111,7 @@ en:
108
111
  interval: Interval
109
112
  is: Is
110
113
  is_not: Is not
111
- items_has_been_removed: items has been removed
114
+ items_have_been_removed: items have been removed
112
115
  items_will_be_removed: '%{count} items will be removed'
113
116
  json: JSON
114
117
  label: Currency
@@ -256,3 +259,6 @@ en:
256
259
  add_text: Add Text
257
260
  edit_text: Edit Text
258
261
  open_in_markdown_editor: Open in markdown editor
262
+ activate: Activate
263
+ load_existing_options_from_database: Load existing options from the database
264
+ add_database: Add Database
@@ -59,6 +59,7 @@ es:
59
59
  emails: Emails
60
60
  every_day_at_hh_mm: todos los días a las HH:mm PM...
61
61
  field_name: Nombre del campo
62
+ filter: Filtrar
62
63
  filters: Filtros
63
64
  form: Formulario
64
65
  form_has_been_saved: ¡Formulario guardado!
@@ -73,7 +74,7 @@ es:
73
74
  hello_admin: Hola Admin 👋
74
75
  input_type: Tipo de campo
75
76
  interval: Intervalo
76
- items_has_been_removed: elementos fueron eliminados
77
+ items_have_been_removed: elementos fueron eliminados
77
78
  label: Moneda
78
79
  line_chart: Gráfico de líneas
79
80
  link_name: Nombre del link
@@ -179,6 +180,8 @@ es:
179
180
  field_is_required: '%{field} es obligatorio'
180
181
  field_list_cant_be_empty: "La lista %{field} no puede estar vacía"
181
182
  field_must_be_exactly_in_length: '%{field} debe tener un largo de %{length} caracteres'
183
+ field_must_be_less_in_length: '%{field} debe tener una longitud inferior a %{length}'
184
+ field_must_be_more_in_length: '%{field} debe tener una longitud superior a %{length}'
182
185
  field_value_does_not_match_pattern: 'El valor de %{field} no coincide con %{pattern}'
183
186
  file: Archivo
184
187
  greater_or_equal: Mayor o igual
@@ -256,6 +259,9 @@ es:
256
259
  add_text: Añadir Texto
257
260
  edit_text: Editar Texto
258
261
  open_in_markdown_editor: Abrir en el editor de markdown
262
+ activate: Activar
263
+ load_existing_options_from_database: Cargar las opciones existentes de la base de datos
264
+ add_database: Añadir base de datos
259
265
  i:
260
266
  locale: es
261
267
  select:
@@ -0,0 +1,345 @@
1
+ pt:
2
+ motor:
3
+ action_has_been_applied: "A ação foi aplicada!"
4
+ action_has_failed_with_code: "A ação falhou com o código"
5
+ action_type: "Tipo de ação"
6
+ actions: "Ações"
7
+ add: "Adicionar"
8
+ add_action: "Adicionar ação"
9
+ add_alert: "Adicionar alerta"
10
+ add_association: "Adicionar associação"
11
+ add_column: "Adicionar Coluna"
12
+ add_dashboard: "Adicionar painel"
13
+ add_field: "Adicionar campo"
14
+ add_filter: "Adicionar filtro"
15
+ add_form: "Adicionar formulário"
16
+ add_group: "Adicionar grupo"
17
+ add_item: "Adicionar item"
18
+ add_link: "Adicionar link"
19
+ add_query: "Adicionar consulta"
20
+ add_scope: "Adicionar escopo"
21
+ add_tab: "Adicionar guia"
22
+ add_text: "Adicionar texto"
23
+ adjust_fields: "Ajustar campos"
24
+ adjust_form: "Ajustar formulário"
25
+ alert_email_has_been_sent: "E-mail de alerta foi enviado!"
26
+ alert_has_been_activated: "O alerta foi ativado"
27
+ alert_has_been_disabled: "O alerta foi desativado"
28
+ alert_has_been_saved: "O alerta foi salvo!"
29
+ alert_name: "Nome do alerta"
30
+ alerts: "Alertas"
31
+ all: "Todos"
32
+ all_resources: "Todos os recursos"
33
+ and: "e"
34
+ api: "API"
35
+ api_path: "Caminho da API"
36
+ api_request: "Solicitação de API"
37
+ apply: "Aplicar"
38
+ are_you_sure: "Tem certeza?"
39
+ associations: "Associações"
40
+ bar_chart: "Gráfico de barras"
41
+ base: "Base"
42
+ boolean: "Booleano"
43
+ build_custom_form: "Crie um formulário personalizado"
44
+ cancel: "Cancelar"
45
+ cents: "Centavos"
46
+ change: "Alterar"
47
+ chart: "Gráfico"
48
+ checkbox: "Caixa de seleção"
49
+ clear: "Limpar"
50
+ clear_all: "Limpar tudo"
51
+ clear_selection: "Limpar seleção"
52
+ close: "Fechar"
53
+ close_editor: "Fechar editor"
54
+ close_settings: "Fechar ajustes"
55
+ close_variables: "Fechar variáveis"
56
+ color: "Cor"
57
+ columns: "Colunas"
58
+ condition: "Condição"
59
+ conditional: "Condicional"
60
+ contains: "Contém"
61
+ copied_to_the_clipboard: "Copiado para a área de transferência"
62
+ create: "Criar"
63
+ create_new: "Criar novo"
64
+ currency: "Moeda"
65
+ current_user_variables_are_always_passed_explicitly_and_can_he_used_html: "<code>{{current_user_id}}</code> e <code>{{current_user_email}}</code> variáveis são sempre passadas explicitamente e podem ser usadas para decidir quais dados devem ser exibidos para determinado usuário:"
66
+ dashboard: "Painel"
67
+ dashboard_has_been_saved: "O painel foi salvo!"
68
+ dashboard_title: "Título do painel"
69
+ dashboards: "Painéis"
70
+ date: "Data"
71
+ date_and_time: "Data e hora"
72
+ decimal: "Decimal"
73
+ default: "Padrão"
74
+ default_value: "Valor predefinido"
75
+ describe_this_alert_optional: "Descreva este alerta (opcional)"
76
+ describe_your_dashboard_optional: "Descreva seu painel (opcional)"
77
+ describe_your_form_optional: "Descreva seu formulário (opcional)"
78
+ describe_your_query_optional: "Descreva sua consulta (opcional)"
79
+ description: "Descrição"
80
+ deselect_all: "Desmarcar tudo"
81
+ details: "Detalhes"
82
+ disable: "Desabilitar"
83
+ display_as: "Exibir como"
84
+ edit: "Editar"
85
+ edit_markdown: "Editar markdown"
86
+ edit_sql: "Editar SQL"
87
+ edit_text: "Editar texto"
88
+ emails: "E-mails"
89
+ empty: "Vazio"
90
+ ends_with: "Termina com"
91
+ equal_to: "É igual a"
92
+ every_day_at_hh_mm: "todos os dias em HH:mm PM..."
93
+ excludes: "exclui"
94
+ field: "Campo"
95
+ field_is_not_a_number: "% {field} não é um número"
96
+ field_is_required: "%{field} é obrigatório"
97
+ field_list_cant_be_empty: "A lista %{field} não pode estar vazia"
98
+ field_must_be_exactly_in_length: "%{field} deve ter exatamente %{length} de comprimento"
99
+ field_must_be_less_in_length: '%{field} deve ser menor que %{length} em comprimento'
100
+ field_must_be_more_in_length: '%{field} deve ter mais do que %{length} de comprimento'
101
+ field_name: "Nome do campo"
102
+ field_value_does_not_match_pattern: "%{field} valor não corresponde %{pattern}"
103
+ file: "Arquivo"
104
+ filters: "Filtros"
105
+ foreign_key: "Foreign key"
106
+ form: "Formulário"
107
+ form_has_been_saved: "O formulário foi salvo!"
108
+ form_has_been_submitted_successfully: "O formulário foi enviado com sucesso!"
109
+ form_name: "Nome do formulário"
110
+ forms: "Formulários"
111
+ funnel: "Funil"
112
+ general: "Geral"
113
+ greater_or_equal: "Maior ou igual"
114
+ greater_than: "Maior do que"
115
+ greater_than_or_equal_to: "Maior que ou igual a"
116
+ group_name: "Nome do grupo"
117
+ has_been_created: "foi criado"
118
+ has_been_removed_succesfully: "foi removido com sucesso"
119
+ has_been_updated: "foi atualizado"
120
+ hello_admin: "Olá Admin \U0001f44b"
121
+ hidden: "Oculto"
122
+ image: "Imagem"
123
+ includes: "Inclui"
124
+ input_type: "Tipo de entrada"
125
+ integer: "Integer"
126
+ interval: "Intervalo"
127
+ is: "É"
128
+ is_not: "Não é"
129
+ items_has_been_removed: "itens foram removidos"
130
+ items_will_be_removed: "%{count} itens serão removidos"
131
+ json: "JSON"
132
+ label: "Moeda"
133
+ less_or_equal: "Menor ou igual"
134
+ less_than: "Menor do que"
135
+ less_than_or_equal_to: "Menor ou igual a"
136
+ line_chart: "Gráfico de linhas"
137
+ link: "Link"
138
+ link_name: "Nome do link"
139
+ link_text: "Texto do link"
140
+ links: "Links"
141
+ load_initial_data: "Carregar dados iniciais"
142
+ loading: "Carregando…"
143
+ long_text: "Texto longo"
144
+ looks_like_you_are_new_here: "Parece que você é novo aqui \U0001f643"
145
+ markdown: "Markdown"
146
+ method: "Método"
147
+ method_call: "Chamada de método"
148
+ more: "Mais"
149
+ multiple: "Múltiplo"
150
+ name: "Nome"
151
+ new_alert: "Novo alerta"
152
+ new_dashboard: "Novo painel"
153
+ new_form: "Novo formulário"
154
+ new_query: "Nova consulta"
155
+ no_data: "Sem dados"
156
+ not_empty: "Não vazio"
157
+ not_found: "Não encontrado"
158
+ number: "Número"
159
+ ok: "Ok"
160
+ open_in_markdown_editor: "Abrir no editor de markdown"
161
+ options_separated_with_new_line_or_comma: "Opções separadas com nova linha ou vírgula"
162
+ or: "Ou"
163
+ other_than: "Outros que não"
164
+ param_name: "Nome do param"
165
+ password: "Senha"
166
+ path: "Caminho"
167
+ percent: "Porcento"
168
+ percentage: "Porcentagem"
169
+ pie_chart: "Gráfico de pizza"
170
+ polymorphic: "Polimórfico"
171
+ primary_key: "Chave primária"
172
+ queries: "Consultas"
173
+ queries_can_contain_variable_via_syntax_html: "As consultas podem conter variáveis incorporadas usando a sintaxe <code>{{variable}}</code>:"
174
+ query: "Consulta"
175
+ query_editor: "Editor de consultas"
176
+ query_has_been_saved: "A consulta foi salva!"
177
+ query_name: "Nome da consulta"
178
+ query_not_selected: "Consulta não selecionada"
179
+ query_revisions: "Revisões de consulta"
180
+ read_only: "Somente leitura"
181
+ read_write: "Leitura-gravação"
182
+ reference: "Referência"
183
+ reference_resource: "Recurso de referência"
184
+ remove: "Remover"
185
+ reports: "Relatórios"
186
+ request_param: "Param de solicitação"
187
+ required: "Requerido"
188
+ resource: "Recurso"
189
+ resource_filters: "%{resource} Filtros"
190
+ resource_settings: "%{resource} Configurações"
191
+ resources: "Recursos"
192
+ restore: "Restaurar"
193
+ resubmit: "Reenviar"
194
+ revert: "Reverter"
195
+ revision_has_been_applied: "A revisão foi aplicada"
196
+ revisions: "Revisões"
197
+ richtext: "Richtext"
198
+ row_chart: "Gráfico de linhas"
199
+ run: "Executar"
200
+ save: "Salvar"
201
+ save_and_create_new: "Salvar e criar novo"
202
+ save_as_new: "Salvar como novo"
203
+ save_dashboard: "Salvar painel"
204
+ save_form: "Salvar formulário"
205
+ save_query: "Salvar consulta"
206
+ scopes: "Escopos"
207
+ search: "Pesquisar"
208
+ search_placeholder: "Pesquisar..."
209
+ search_query: "Consulta de pesquisa"
210
+ select: "Selecionar"
211
+ select_alert_tags: "Selecionar tags de alerta"
212
+ select_dashboard: "Selecione o painel"
213
+ select_dashboard_tags: "Selecionar tags de painel"
214
+ select_form: "Selecionar formulário"
215
+ select_form_tags: "Selecionar tags de dashform"
216
+ select_options: "Selecionar opções"
217
+ select_placeholder: "Selecione..."
218
+ select_query: "Selecionar consulta"
219
+ select_query_tags: "Selecionar tags de consulta"
220
+ select_resource_placeholder: "Selecionar recurso..."
221
+ selected_item_has_been_removed: "O item selecionado foi removido"
222
+ selected_item_will_be_removed: "O item selecionado será removido"
223
+ selected_item_will_be_removed_are_you_sure: "O item selecionado será removido. Tem certeza?"
224
+ send_empty: "Enviar vazio?"
225
+ send_now: "Enviar agora"
226
+ send_to: "Enviar para"
227
+ set_tags: "Definir tags"
228
+ settings: "Configurações"
229
+ should_be_a_valid_json: "Deve ser um JSON válido"
230
+ should_be_error_constraint: "Deve ser %{erro} %{constraint}"
231
+ source: "Fonte"
232
+ stacked_bars: "Barras empilhadas"
233
+ starts_with: "Começa com"
234
+ submit: "Enviar"
235
+ syntax_is_used_for_if_and_if_not_conditions_html: "<code>{{#variable}}... {{/variable}}</code> e <code>{{^variable}}... A sintaxe {{variable}}</code> é usada para condições <code>if</code> e <code>se não</code>, respectivamente:"
236
+ tab_type: "Tipo de guia"
237
+ table: "Tabela"
238
+ tabs: "Guias"
239
+ tag: "Tag"
240
+ tags: "Tags"
241
+ text: "Texto"
242
+ textarea: "Área de texto"
243
+ there_are_unsaved_changes_from: "Há alterações não salvas de %{timestamp}"
244
+ through: "Através"
245
+ timezone: "Fuso horário"
246
+ title: "Título"
247
+ type: "Tipo"
248
+ unable_to_load_form_data: "Não é possível carregar dados do formulário"
249
+ unable_to_remove_item: "Não foi possível remover o item"
250
+ unable_to_remove_items: "Não foi possível remover itens"
251
+ unable_to_send_email: "Não foi possível enviar e-mail"
252
+ unable_to_submit_form: "Não foi possível enviar o formulário"
253
+ unit: "Unidade"
254
+ use_default: "Usar padrão"
255
+ value: "Valor"
256
+ values_axis: "Eixo valores"
257
+ variables: "Variáveis"
258
+ visibility: "Visibilidade"
259
+ visualization: "Visualização"
260
+ write_only: "Somente gravação"
261
+ i:
262
+ locale: pt
263
+ select:
264
+ placeholder: Selecionar
265
+ noMatch: Sem correspondências
266
+ loading: Carregando
267
+ table:
268
+ noDataText: Sem dados
269
+ noFilteredDataText: Sem dados para o filtro
270
+ confirmFilter: Aceitar
271
+ resetFilter: Resetar filtro
272
+ clearFilter: Limpar
273
+ sumText: Soma
274
+ datepicker:
275
+ selectDate: Selecionar data
276
+ selectTime: Selecionar hora
277
+ startTime: Horário de início
278
+ endTime: Horário de final
279
+ clear: Limpar
280
+ ok: Aceitar
281
+ datePanelLabel: "[mmmm] [yyyy]"
282
+ month: Mês
283
+ month1: Janeiro
284
+ month2: Fevereiro
285
+ month3: Março
286
+ month4: Abril
287
+ month5: Maio
288
+ month6: Junho
289
+ month7: Julho
290
+ month8: Agosto
291
+ month9: Setembro
292
+ month10: Outubro
293
+ month11: Novembro
294
+ month12: Dezembro
295
+ year: Ano
296
+ weekStartDay: '1'
297
+ weeks:
298
+ sun: Dom
299
+ mon: Seg
300
+ tue: Ter
301
+ wed: Qua
302
+ thu: Qui
303
+ fri: Sex
304
+ sat: Sab
305
+ months:
306
+ m1: Jan
307
+ m2: Fev
308
+ m3: Mar
309
+ m4: Abr
310
+ m5: Mai
311
+ m6: Jun
312
+ m7: Jul
313
+ m8: Ago
314
+ m9: Set
315
+ m10: Out
316
+ m11: Nov
317
+ m12: Dez
318
+ transfer:
319
+ titles:
320
+ source: Origem
321
+ target: Destino
322
+ filterPlaceholder: Buscar aqui
323
+ notFoundText: Sem resultados
324
+ modal:
325
+ okText: Aceitar
326
+ cancelText: Cancelar
327
+ poptip:
328
+ okText: Aceitar
329
+ cancelText: Cancelar
330
+ page:
331
+ prev: Página Anterior
332
+ next: Página Seguinte
333
+ total: Total
334
+ item: Elemento
335
+ items: Elementos
336
+ prev5: 5 Páginas Anteriores
337
+ next5: 5 Páginas Seguintes
338
+ page: "/page"
339
+ goto: Ir a
340
+ p: ''
341
+ rate:
342
+ star: Estrela
343
+ stars: Estrelas
344
+ tree:
345
+ emptyText: Sem Dados
data/config/routes.rb CHANGED
@@ -21,7 +21,8 @@ Motor::Admin.routes.draw do
21
21
  resources :audits, only: %i[index]
22
22
  resources :resources, path: '/data/:resource',
23
23
  only: %i[index show update create destroy],
24
- controller: 'data' do
24
+ controller: 'data',
25
+ constraints: { id: %r{[^/]+} } do
25
26
  put '/:method', to: 'data#execute'
26
27
  resources :association, path: '/:association',
27
28
  only: %i[index create],
@@ -143,6 +143,15 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
143
143
  add_index :motor_audits, %i[user_id user_type], name: 'motor_auditable_user_index'
144
144
  add_index :motor_audits, :request_uuid
145
145
  add_index :motor_audits, :created_at
146
+
147
+ model = Class.new(ApplicationRecord)
148
+
149
+ model.table_name = 'motor_configs'
150
+
151
+ model.create!(key: 'header.links', value: [{
152
+ name: '⭐ Star on GitHub',
153
+ path: 'https://github.com/omohokcoj/motor-admin'
154
+ }].to_json)
146
155
  end
147
156
 
148
157
  def self.down
@@ -14,5 +14,17 @@ if Rails::VERSION::MAJOR == 6
14
14
  build_filters(arel, my_alias_tracker)
15
15
  arel
16
16
  end
17
+
18
+ def build_filters(manager, alias_tracker)
19
+ where_clause = nil
20
+
21
+ @filters.each do |filters|
22
+ where_clause = filter_clause_factory.build(filters, alias_tracker)
23
+
24
+ manager.where(where_clause.ast)
25
+ end
26
+
27
+ @values[:where] = where_clause if where_clause
28
+ end
17
29
  end
18
30
  end
@@ -92,7 +92,7 @@ module Motor
92
92
  def find_key_in_params(params, key)
93
93
  params = params['include']
94
94
 
95
- return if params.blank?
95
+ return {} if params.blank?
96
96
  return params[key] if params[key]
97
97
 
98
98
  params.keys.reduce(nil) do |acc, k|
@@ -22,9 +22,9 @@ module Motor
22
22
 
23
23
  def call
24
24
  models.map do |model|
25
- Object.const_get(model.name)
25
+ model = Object.const_get(model.name)
26
26
 
27
- next unless ActiveRecord::Base.connection.table_exists?(model.table_name)
27
+ next unless model.table_exists?
28
28
 
29
29
  schema = build_model_schema(model)
30
30
 
@@ -37,7 +37,7 @@ module Motor
37
37
  Rails.logger.error(e)
38
38
 
39
39
  next
40
- end.compact
40
+ end.compact.uniq
41
41
  end
42
42
 
43
43
  def models
@@ -225,8 +225,8 @@ module Motor
225
225
  display_name: model.human_attribute_name(name),
226
226
  model_name: reflection.polymorphic? ? nil : reflection.klass.name.underscore,
227
227
  reference_type: reflection.belongs_to? ? 'belongs_to' : 'has_one',
228
- foreign_key: reflection.foreign_key,
229
- primary_key: reflection.polymorphic? ? 'id' : reflection.active_record_primary_key,
228
+ foreign_key: reflection.join_foreign_key,
229
+ primary_key: reflection.polymorphic? ? 'id' : reflection.join_primary_key,
230
230
  options: reflection.options.slice(:through, :source),
231
231
  polymorphic: reflection.polymorphic?,
232
232
  virtual: false
@@ -248,8 +248,8 @@ module Motor
248
248
  display_name: model.human_attribute_name(name),
249
249
  slug: name.underscore,
250
250
  model_name: model_class.name.underscore,
251
- foreign_key: ref.foreign_key,
252
- primary_key: ref.active_record_primary_key,
251
+ foreign_key: ref.join_primary_key,
252
+ primary_key: ref.join_foreign_key,
253
253
  polymorphic: ref.options[:as].present?,
254
254
  icon: Motor::FindIcon.call(name),
255
255
  options: ref.options.slice(:through, :source),
@@ -290,12 +290,20 @@ module Motor
290
290
  when ActiveModel::Validations::FormatValidator
291
291
  { format: JsRegex.new(options[:with]).to_h.slice(:source, :options) }
292
292
  when ActiveRecord::Validations::LengthValidator
293
- { length: options }
293
+ { length: normalize_length_validation_options(options) }
294
294
  when ActiveModel::Validations::NumericalityValidator
295
295
  { numeric: options }
296
296
  end
297
297
  end
298
298
 
299
+ def normalize_length_validation_options(options)
300
+ return options if options[:in].blank?
301
+
302
+ in_range = options[:in]
303
+
304
+ options.merge(in: in_range.minmax)
305
+ end
306
+
299
307
  def valid_reflection?(reflection)
300
308
  reflection.klass
301
309
  reflection.foreign_key
@@ -5,6 +5,7 @@ module Motor
5
5
  module Utils
6
6
  ABBREVIATIONS = {
7
7
  'Id' => 'ID',
8
+ 'Uuid' => 'UUID',
8
9
  'Url' => 'URL',
9
10
  'Iso' => 'ISO',
10
11
  'vip' => 'VIP',
@@ -51,7 +51,11 @@ module Motor
51
51
  COLUMN_NAME_ACCESS_TYPES = {
52
52
  id: ColumnAccessTypes::READ_ONLY,
53
53
  created_at: ColumnAccessTypes::READ_ONLY,
54
+ created_on: ColumnAccessTypes::READ_ONLY,
55
+ inserted_at: ColumnAccessTypes::READ_ONLY,
54
56
  updated_at: ColumnAccessTypes::READ_ONLY,
57
+ updated_on: ColumnAccessTypes::READ_ONLY,
58
+ modified_at: ColumnAccessTypes::READ_ONLY,
55
59
  deleted_at: ColumnAccessTypes::READ_ONLY
56
60
  }.with_indifferent_access.freeze
57
61
 
@@ -12,9 +12,7 @@ module Motor
12
12
 
13
13
  module_function
14
14
 
15
- def call(current_user = nil, current_ability = nil)
16
- cache_keys = LoadFromCache.load_cache_keys
17
-
15
+ def call(current_user = nil, current_ability = nil, cache_keys: LoadFromCache.load_cache_keys)
18
16
  CACHE_STORE.fetch("#{I18n.locale}#{cache_keys.hash}#{current_user&.id}") do
19
17
  CACHE_STORE.clear
20
18
 
@@ -23,16 +21,19 @@ module Motor
23
21
  end
24
22
  end
25
23
 
24
+ # rubocop:disable Metrics/AbcSize
26
25
  # @return [Hash]
27
26
  def build_data(cache_keys = {}, current_user = nil, current_ability = nil)
28
27
  configs_cache_key = cache_keys[:configs]
29
28
 
30
29
  {
30
+ version: Motor::VERSION,
31
31
  current_user: current_user&.as_json(only: %i[id email]),
32
32
  current_rules: current_ability.serialized_rules,
33
33
  audits_count: Motor::Audit.count,
34
34
  i18n: i18n_data,
35
35
  base_path: Motor::Admin.routes.url_helpers.motor_path,
36
+ admin_settings_path: Rails.application.routes.url_helpers.try(:admin_settings_general_path),
36
37
  schema: Motor::BuildSchema.call(cache_keys, current_ability),
37
38
  header_links: header_links_data_hash(configs_cache_key),
38
39
  homepage_layout: homepage_layout_data_hash(configs_cache_key),
@@ -45,6 +46,7 @@ module Motor
45
46
  forms: forms_data_hash(build_cache_key(cache_keys, :forms, current_user, current_ability), current_ability)
46
47
  }
47
48
  end
49
+ # rubocop:enable Metrics/AbcSize
48
50
 
49
51
  def i18n_data
50
52
  I18n.t('motor', default: I18n.t('motor', locale: :en))
@@ -3,7 +3,6 @@
3
3
  module Motor
4
4
  module Configs
5
5
  module SyncFromFile
6
- FILE_PATH = Motor::Configs::FILE_PATH
7
6
  MUTEXT = Mutex.new
8
7
  FILE_TIMESTAMPS_STORE = ActiveSupport::Cache::MemoryStore.new(size: 1.megabyte)
9
8
 
@@ -11,7 +10,7 @@ module Motor
11
10
 
12
11
  def call(with_exception: false)
13
12
  MUTEXT.synchronize do
14
- file = Rails.root.join(FILE_PATH)
13
+ file = Pathname.new(Motor::Configs.file_path)
15
14
 
16
15
  file_timestamp =
17
16
  begin
@@ -4,7 +4,6 @@ module Motor
4
4
  module Configs
5
5
  module WriteToFile
6
6
  THREAD_POOL = Concurrent::FixedThreadPool.new(1)
7
- FILE_PATH = Motor::Configs::FILE_PATH
8
7
 
9
8
  module_function
10
9
 
@@ -22,7 +21,7 @@ module Motor
22
21
  end
23
22
 
24
23
  def write_with_lock
25
- File.open(Rails.root.join(FILE_PATH), 'w') do |file|
24
+ File.open(Motor::Configs.file_path, 'w') do |file|
26
25
  file.flock(File::LOCK_EX)
27
26
 
28
27
  YAML.dump(Motor::Configs::BuildConfigsHash.call, file)
data/lib/motor/configs.rb CHANGED
@@ -5,6 +5,19 @@ module Motor
5
5
  FILE_PATH = 'config/motor.yml'
6
6
  SYNC_API_PATH = '/motor_configs_sync'
7
7
  SYNC_ACCESS_KEY = ENV.fetch('MOTOR_SYNC_API_KEY', '')
8
+ MEMFS_PATH = '/__enclose_io_memfs__/'
9
+ PWD_FILE_NAME = 'motor-admin.yml'
10
+
11
+ module_function
12
+
13
+ # @return [String]
14
+ def file_path
15
+ if Rails.root.to_s.start_with?(MEMFS_PATH)
16
+ [ENV['PWD'], PWD_FILE_NAME].join('/')
17
+ else
18
+ Rails.root.join(FILE_PATH).to_s
19
+ end
20
+ end
8
21
  end
9
22
  end
10
23
 
@@ -63,15 +63,15 @@ module Motor
63
63
  result = nil
64
64
  statement = prepare_sql_statement(query, limit, variables_hash, filters)
65
65
 
66
- ActiveRecord::Base.transaction do
66
+ connection_class.transaction do
67
67
  result =
68
- case ActiveRecord::Base.connection.class.name
68
+ case connection_class.connection.class.name
69
69
  when 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
70
- PostgresqlExecQuery.call(ActiveRecord::Base.connection, statement)
70
+ PostgresqlExecQuery.call(connection_class.connection, statement)
71
71
  else
72
72
  statement = normalize_statement_for_sql(statement)
73
73
 
74
- ActiveRecord::Base.connection.exec_query(*statement)
74
+ connection_class.connection.exec_query(*statement)
75
75
  end
76
76
 
77
77
  raise ActiveRecord::Rollback
@@ -172,6 +172,10 @@ module Motor
172
172
  acc[variable[:name]] ||= variables_hash[variable[:name]] || variable[:default_value]
173
173
  end
174
174
  end
175
+
176
+ def connection_class
177
+ defined?(ResourceRecord) ? ResourceRecord : ActiveRecord::Base
178
+ end
175
179
  end
176
180
  end
177
181
  end
@@ -117,7 +117,7 @@ module Motor
117
117
 
118
118
  def define_belongs_to_reflection(klass, config)
119
119
  klass.belongs_to(config[:name].to_sym,
120
- class_name: config[:model_name].classify,
120
+ class_name: config[:model_name]&.classify,
121
121
  foreign_key: config[:foreign_key],
122
122
  polymorphic: config[:polymorphic],
123
123
  primary_key: config[:primary_key],
@@ -163,20 +163,29 @@ module Motor
163
163
 
164
164
  def define_associations(klass, config)
165
165
  config.fetch(:associations, []).each do |association|
166
- is_virtual, is_polymorphic = association.values_at(:virtual, :polymorphic)
166
+ next unless association[:virtual]
167
167
 
168
- next unless is_virtual
168
+ options = normalize_association_params(association)
169
169
 
170
- options = association.slice(:foreign_key, :primary_key)
171
- options[:class_name] = association[:model_name].classify
172
- options[:as] = association[:foreign_key].delete_suffix('_id') if is_polymorphic
170
+ filters = options.delete(:filters)
173
171
 
174
- options = options.merge(association[:options] || {})
175
-
176
- klass.has_many(association[:name].to_sym, **options.symbolize_keys)
172
+ if filters.present?
173
+ klass.has_many(association[:name].to_sym, -> { filter(filters).tap(&:arel) }, **options.symbolize_keys)
174
+ else
175
+ klass.has_many(association[:name].to_sym, **options.symbolize_keys)
176
+ end
177
177
  end
178
178
  end
179
179
 
180
+ def normalize_association_params(params)
181
+ options = params.slice(:foreign_key, :primary_key).merge(dependent: :destroy)
182
+
183
+ options[:class_name] = params[:model_name].classify
184
+ options[:as] = params[:foreign_key].delete_suffix('_id') if params[:polymorphic]
185
+
186
+ options.merge(params[:options] || {})
187
+ end
188
+
180
189
  def maybe_fetch_from_cache(model, cache_key, miss_cache_block, postprocess_block)
181
190
  return miss_cache_block.call unless cache_key
182
191
 
data/lib/motor/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Motor
4
- VERSION = '0.1.102'
4
+ VERSION = '0.2.1'
5
5
  end
@@ -2601,9 +2601,9 @@
2601
2601
  "icons/zoom-out.svg.gz": "icons/zoom-out.svg.gz",
2602
2602
  "icons/zoom-question.svg": "icons/zoom-question.svg",
2603
2603
  "icons/zoom-question.svg.gz": "icons/zoom-question.svg.gz",
2604
- "main-61a9a4d250915792b3e7.css.gz": "main-61a9a4d250915792b3e7.css.gz",
2605
- "main-61a9a4d250915792b3e7.js.LICENSE.txt": "main-61a9a4d250915792b3e7.js.LICENSE.txt",
2606
- "main-61a9a4d250915792b3e7.js.gz": "main-61a9a4d250915792b3e7.js.gz",
2607
- "main.css": "main-61a9a4d250915792b3e7.css",
2608
- "main.js": "main-61a9a4d250915792b3e7.js"
2604
+ "main-5c903ff9235e54ac03f1.css.gz": "main-5c903ff9235e54ac03f1.css.gz",
2605
+ "main-5c903ff9235e54ac03f1.js.LICENSE.txt": "main-5c903ff9235e54ac03f1.js.LICENSE.txt",
2606
+ "main-5c903ff9235e54ac03f1.js.gz": "main-5c903ff9235e54ac03f1.js.gz",
2607
+ "main.css": "main-5c903ff9235e54ac03f1.css",
2608
+ "main.js": "main-5c903ff9235e54ac03f1.js"
2609
2609
  }
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: motor-admin
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.102
4
+ version: 0.2.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Pete Matsyburka
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-08-19 00:00:00.000000000 Z
11
+ date: 2021-09-06 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord-filter
@@ -166,6 +166,7 @@ files:
166
166
  - app/views/motor/ui/show.html.erb
167
167
  - config/locales/en.yml
168
168
  - config/locales/es.yml
169
+ - config/locales/pt.yml
169
170
  - config/routes.rb
170
171
  - lib/generators/motor/install_generator.rb
171
172
  - lib/generators/motor/migration.rb
@@ -1536,8 +1537,8 @@ files:
1536
1537
  - ui/dist/icons/zoom-money.svg.gz
1537
1538
  - ui/dist/icons/zoom-out.svg.gz
1538
1539
  - ui/dist/icons/zoom-question.svg.gz
1539
- - ui/dist/main-61a9a4d250915792b3e7.css.gz
1540
- - ui/dist/main-61a9a4d250915792b3e7.js.gz
1540
+ - ui/dist/main-5c903ff9235e54ac03f1.css.gz
1541
+ - ui/dist/main-5c903ff9235e54ac03f1.js.gz
1541
1542
  - ui/dist/manifest.json
1542
1543
  homepage:
1543
1544
  licenses: