motor-admin 0.1.104 → 0.2.0

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