motor-admin 0.1.104 → 0.2.0

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: 266533361474d8854cfc08b9b1b43766bde0fa49dfc6a3d840ab6392dbe6af2d
4
- data.tar.gz: 1c0e11689a619fb1afd2f47c47b133f6d1b0a10ccdff01c6fbbb4e78b6e8a6f5
3
+ metadata.gz: 5bd6e9f08fd07c47dd7f80ed20e32360a1e17efaad9a73a068ff9edc1d76fc37
4
+ data.tar.gz: 54dfd675226f75ade0a0f6048dcb8f96c1aaae7d9d6a1d73515286b8d4c240ab
5
5
  SHA512:
6
- metadata.gz: 27abaae03cde38935bd4b7801f79375f5297047ab5fc3d25b6c318662d4459b9f29cc4194d8ed3fa9599421241e35df0960ee2755fd773b38eea5d8876377670
7
- data.tar.gz: 4b2c7a8b6d14da8e44bbd0bd984168ee4ef2dc8b50fd6904bbf3b45f1854ea0af3dde8ee697264298ea2f9696c0848269ddd454699ad9cb7ca4d2c6a8472128d
6
+ metadata.gz: fda28b2381654ba371ce61a81d41056835b87d42106c7aa194546f9d022c610304891bc9bf7a2ad7cb59396f78f971a50a4dd91abf411ecc810358dc786d59bf
7
+ data.tar.gz: bfd3fab365192c0d48f0841059b5d42ec1a8220c5ed22e33f237e448fcd60fe8faaf8a0be1a2f5158bc1b807cf1019fddee17139811e22d71f2e4a2ee59126c0
@@ -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)) %>
@@ -87,6 +87,7 @@ en:
87
87
  field_name: Field name
88
88
  field_value_does_not_match_pattern: '%{field} value does not match %{pattern}'
89
89
  file: File
90
+ filter: Filter
90
91
  filters: Filters
91
92
  form: Form
92
93
  form_has_been_saved: Form has been saved!
@@ -110,7 +111,7 @@ en:
110
111
  interval: Interval
111
112
  is: Is
112
113
  is_not: Is not
113
- items_has_been_removed: items has been removed
114
+ items_have_been_removed: items have been removed
114
115
  items_will_be_removed: '%{count} items will be removed'
115
116
  json: JSON
116
117
  label: Currency
@@ -258,3 +259,6 @@ en:
258
259
  add_text: Add Text
259
260
  edit_text: Edit Text
260
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
@@ -258,6 +259,9 @@ es:
258
259
  add_text: Añadir Texto
259
260
  edit_text: Editar Texto
260
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
261
265
  i:
262
266
  locale: es
263
267
  select:
@@ -96,8 +96,8 @@ pt:
96
96
  field_is_required: "%{field} é obrigatório"
97
97
  field_list_cant_be_empty: "A lista %{field} não pode estar vazia"
98
98
  field_must_be_exactly_in_length: "%{field} deve ter exatamente %{length} de comprimento"
99
- field_must_be_less_in_length: '%{campo} deve ser menor que %{comprimento} em comprimento'
100
- field_must_be_more_in_length: '%{campo} deve ter mais do que %{comprimento} 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
101
  field_name: "Nome do campo"
102
102
  field_value_does_not_match_pattern: "%{field} valor não corresponde %{pattern}"
103
103
  file: "Arquivo"
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
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),
@@ -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.104'
4
+ VERSION = '0.2.0'
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-37390abd2976b4fd28f0.css.gz": "main-37390abd2976b4fd28f0.css.gz",
2605
- "main-37390abd2976b4fd28f0.js.LICENSE.txt": "main-37390abd2976b4fd28f0.js.LICENSE.txt",
2606
- "main-37390abd2976b4fd28f0.js.gz": "main-37390abd2976b4fd28f0.js.gz",
2607
- "main.css": "main-37390abd2976b4fd28f0.css",
2608
- "main.js": "main-37390abd2976b4fd28f0.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.104
4
+ version: 0.2.0
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-26 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
@@ -1537,8 +1537,8 @@ files:
1537
1537
  - ui/dist/icons/zoom-money.svg.gz
1538
1538
  - ui/dist/icons/zoom-out.svg.gz
1539
1539
  - ui/dist/icons/zoom-question.svg.gz
1540
- - ui/dist/main-37390abd2976b4fd28f0.css.gz
1541
- - ui/dist/main-37390abd2976b4fd28f0.js.gz
1540
+ - ui/dist/main-5c903ff9235e54ac03f1.css.gz
1541
+ - ui/dist/main-5c903ff9235e54ac03f1.js.gz
1542
1542
  - ui/dist/manifest.json
1543
1543
  homepage:
1544
1544
  licenses: