motor-admin 0.1.102 → 0.2.1

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.
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: