motor-admin 0.1.47 → 0.1.52

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.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. data/LICENSE +1 -1
  3. data/README.md +19 -13
  4. data/app/controllers/concerns/motor/current_user_method.rb +15 -0
  5. data/app/controllers/motor/api_base_controller.rb +13 -17
  6. data/app/controllers/motor/application_controller.rb +1 -0
  7. data/app/controllers/motor/assets_controller.rb +1 -1
  8. data/app/controllers/motor/ui_controller.rb +12 -4
  9. data/app/mailers/motor/alerts_mailer.rb +3 -3
  10. data/app/models/motor/alert.rb +1 -0
  11. data/app/models/motor/dashboard.rb +1 -0
  12. data/app/models/motor/form.rb +1 -0
  13. data/app/models/motor/query.rb +1 -0
  14. data/app/models/motor/resource.rb +1 -0
  15. data/app/models/motor/tag.rb +1 -1
  16. data/app/views/motor/ui/show.html.erb +1 -1
  17. data/config/routes.rb +8 -9
  18. data/lib/generators/motor/templates/install.rb +74 -65
  19. data/lib/motor.rb +0 -1
  20. data/lib/motor/active_record_utils.rb +13 -1
  21. data/lib/motor/active_record_utils/active_storage_blob_patch.rb +4 -0
  22. data/lib/motor/admin.rb +6 -7
  23. data/lib/motor/alerts/persistance.rb +4 -4
  24. data/lib/motor/alerts/scheduler.rb +1 -1
  25. data/lib/motor/api_query/apply_scope.rb +1 -1
  26. data/lib/motor/api_query/build_json.rb +6 -6
  27. data/lib/motor/api_query/paginate.rb +1 -0
  28. data/lib/motor/api_query/search.rb +1 -7
  29. data/lib/motor/api_query/sort.rb +3 -1
  30. data/lib/motor/assets.rb +1 -1
  31. data/lib/motor/build_schema/find_icon.rb +1 -0
  32. data/lib/motor/build_schema/load_from_rails.rb +5 -2
  33. data/lib/motor/configs/build_ui_app_tag.rb +6 -6
  34. data/lib/motor/configs/load_from_cache.rb +28 -16
  35. data/lib/motor/configs/sync_from_hash.rb +1 -1
  36. data/lib/motor/configs/sync_with_remote.rb +1 -1
  37. data/lib/motor/configs/write_to_file.rb +1 -0
  38. data/lib/motor/dashboards/persistance.rb +4 -4
  39. data/lib/motor/forms/persistance.rb +4 -4
  40. data/lib/motor/queries/persistance.rb +4 -4
  41. data/lib/motor/queries/run_query.rb +3 -3
  42. data/lib/motor/tags.rb +2 -2
  43. data/lib/motor/tasks/motor.rake +4 -0
  44. data/lib/motor/version.rb +1 -1
  45. data/ui/dist/{main-5cac10cadc5aa16e9b69.css.gz → main-36a9ee6a9b0427a77d87.css.gz} +0 -0
  46. data/ui/dist/main-36a9ee6a9b0427a77d87.js.gz +0 -0
  47. data/ui/dist/manifest.json +5 -5
  48. metadata +5 -4
  49. data/ui/dist/main-5cac10cadc5aa16e9b69.js.gz +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bb7c564e999344d1cd2b2939b095e1e5c0b27d441b176365192275f623a1a887
4
- data.tar.gz: 748ecbf8188b5375efb7b957503f1d0986144e0fd394956edf81677623a73a59
3
+ metadata.gz: 8798474d7c7dc20d6410dd00f13950997be5e3f5d67e1d2585a57af92cd8db10
4
+ data.tar.gz: 90fcc0b1835fe56b78ecfcc28de03f9d4397b213f6e499afc626c3f4d13b1ef9
5
5
  SHA512:
6
- metadata.gz: 8faab90b6e21873a78e2eab889d2a328550c4533b922040662011930d89e6d9e515ba1eb860ed2978033ab339b0cd686250994cf8ee8a70b89223fb66337ecb5
7
- data.tar.gz: f51c89fdcd5f453793b89a37b4868f282ed2bb5d457cd4cb50b0a786592ccbceb8d0d458a98fa19a3b176698a1787b1845e7321a18ad5d3d11bbd94dd4684ca2
6
+ metadata.gz: c3a34e36e74e5004508e420ba4cdf95e2784d52647dfefccb64fc078b2785cbdd42a7aad84ca41f4c86e2c680b76516f219a41739b7d19601798dda4c9319e06
7
+ data.tar.gz: 727c31fafba431b69e3206d0fb2cff568171760cd43b36b9a42e97a356d5e344c7cacb069c5e9e79899e0fa804f39fd122ae14e30aece4cb77ca64de31620c5f
data/LICENSE CHANGED
@@ -1,4 +1,4 @@
1
- Copyright (c) 2021 Pete Matsyburka
1
+ Copyright (c) 2021 Pete Matsyburka <pete.matsy@gmail.com>
2
2
 
3
3
  Permission is hereby granted, free of charge, to any person obtaining
4
4
  a copy of this software and associated documentation files (the
data/README.md CHANGED
@@ -2,9 +2,9 @@
2
2
 
3
3
  Low-code Admin panel and Business intelligence Rails engine **(no DSL - configurable from the UI)**.
4
4
 
5
- 🤓 [Demo App](https://motor-admin-demo.herokuapp.com)
5
+ 🤓 [Demo App](https://motor-admin.herokuapp.com/demo) | [Features overview](https://www.youtube.com/watch?v=ngVoci8Hll4&list=PLu7llEMh0KcOkR3Uy_RJT0cXPZQKAYVsq&index=1)
6
6
 
7
- [![Admin Panel](https://user-images.githubusercontent.com/5418788/119258612-2351f780-bbd3-11eb-8264-2e044cde868d.png)](https://motor-admin-demo.herokuapp.com)
7
+ [![Admin Panel](https://user-images.githubusercontent.com/5418788/119318538-1f30e300-bc82-11eb-94a4-107c31c93b13.png)](https://motor-admin.herokuapp.com/demo)
8
8
 
9
9
  ## Installation
10
10
  Add this line to your application's Gemfile:
@@ -20,7 +20,7 @@ $ bundle install
20
20
 
21
21
  Create and run migration:
22
22
  ```bash
23
- $ rails generate motor:install && rake db:migrate
23
+ $ rails motor:install && rake db:migrate
24
24
  ```
25
25
 
26
26
  ## Features
@@ -38,11 +38,11 @@ $ rails generate motor:install && rake db:migrate
38
38
 
39
39
  ### Customizable CRUD
40
40
 
41
- ![Resource settings](https://user-images.githubusercontent.com/5418788/119262945-b136de00-bbe5-11eb-846a-6e01c8c42904.png)
41
+ ![Resource settings](https://user-images.githubusercontent.com/5418788/119318569-2a840e80-bc82-11eb-9ba3-f3964eb6f997.png)
42
42
 
43
43
  ![Settings UI](https://user-images.githubusercontent.com/5418788/119263883-90708780-bbe9-11eb-9f9f-f76fed0b7f27.png)
44
44
 
45
- Everything in the admin panel can be configured using the intuitive settings that can be opened via the icon in the top right corner.
45
+ Everything in the admin panel can be configured using intuitive settings UI, which can be opened via the icon in the top right corner.
46
46
 
47
47
  Data displayed on the resource page can be completely custimized via [SQL queries](#sql-queries) and [dashboards](#dashboards) attached to the resource as a tab. Usually, queries used to display resource data should contain `{{resource_name_id}}` [variable](#sql-queries).
48
48
 
@@ -50,13 +50,13 @@ Data displayed on the resource page can be completely custimized via [SQL querie
50
50
 
51
51
  ![Custom actions](https://user-images.githubusercontent.com/5418788/119266132-3c1dd580-bbf2-11eb-9666-09e1640eaf7b.png)
52
52
 
53
- Custom resource actions can be added via Active Record method call, API endpoint, or [custom forms](#forms-builder). Also, it's possible to override default Create/Update/Delete actions.
53
+ Custom resource actions can be added via Active Record method call, API endpoint, or [custom forms](#forms-builder). Also, it's possible to override default create/update/delete actions.
54
54
 
55
55
  ### Forms Builder
56
56
 
57
57
  ![Custom form](https://user-images.githubusercontent.com/5418788/119264008-1391dd80-bbea-11eb-9f14-cb405e77fb60.png)
58
58
 
59
- Values from the form fields can be used in API path via `{field_name}` syntax: `/api/some-endpoint/{field_name}/apply`
59
+ Values from the form fields can be used in API path via `{field_name}` syntax: `/api/some-endpoint/{resource_id}/apply`
60
60
 
61
61
  ### SQL Queries
62
62
 
@@ -68,7 +68,7 @@ Queries can include embeded variables via `{{variable}}` syntax ([mustache](http
68
68
 
69
69
  ![motor-visualization](https://user-images.githubusercontent.com/5418788/119264625-a2075e80-bbec-11eb-986c-6106dd6e47ce.png)
70
70
 
71
- Data from the SQL query can be represented as: [table](https://motor-admin-demo.herokuapp.com/queries/12), [number](https://motor-admin-demo.herokuapp.com/queries/6), [line chart](https://motor-admin-demo.herokuapp.com/queries/3), [bar chart](https://motor-admin-demo.herokuapp.com/queries/1), [pie chart](https://motor-admin-demo.herokuapp.com/queries/9), [funnel](https://motor-admin-demo.herokuapp.com/queries/7), [markdown](https://motor-admin-demo.herokuapp.com/queries/8)
71
+ Data from the SQL query can be represented as: [table](https://motor-admin.herokuapp.com/demo/queries/12), [number](https://motor-admin.herokuapp.com/demo/queries/6), [line chart](https://motor-admin.herokuapp.com/demo/queries/3), [bar chart](https://motor-admin.herokuapp.com/demo/queries/1), [pie chart](https://motor-admin.herokuapp.com/demo/queries/9), [funnel](https://motor-admin.herokuapp.com/demo/queries/7), [markdown](https://motor-admin.herokuapp.com/demo/queries/8)
72
72
 
73
73
  ### Dashboards
74
74
 
@@ -80,20 +80,20 @@ SQL queries can be organized into dashboards to create a convenient representati
80
80
 
81
81
  ![Email alert](https://user-images.githubusercontent.com/5418788/119265049-feb74900-bbed-11eb-8070-bcc8d6113b9b.png)
82
82
 
83
- Query data can be sent via email periodically using the alerts feature. Interval of the alert email can be specified using natural language, e.g. `every day at midnight`, `every Monday at 8 PM`, `every weekday at 6AM and 6PM`, `every minute`.
83
+ Query data can be sent via email periodically using the alerts feature. Interval of the alert email can be specified using natural language, e.g., `every day at midnight`, `every Monday at 8 PM`, `every weekday at 6AM and 6PM`, `every minute`.
84
84
 
85
- The sender address can be specified using `MOTOR_ADMIN_FROM_ADDRESS` environment variable.
85
+ Sender address can be specified using `MOTOR_ALERTS_FROM_ADDRESS` environment variable.
86
86
 
87
87
  ### Intelligence Search
88
88
 
89
89
  ![Intelligence search](https://user-images.githubusercontent.com/5418788/119266559-eea26800-bbf3-11eb-8cb3-d0538aa386a9.png)
90
90
 
91
- Intelligence search can be opened from the top right corner or using <kbd>Cmd</kbd> + <kbd>P</kbd> shortcut.
91
+ Intelligence search can be opened via the top right corner button or using <kbd>Cmd</kbd> + <kbd>P</kbd> shortcut.
92
92
 
93
93
 
94
94
  ### Optimized for Mobile
95
95
 
96
- ![motor-mobile](https://user-images.githubusercontent.com/5418788/119265905-38d61a00-bbf1-11eb-95c8-a4c95a2d77f7.png)
96
+ ![motor-mobile](https://user-images.githubusercontent.com/5418788/119269566-03392d00-bc01-11eb-9e9d-1f6a58fe0749.png)
97
97
 
98
98
 
99
99
  ### Configurations Sync
@@ -127,6 +127,12 @@ Start webpack dev server:
127
127
  yarn install && yarn serve
128
128
  ```
129
129
 
130
+ Setup development database:
131
+
132
+ ```bash
133
+ rake app:db:create && rake app:db:setup
134
+ ```
135
+
130
136
  Start example application in development mode:
131
137
 
132
138
  ```bash
@@ -143,4 +149,4 @@ MOTOR_DEVELOPMENT=true rails s
143
149
 
144
150
  ## License
145
151
 
146
- The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
152
+ The gem is available as open source under the terms of the [MIT License](https://github.com/omohokcoj/motor-admin/blob/master/LICENSE).
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Motor
4
+ module CurrentUserMethod
5
+ def current_user
6
+ if defined?(current_admin)
7
+ current_admin
8
+ elsif defined?(current_admin_user)
9
+ current_admin_user
10
+ elsif defined?(super)
11
+ super
12
+ end
13
+ end
14
+ end
15
+ end
@@ -2,30 +2,26 @@
2
2
 
3
3
  module Motor
4
4
  class ApiBaseController < ActionController::API
5
- rescue_from StandardError do |e|
6
- Rails.logger.error(e)
5
+ include Motor::CurrentUserMethod
7
6
 
8
- render json: { errors: [e.message] }, status: :internal_server_error
9
- end
7
+ class CanCanAbilityManageAll
8
+ include CanCan::Ability
10
9
 
11
- def current_user
12
- if defined?(current_admin)
13
- current_admin
14
- elsif defined?(current_admin_user)
15
- current_admin_user
16
- elsif defined?(super)
17
- super
10
+ def initialize(_)
11
+ can :manage, :all
18
12
  end
19
13
  end
20
14
 
21
- def current_ability
22
- klass = Class.new
23
- klass.include(CanCan::Ability)
24
- klass.define_method(:initialize) do |_user|
25
- can :manage, :all
15
+ unless Rails.env.test?
16
+ rescue_from StandardError do |e|
17
+ Rails.logger.error(e)
18
+
19
+ render json: { errors: [e.message] }, status: :internal_server_error
26
20
  end
21
+ end
27
22
 
28
- klass.new(current_user)
23
+ def current_ability
24
+ CanCanAbilityManageAll.new(current_user)
29
25
  end
30
26
  end
31
27
  end
@@ -2,5 +2,6 @@
2
2
 
3
3
  module Motor
4
4
  class ApplicationController < ActionController::Base
5
+ include Motor::CurrentUserMethod
5
6
  end
6
7
  end
@@ -20,7 +20,7 @@ module Motor
20
20
  def show
21
21
  filename = params[:filename]
22
22
 
23
- return [404, {}, ''] unless Motor::Assets.manifest.values.include?(filename)
23
+ return [404, {}, ''] unless Motor::Assets.manifest.value?(filename)
24
24
 
25
25
  assign_headers(filename)
26
26
 
@@ -4,15 +4,23 @@ module Motor
4
4
  class UiController < ApplicationController
5
5
  layout 'motor/application'
6
6
 
7
- def index
8
- Motor.reload! if Motor.development?
7
+ helper_method :current_user
9
8
 
10
- Motor::Configs::SyncFromFile.call
9
+ def index
10
+ render_ui
11
+ end
11
12
 
12
- render :show
13
+ def new
14
+ render_ui
13
15
  end
14
16
 
15
17
  def show
18
+ render_ui
19
+ end
20
+
21
+ private
22
+
23
+ def render_ui
16
24
  Motor.reload! if Motor.development?
17
25
 
18
26
  Motor::Configs::SyncFromFile.call
@@ -26,11 +26,11 @@ module Motor
26
26
  end
27
27
 
28
28
  def from_address
29
- from = ENV['MOTOR_ADMIN_FROM_ADDRESS'].presence
29
+ from = ENV['MOTOR_ALERTS_FROM_ADDRESS'].presence
30
30
 
31
31
  from ||= application_mailer_default_from
32
32
  from ||= mailer_config_from_address
33
- from ||= "reports@#{ENV['HOST'].sub(/\Awww\./, '')}" if ENV['HOST'].present?
33
+ from ||= "reports@#{ENV['HOST'].delete_prefix('www.')}" if ENV['HOST'].present?
34
34
 
35
35
  from || 'reports@example.com'
36
36
  end
@@ -44,7 +44,7 @@ module Motor
44
44
  def mailer_config_from_address
45
45
  return if Rails.application.config.action_mailer.default_url_options&.dig(:host).blank?
46
46
 
47
- "reports@#{Rails.application.config.action_mailer.default_url_options[:host].sub(/\Awww\./, '')}"
47
+ "reports@#{Rails.application.config.action_mailer.default_url_options[:host].delete_prefix('www.')}"
48
48
  end
49
49
  end
50
50
  end
@@ -11,6 +11,7 @@ module Motor
11
11
  has_many :taggable_tags, as: :taggable, dependent: :destroy
12
12
  has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
13
13
 
14
+ attribute :preferences, default: -> { HashWithIndifferentAccess.new }
14
15
  serialize :preferences, HashSerializer
15
16
 
16
17
  scope :active, -> { where(deleted_at: nil) }
@@ -9,6 +9,7 @@ module Motor
9
9
  has_many :taggable_tags, as: :taggable, dependent: :destroy
10
10
  has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
11
11
 
12
+ attribute :preferences, default: -> { HashWithIndifferentAccess.new }
12
13
  serialize :preferences, HashSerializer
13
14
 
14
15
  scope :active, -> { where(deleted_at: nil) }
@@ -9,6 +9,7 @@ module Motor
9
9
  has_many :taggable_tags, as: :taggable, dependent: :destroy
10
10
  has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
11
11
 
12
+ attribute :preferences, default: -> { HashWithIndifferentAccess.new }
12
13
  serialize :preferences, HashSerializer
13
14
 
14
15
  scope :active, -> { where(deleted_at: nil) }
@@ -10,6 +10,7 @@ module Motor
10
10
  has_many :tags, through: :taggable_tags, class_name: 'Motor::Tag'
11
11
  has_many :alerts, dependent: :destroy
12
12
 
13
+ attribute :preferences, default: -> { HashWithIndifferentAccess.new }
13
14
  serialize :preferences, HashSerializer
14
15
 
15
16
  scope :active, -> { where(deleted_at: nil) }
@@ -4,6 +4,7 @@ module Motor
4
4
  class Resource < ::Motor::ApplicationRecord
5
5
  audited
6
6
 
7
+ attribute :preferences, default: -> { HashWithIndifferentAccess.new }
7
8
  serialize :preferences, HashSerializer
8
9
  end
9
10
  end
@@ -2,6 +2,6 @@
2
2
 
3
3
  module Motor
4
4
  class Tag < ::Motor::ApplicationRecord
5
- has_many :taggable_tags
5
+ has_many :taggable_tags, dependent: :destroy
6
6
  end
7
7
  end
@@ -1 +1 @@
1
- <%= raw(Motor::Configs::BuildUiAppTag.call) %>
1
+ <%= raw(Motor::Configs::BuildUiAppTag.call(current_user)) %>
data/config/routes.rb CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Motor::Admin.routes.draw do
4
4
  namespace :motor, path: '' do
5
- scope 'api', as: 'api' do
5
+ scope 'api', as: :api do
6
6
  resources :run_queries, only: %i[show create]
7
7
  resources :send_alerts, only: %i[create]
8
8
  resources :queries, only: %i[index show create update destroy]
@@ -34,16 +34,15 @@ Motor::Admin.routes.draw do
34
34
 
35
35
  get '/', to: 'ui#show'
36
36
 
37
- scope as: 'ui' do
37
+ scope as: :ui do
38
+ get '/data(/*path)', to: 'ui#index', as: :data
39
+
38
40
  with_options controller: 'ui' do
39
- resources :data, only: %i[index show],
40
- param: 'path',
41
- constraints: { path: /.+/ }
42
41
  resources :reports, only: %i[index show]
43
- resources :queries, only: %i[index show]
44
- resources :dashboards, only: %i[index show]
45
- resources :alerts, only: %i[index show]
46
- resources :forms, only: %i[index show]
42
+ resources :queries, only: %i[index show new]
43
+ resources :dashboards, only: %i[index show new]
44
+ resources :alerts, only: %i[index show new]
45
+ resources :forms, only: %i[index show new]
47
46
  end
48
47
  end
49
48
  end
@@ -1,62 +1,65 @@
1
1
  class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Migration.current_version %>]
2
2
  def self.up
3
3
  create_table :motor_queries do |t|
4
- t.column :name, :string, null: false
5
- t.column :description, :string
6
- t.column :sql_body, :string, null: false
7
- t.column :preferences, :string, null: false, default: '{}'
8
- t.column :author_id, :integer
9
- t.column :author_type, :string
4
+ t.column :name, :text, null: false
5
+ t.column :description, :text
6
+ t.column :sql_body, :text, null: false
7
+ t.column :preferences, :text, null: false
8
+ t.column :author_id, :bigint
9
+ t.column :author_type, :text
10
10
  t.column :deleted_at, :datetime
11
11
 
12
12
  t.timestamps
13
13
 
14
14
  t.index :updated_at
15
- t.index 'lower(name)',
16
- name: 'motor_queries_lower_name_unique_index',
15
+ t.index 'name',
16
+ name: 'motor_queries_name_unique_index',
17
17
  unique: true,
18
- where: 'deleted_at IS NULL'
18
+ where: 'deleted_at IS NULL',
19
+ length: { name: 255 }
19
20
  end
20
21
 
21
22
  create_table :motor_dashboards do |t|
22
- t.column :title, :string, null: false
23
- t.column :description, :string
24
- t.column :preferences, :string, null: false, default: '{}'
25
- t.column :author_id, :integer
26
- t.column :author_type, :string
23
+ t.column :title, :text, null: false
24
+ t.column :description, :text
25
+ t.column :preferences, :text, null: false
26
+ t.column :author_id, :bigint
27
+ t.column :author_type, :text
27
28
  t.column :deleted_at, :datetime
28
29
 
29
30
  t.timestamps
30
31
 
31
32
  t.index :updated_at
32
- t.index 'lower(title)',
33
- name: 'motor_dashboards_lower_title_unique_index',
33
+ t.index 'title',
34
+ name: 'motor_dashboards_title_unique_index',
34
35
  unique: true,
35
- where: 'deleted_at IS NULL'
36
+ where: 'deleted_at IS NULL',
37
+ length: { title: 255 }
36
38
  end
37
39
 
38
40
  create_table :motor_forms do |t|
39
- t.column :name, :string, null: false
40
- t.column :description, :string
41
- t.column :api_path, :string, null: false
42
- t.column :http_method, :string, null: false
43
- t.column :preferences, :string, null: false, default: '{}'
44
- t.column :author_id, :integer
45
- t.column :author_type, :string
41
+ t.column :name, :text, null: false
42
+ t.column :description, :text
43
+ t.column :api_path, :text, null: false
44
+ t.column :http_method, :text, null: false
45
+ t.column :preferences, :text, null: false
46
+ t.column :author_id, :bigint
47
+ t.column :author_type, :text
46
48
  t.column :deleted_at, :datetime
47
49
 
48
50
  t.timestamps
49
51
 
50
52
  t.index :updated_at
51
- t.index 'lower(name)',
52
- name: 'motor_forms_lower_name_unique_index',
53
+ t.index 'name',
54
+ name: 'motor_forms_name_unique_index',
53
55
  unique: true,
54
- where: 'deleted_at IS NULL'
56
+ where: 'deleted_at IS NULL',
57
+ length: { name: 255 }
55
58
  end
56
59
 
57
60
  create_table :motor_resources do |t|
58
- t.column :name, :string, null: false, index: { unique: true }
59
- t.column :preferences, :string, null: false, default: '{}'
61
+ t.column :name, :text, null: false, index: { unique: true, length: 255 }
62
+ t.column :preferences, :text, null: false
60
63
 
61
64
  t.timestamps
62
65
 
@@ -64,8 +67,8 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
64
67
  end
65
68
 
66
69
  create_table :motor_configs do |t|
67
- t.column :key, :string, null: false, index: { unique: true }
68
- t.column :value, :string, null: false, default: '{}'
70
+ t.column :key, :text, null: false, index: { unique: true, length: 255 }
71
+ t.column :value, :text, null: false
69
72
 
70
73
  t.timestamps
71
74
 
@@ -74,74 +77,80 @@ class <%= migration_class_name %> < ActiveRecord::Migration[<%= ActiveRecord::Mi
74
77
 
75
78
  create_table :motor_alerts do |t|
76
79
  t.references :query, null: false, foreign_key: { to_table: :motor_queries }, index: true
77
- t.column :name, :string, null: false
78
- t.column :description, :string
79
- t.column :to_emails, :string, null: false
80
+ t.column :name, :text, null: false
81
+ t.column :description, :text
82
+ t.column :to_emails, :text, null: false
80
83
  t.column :is_enabled, :boolean, null: false, default: true
81
- t.column :preferences, :string, null: false, default: '{}'
82
- t.column :author_id, :integer
83
- t.column :author_type, :string
84
+ t.column :preferences, :text, null: false
85
+ t.column :author_id, :bigint
86
+ t.column :author_type, :text
84
87
  t.column :deleted_at, :datetime
85
88
 
86
89
  t.timestamps
87
90
 
88
91
  t.index :updated_at
89
- t.index 'lower(name)',
90
- name: 'motor_alerts_lower_name_unique_index',
92
+ t.index 'name',
93
+ name: 'motor_alerts_name_unique_index',
91
94
  unique: true,
92
- where: 'deleted_at IS NULL'
95
+ where: 'deleted_at IS NULL',
96
+ length: { name: 255 }
93
97
  end
94
98
 
95
99
  create_table :motor_alert_locks do |t|
96
100
  t.references :alert, null: false, foreign_key: { to_table: :motor_alerts }
97
- t.column :lock_timestamp, :string, null: false
101
+ t.column :lock_timestamp, :text, null: false
98
102
 
99
103
  t.timestamps
100
104
 
101
- t.index %i[alert_id lock_timestamp], unique: true
105
+ t.index %i[alert_id lock_timestamp], unique: true, length: { lock_timestamp: 255 }
102
106
  end
103
107
 
104
108
  create_table :motor_tags do |t|
105
- t.column :name, :string, null: false
109
+ t.column :name, :text, null: false
106
110
 
107
111
  t.timestamps
108
112
 
109
- t.index 'lower(name)',
110
- name: 'motor_tags_lower_name_unique_index',
111
- unique: true
113
+ t.index 'name',
114
+ name: 'motor_tags_name_unique_index',
115
+ unique: true,
116
+ length: { name: 255 }
112
117
  end
113
118
 
114
119
  create_table :motor_taggable_tags do |t|
115
120
  t.references :tag, null: false, foreign_key: { to_table: :motor_tags }, index: true
116
- t.column :taggable_id, :integer, null: false
117
- t.column :taggable_type, :string, null: false
121
+ t.column :taggable_id, :bigint, null: false
122
+ t.column :taggable_type, :text, null: false
118
123
 
119
124
  t.index %i[taggable_id taggable_type tag_id],
120
125
  name: 'motor_polymorphic_association_tag_index',
121
- unique: true
126
+ unique: true,
127
+ length: { taggable_type: 255 }
122
128
  end
123
129
 
124
130
  create_table :motor_audits do |t|
125
- t.column :auditable_id, :integer
126
- t.column :auditable_type, :string
127
- t.column :associated_id, :integer
128
- t.column :associated_type, :string
129
- t.column :user_id, :integer
130
- t.column :user_type, :string
131
- t.column :username, :string
132
- t.column :action, :string
131
+ t.column :auditable_id, :bigint
132
+ t.column :auditable_type, :text
133
+ t.column :associated_id, :bigint
134
+ t.column :associated_type, :text
135
+ t.column :user_id, :bigint
136
+ t.column :user_type, :text
137
+ t.column :username, :text
138
+ t.column :action, :text
133
139
  t.column :audited_changes, :text
134
- t.column :version, :integer, default: 0
135
- t.column :comment, :string
136
- t.column :remote_address, :string
137
- t.column :request_uuid, :string
140
+ t.column :version, :bigint, default: 0
141
+ t.column :comment, :text
142
+ t.column :remote_address, :text
143
+ t.column :request_uuid, :text
138
144
  t.column :created_at, :datetime
139
145
  end
140
146
 
141
- add_index :motor_audits, %i[auditable_type auditable_id version], name: 'motor_auditable_index'
142
- add_index :motor_audits, %i[associated_type associated_id], name: 'motor_auditable_associated_index'
143
- add_index :motor_audits, %i[user_id user_type], name: 'motor_auditable_user_index'
144
- add_index :motor_audits, :request_uuid
147
+ add_index :motor_audits, %i[auditable_type auditable_id version], name: 'motor_auditable_index',
148
+ length: { auditable_type: 255 }
149
+ add_index :motor_audits, %i[associated_type associated_id], name: 'motor_auditable_associated_index',
150
+ length: { associated_type: 255 }
151
+ add_index :motor_audits, %i[user_id user_type], name: 'motor_auditable_user_index',
152
+ length: { user_type: 255 }
153
+ add_index :motor_audits, :request_uuid, length: { request_uuid: 255 }
145
154
  add_index :motor_audits, :created_at
146
155
  end
147
156
 
data/lib/motor.rb CHANGED
@@ -40,7 +40,6 @@ module Motor
40
40
  (defined?(::Puma) && File.basename($PROGRAM_NAME) == 'puma') ||
41
41
  defined?(::Unicorn::HttpServer) ||
42
42
  defined?(::Mongrel::HttpServer) ||
43
- defined?(::WEBrick::VERSION) ||
44
43
  defined?(JRuby::Rack::VERSION) ||
45
44
  defined?(::Trinidad::Server)
46
45
  end
@@ -1,6 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- module ActiveRecordUtils
3
+ module Motor
4
+ module ActiveRecordUtils
5
+ module_function
6
+
7
+ def reset_id_sequence!(model)
8
+ case ActiveRecord::Base.connection.class.name
9
+ when 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
10
+ ActiveRecord::Base.connection.reset_pk_sequence!(model.table_name)
11
+ else
12
+ ActiveRecord::Base.connection.reset_sequence!(model.table_name, 'id')
13
+ end
14
+ end
15
+ end
4
16
  end
5
17
 
6
18
  require_relative './active_record_utils/types'
@@ -13,6 +13,10 @@ module Motor
13
13
  super(**hash.with_indifferent_access.slice(*KEYWORD_ARGS).symbolize_keys)
14
14
  end
15
15
 
16
+ def create_after_upload!(hash)
17
+ super(**hash.with_indifferent_access.slice(*KEYWORD_ARGS).symbolize_keys)
18
+ end
19
+
16
20
  def create_after_unfurling!(hash)
17
21
  super(**hash.with_indifferent_access.slice(*KEYWORD_ARGS).symbolize_keys)
18
22
  end
data/lib/motor/admin.rb CHANGED
@@ -43,7 +43,7 @@ module Motor
43
43
  end
44
44
 
45
45
  initializer 'motor.alerts.scheduler' do
46
- config.after_initialize do |_app|
46
+ config.after_initialize do
47
47
  next unless Motor.server?
48
48
 
49
49
  Motor::Alerts::Scheduler::SCHEDULER_TASK.execute
@@ -66,13 +66,12 @@ module Motor
66
66
  end
67
67
 
68
68
  initializer 'motor.active_storage.extensions' do
69
- ActiveSupport.on_load(:active_storage_attachment) do
70
- ActiveStorage::Attachment.include(Motor::ActiveRecordUtils::ActiveStorageLinksExtension)
71
- end
69
+ next unless defined?(ActiveStorage::Engine)
72
70
 
73
- ActiveSupport.on_load(:active_storage_blob) do
74
- ActiveStorage::Blob.singleton_class.prepend(Motor::ActiveRecordUtils::ActiveStorageBlobPatch)
75
- end
71
+ ActiveStorage::Engine.eager_load!
72
+
73
+ ActiveStorage::Attachment.include(Motor::ActiveRecordUtils::ActiveStorageLinksExtension)
74
+ ActiveStorage::Blob.singleton_class.prepend(Motor::ActiveRecordUtils::ActiveStorageBlobPatch)
76
75
  end
77
76
  end
78
77
  end
@@ -28,7 +28,7 @@ module Motor
28
28
  end
29
29
 
30
30
  def create_from_params!(params, current_user = nil)
31
- raise NameAlreadyExists if Alert.exists?(['lower(name) = ?', params[:name].to_s.downcase])
31
+ raise NameAlreadyExists if Alert.exists?(name: params[:name])
32
32
 
33
33
  alert = build_from_params(params, current_user)
34
34
 
@@ -77,7 +77,7 @@ module Motor
77
77
  end
78
78
 
79
79
  def archive_with_existing_name(alert)
80
- Motor::Alert.where(['lower(name) = ? AND id != ?', alert.name.to_s.downcase, alert.id])
80
+ Motor::Alert.where(['name = ? AND id != ?', alert.name, alert.id])
81
81
  .update_all(deleted_at: Time.current)
82
82
  end
83
83
 
@@ -87,9 +87,9 @@ module Motor
87
87
 
88
88
  def name_already_exists?(alert)
89
89
  if alert.new_record?
90
- Alert.exists?(['lower(name) = ?', alert.name.to_s.downcase])
90
+ Alert.exists?(name: alert.name)
91
91
  else
92
- Alert.exists?(['lower(name) = ? AND id != ?', alert.name.to_s.downcase, alert.id])
92
+ Alert.exists?(['name = ? AND id != ?', alert.name, alert.id])
93
93
  end
94
94
  end
95
95
  end
@@ -3,7 +3,7 @@
3
3
  module Motor
4
4
  module Alerts
5
5
  module Scheduler
6
- SCHEDULER_INTERVAL = 10.seconds
6
+ SCHEDULER_INTERVAL = 1.minute
7
7
  CHECK_BEHIND_DURATION = 15.minutes
8
8
 
9
9
  SCHEDULER_TASK = Concurrent::TimerTask.new(
@@ -18,7 +18,7 @@ module Motor
18
18
  end
19
19
 
20
20
  def apply_filter_scope(rel, scope)
21
- configs = Motor::Resource.find_by_name(rel.klass.name.underscore)
21
+ configs = Motor::Resource.find_by(name: rel.klass.name.underscore)
22
22
 
23
23
  return rel unless configs
24
24
 
@@ -10,12 +10,12 @@ module Motor
10
10
 
11
11
  rel = rel.preload_associations_lazily if rel.is_a?(ActiveRecord::Relation)
12
12
 
13
- json_params = {}
13
+ json_params = {}.with_indifferent_access
14
14
 
15
15
  assign_include_params(json_params, rel, params)
16
16
  assign_fields_params(json_params, rel, params)
17
17
 
18
- rel.as_json(json_params.with_indifferent_access)
18
+ rel.as_json(json_params)
19
19
  end
20
20
 
21
21
  def assign_include_params(json_params, _rel, api_params)
@@ -51,11 +51,11 @@ module Motor
51
51
  params[:fields].each do |key, fields|
52
52
  fields = fields.split(',') if fields.is_a?(String)
53
53
 
54
- merge_fields_params!(key, fields, json_params, model)
54
+ merge_fields_params!(json_params, key, fields, model)
55
55
  end
56
56
  end
57
57
 
58
- def merge_fields_params!(key, fields, json_params, model)
58
+ def merge_fields_params!(json_params, key, fields, model)
59
59
  model_name = model.name.underscore
60
60
 
61
61
  if key == model_name || model_name.split('/').last == key
@@ -96,8 +96,8 @@ module Motor
96
96
  def normalize_include_params(params)
97
97
  case params
98
98
  when Array
99
- params.each_with_object({}) do |name, hash|
100
- hash[name] = { 'include' => {} }
99
+ params.index_with do |_|
100
+ { 'include' => {} }
101
101
  end
102
102
  when String
103
103
  { params => { 'include' => {} } }
@@ -11,6 +11,7 @@ module Motor
11
11
  params ||= {}
12
12
 
13
13
  rel = rel.limit([MAX_PER_PAGE, (params[:limit] || MAX_PER_PAGE).to_i].min)
14
+
14
15
  rel.offset(params[:offset].to_i)
15
16
  end
16
17
  end
@@ -57,13 +57,7 @@ module Motor
57
57
  def find_searchable_columns(model)
58
58
  model.columns.map do |column|
59
59
  next unless column.type.in?(COLUMN_TYPES)
60
-
61
- has_inclusion_validator =
62
- model.validators_on(column.name).any? do |e|
63
- e.is_a?(ActiveModel::Validations::InclusionValidator)
64
- end
65
-
66
- next if has_inclusion_validator
60
+ next if model.validators_on(column.name).any?(ActiveModel::Validations::InclusionValidator)
67
61
 
68
62
  column.name
69
63
  end.compact
@@ -13,7 +13,9 @@ module Motor
13
13
  arel_order = build_arel_order(rel.klass, param)
14
14
  join_params = build_join_params(rel.klass, param)
15
15
 
16
- rel.reorder(arel_order).left_joins(join_params)
16
+ rel = rel.left_joins(join_params) if join_params.present?
17
+
18
+ rel.reorder(arel_order)
17
19
  end
18
20
 
19
21
  def build_join_params(_model, param)
data/lib/motor/assets.rb CHANGED
@@ -16,7 +16,7 @@ module Motor
16
16
 
17
17
  def icons
18
18
  manifest.select do |k, v|
19
- !k.ends_with?('.gz') && v.starts_with?('icons/') && !v.include?('DS_Store')
19
+ !k.ends_with?('.gz') && v.starts_with?('icons/') && v.exclude?('DS_Store')
20
20
  end.keys
21
21
  end
22
22
 
@@ -13,6 +13,7 @@ module Motor
13
13
  'location' => 'gps',
14
14
  'photo' => 'photo',
15
15
  'image' => 'photo',
16
+ 'screenshot' => 'photo',
16
17
  'picture' => 'photo',
17
18
  'video' => 'video',
18
19
  'file' => 'file',
@@ -12,6 +12,8 @@ module Motor
12
12
  models.map do |model|
13
13
  Object.const_get(model.name)
14
14
 
15
+ next unless ActiveRecord::Base.connection.table_exists?(model.table_name)
16
+
15
17
  schema = build_model_schema(model)
16
18
 
17
19
  if model.respond_to?(:devise_modules)
@@ -20,7 +22,7 @@ module Motor
20
22
 
21
23
  schema
22
24
  rescue StandardError, NotImplementedError => e
23
- Rails.logger.error(e) if model.name != 'Audited::Audit'
25
+ Rails.logger.error(e)
24
26
 
25
27
  next
26
28
  end.compact
@@ -34,6 +36,7 @@ module Motor
34
36
  models -= Motor::ApplicationRecord.descendants
35
37
  models -= [Motor::Audit]
36
38
  models -= [ActiveRecord::SchemaMigration] if defined?(ActiveRecord::SchemaMigration)
39
+ models -= [ActiveRecord::InternalMetadata] if defined?(ActiveRecord::InternalMetadata)
37
40
  models -= [ActiveStorage::Blob] if defined?(ActiveStorage::Blob)
38
41
  models -= [ActiveStorage::VariantRecord] if defined?(ActiveStorage::VariantRecord)
39
42
 
@@ -101,7 +104,7 @@ module Motor
101
104
  name: column.name,
102
105
  display_name: Utils.humanize_column_name(column.name),
103
106
  column_type: is_enum ? 'string' : UNIFIED_TYPES[column.type.to_s] || column.type.to_s,
104
- is_array: column.array?,
107
+ is_array: column.respond_to?(:array?) && column.array?,
105
108
  access_type: COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE),
106
109
  default_value: default_attrs[column.name],
107
110
  validators: fetch_validators(model, column.name),
@@ -12,21 +12,21 @@ module Motor
12
12
 
13
13
  module_function
14
14
 
15
- def call
15
+ def call(current_user = nil)
16
16
  cache_keys = LoadFromCache.load_cache_keys
17
17
 
18
- CACHE_STORE.fetch(cache_keys.hash) do
18
+ CACHE_STORE.fetch("#{cache_keys.hash}#{current_user&.id}") do
19
19
  CACHE_STORE.clear
20
20
 
21
- Motor::ApplicationController.helpers.content_tag(
22
- :div, '', id: 'app', data: build_data(cache_keys)
23
- )
21
+ Motor::ApplicationController.helpers.tag.div('', id: 'app', data: build_data(cache_keys, current_user))
24
22
  end
25
23
  end
26
24
 
27
25
  # @return [Hash]
28
- def build_data(cache_keys = {})
26
+ def build_data(cache_keys = {}, current_user = nil)
29
27
  {
28
+ current_user: current_user&.as_json(only: %i[id email]),
29
+ audits_count: Motor::Audit.count,
30
30
  base_path: Motor::Admin.routes.url_helpers.motor_path,
31
31
  schema: Motor::BuildSchema.call(cache_keys),
32
32
  header_links: header_links_data_hash(cache_keys[:configs]),
@@ -3,7 +3,7 @@
3
3
  module Motor
4
4
  module Configs
5
5
  module LoadFromCache
6
- CACHE_STORE = ActiveSupport::Cache::MemoryStore.new(size: 10.megabytes)
6
+ CACHE_HASH = HashWithIndifferentAccess.new
7
7
 
8
8
  module_function
9
9
 
@@ -56,25 +56,37 @@ module Motor
56
56
  end
57
57
  end
58
58
 
59
- def maybe_fetch_from_cache(type, cache_key, &block)
60
- return block.call unless cache_key
59
+ def maybe_fetch_from_cache(type, cache_key)
60
+ return yield unless cache_key
61
61
 
62
- CACHE_STORE.fetch(type + cache_key.to_s, &block)
62
+ if CACHE_HASH[type] && CACHE_HASH[type][:key] == cache_key
63
+ CACHE_HASH[type][:value]
64
+ else
65
+ result = yield
66
+
67
+ CACHE_HASH[type] = { key: cache_key, value: result }
68
+
69
+ result
70
+ end
63
71
  end
64
72
 
65
73
  def load_cache_keys
66
- ActiveRecord::Base.connection.execute(
67
- "(#{
68
- [
69
- Motor::Config.select("'configs', MAX(updated_at)").to_sql,
70
- Motor::Resource.select("'resources', MAX(updated_at)").to_sql,
71
- Motor::Dashboard.select("'dashboards', MAX(updated_at)").to_sql,
72
- Motor::Alert.select("'alerts', MAX(updated_at)").to_sql,
73
- Motor::Query.select("'queries', MAX(updated_at)").to_sql,
74
- Motor::Form.select("'forms', MAX(updated_at)").to_sql
75
- ].join(') UNION (')
76
- })"
77
- ).to_a.map(&:values).to_h.with_indifferent_access
74
+ result = ActiveRecord::Base.connection.execute(cache_keys_sql).to_a
75
+
76
+ result = result.map(&:values) if result.first.is_a?(Hash)
77
+
78
+ result.to_h.with_indifferent_access
79
+ end
80
+
81
+ def cache_keys_sql
82
+ [
83
+ Motor::Config.select("'configs', MAX(updated_at)").to_sql,
84
+ Motor::Resource.select("'resources', MAX(updated_at)").to_sql,
85
+ Motor::Dashboard.select("'dashboards', MAX(updated_at)").to_sql,
86
+ Motor::Alert.select("'alerts', MAX(updated_at)").to_sql,
87
+ Motor::Query.select("'queries', MAX(updated_at)").to_sql,
88
+ Motor::Form.select("'forms', MAX(updated_at)").to_sql
89
+ ].join(' UNION ')
78
90
  end
79
91
  end
80
92
  end
@@ -87,7 +87,7 @@ module Motor
87
87
 
88
88
  archive_taggable_items(records - processed_records, configs_timestamp)
89
89
 
90
- ActiveRecord::Base.connection.reset_pk_sequence!(records.klass.table_name)
90
+ ActiveRecordUtils.reset_id_sequence!(records.klass)
91
91
  end
92
92
 
93
93
  def update_taggable_items(records, config_items, update_proc)
@@ -9,7 +9,7 @@ module Motor
9
9
  module_function
10
10
 
11
11
  def call(remote_url, api_key)
12
- url = remote_url.sub(%r{/\z}, '') + Motor::Configs::SYNC_API_PATH
12
+ url = remote_url.delete_suffix('/') + Motor::Configs::SYNC_API_PATH
13
13
 
14
14
  sync_from_remote!(url, api_key)
15
15
  sync_to_remote!(url, api_key)
@@ -9,6 +9,7 @@ module Motor
9
9
  module_function
10
10
 
11
11
  def call
12
+ return if Rails.env.test?
12
13
  return if THREAD_POOL.queue_length.positive?
13
14
 
14
15
  THREAD_POOL.post do
@@ -16,7 +16,7 @@ module Motor
16
16
  end
17
17
 
18
18
  def create_from_params!(params, current_user = nil)
19
- raise TitleAlreadyExists if Dashboard.exists?(['lower(title) = ?', params[:title].to_s.downcase])
19
+ raise TitleAlreadyExists if Dashboard.exists?(title: params[:title])
20
20
 
21
21
  dashboard = build_from_params(params, current_user)
22
22
 
@@ -57,15 +57,15 @@ module Motor
57
57
  end
58
58
 
59
59
  def archive_with_existing_name(dashboard)
60
- Motor::Dashboard.where(['lower(title) = ? AND id != ?', dashboard.title.to_s.downcase, dashboard.id])
60
+ Motor::Dashboard.where(['title = ? AND id != ?', dashboard.title, dashboard.id])
61
61
  .update_all(deleted_at: Time.current)
62
62
  end
63
63
 
64
64
  def title_already_exists?(dashboard)
65
65
  if dashboard.new_record?
66
- Motor::Dashboard.exists?(['lower(title) = ?', dashboard.title.to_s.downcase])
66
+ Motor::Dashboard.exists?(title: dashboard.title)
67
67
  else
68
- Motor::Dashboard.exists?(['lower(title) = ? AND id != ?', dashboard.title.to_s.downcase, dashboard.id])
68
+ Motor::Dashboard.exists?(['title = ? AND id != ?', dashboard.title, dashboard.id])
69
69
  end
70
70
  end
71
71
  end
@@ -16,7 +16,7 @@ module Motor
16
16
  end
17
17
 
18
18
  def create_from_params!(params, current_user = nil)
19
- raise NameAlreadyExists if Form.exists?(['lower(name) = ?', params[:name].to_s.downcase])
19
+ raise NameAlreadyExists if Form.exists?(name: params[:name])
20
20
 
21
21
  form = build_from_params(params, current_user)
22
22
 
@@ -57,15 +57,15 @@ module Motor
57
57
  end
58
58
 
59
59
  def archive_with_existing_name(form)
60
- Motor::Form.where(['lower(name) = ? AND id != ?', form.name.to_s.downcase, form.id])
60
+ Motor::Form.where(['name = ? AND id != ?', form.name, form.id])
61
61
  .update_all(deleted_at: Time.current)
62
62
  end
63
63
 
64
64
  def name_already_exists?(form)
65
65
  if form.new_record?
66
- Motor::Form.exists?(['lower(name) = ?', form.name.to_s.downcase])
66
+ Motor::Form.exists?(['name = ?', form.name])
67
67
  else
68
- Motor::Form.exists?(['lower(name) = ? AND id != ?', form.name.to_s.downcase, form.id])
68
+ Motor::Form.exists?(['name = ? AND id != ?', form.name, form.id])
69
69
  end
70
70
  end
71
71
  end
@@ -16,7 +16,7 @@ module Motor
16
16
  end
17
17
 
18
18
  def create_from_params!(params, current_user = nil)
19
- raise NameAlreadyExists if Query.exists?(['lower(name) = ?', params[:name].to_s.downcase])
19
+ raise NameAlreadyExists if Query.exists?(name: params[:name])
20
20
 
21
21
  query = build_from_params(params, current_user)
22
22
 
@@ -57,15 +57,15 @@ module Motor
57
57
  end
58
58
 
59
59
  def archive_with_existing_name(query)
60
- Motor::Query.where(['lower(name) = ? AND id != ?', query.name.to_s.downcase, query.id])
60
+ Motor::Query.where(['name = ? AND id != ?', query.name, query.id])
61
61
  .update_all(deleted_at: Time.current)
62
62
  end
63
63
 
64
64
  def name_already_exists?(query)
65
65
  if query.new_record?
66
- Query.exists?(['lower(name) = ?', query.name.to_s.downcase])
66
+ Query.exists?(name: query.name)
67
67
  else
68
- Query.exists?(['lower(name) = ? AND id != ?', query.name.to_s.downcase, query.id])
68
+ Query.exists?(['name = ? AND id != ?', query.name, query.id])
69
69
  end
70
70
  end
71
71
  end
@@ -35,8 +35,8 @@ module Motor
35
35
 
36
36
  ActiveRecord::Base.transaction do
37
37
  result =
38
- case ActiveRecord::Base.connection
39
- when ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
38
+ case ActiveRecord::Base.connection.class.name
39
+ when 'ActiveRecord::ConnectionAdapters::PostgreSQLAdapter'
40
40
  PostgresqlExecQuery.call(ActiveRecord::Base.connection, statement)
41
41
  else
42
42
  ActiveRecord::Base.connection.exec_query(*statement)
@@ -73,7 +73,7 @@ module Motor
73
73
 
74
74
  attributes = build_statement_attributes(query_variables)
75
75
 
76
- [format(WITH_STATEMENT_TEMPLATE, sql_body: sql.strip.gsub(/;\z/, ''), limit: limit), 'SQL', attributes]
76
+ [format(WITH_STATEMENT_TEMPLATE, sql_body: sql.strip.delete_suffix(';'), limit: limit), 'SQL', attributes]
77
77
  end
78
78
 
79
79
  # @param variables [Array<(String, Object)>]
data/lib/motor/tags.rb CHANGED
@@ -8,9 +8,9 @@ module Motor
8
8
  return taggable unless tags
9
9
 
10
10
  tags.each do |tag_name|
11
- next if taggable.taggable_tags.find { |tt| tt.tag.name.downcase == tag_name.downcase }
11
+ next if taggable.taggable_tags.find { |tt| tt.tag.name.casecmp(tag_name).zero? }
12
12
 
13
- tag = Tag.where('lower(name) = ?', tag_name.downcase).take || Tag.new(name: tag_name)
13
+ tag = Tag.find_or_initialize_by(name: tag_name)
14
14
 
15
15
  taggable.taggable_tags.new(tag: tag)
16
16
  end
@@ -3,6 +3,10 @@
3
3
  namespace :motor do
4
4
  desc 'Update configs/motor.yml file'
5
5
 
6
+ task install: :environment do
7
+ Rails::Generators.invoke('motor:install')
8
+ end
9
+
6
10
  task dump: :environment do
7
11
  Motor::Configs::WriteToFile.write_with_lock
8
12
 
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.47'
4
+ VERSION = '0.1.52'
5
5
  end
@@ -2068,11 +2068,11 @@
2068
2068
  "mail-opened.svg": "icons/mail-opened.svg",
2069
2069
  "mail.svg": "icons/mail.svg",
2070
2070
  "mailbox.svg": "icons/mailbox.svg",
2071
- "main-5cac10cadc5aa16e9b69.css.gz": "main-5cac10cadc5aa16e9b69.css.gz",
2072
- "main-5cac10cadc5aa16e9b69.js.LICENSE.txt": "main-5cac10cadc5aa16e9b69.js.LICENSE.txt",
2073
- "main-5cac10cadc5aa16e9b69.js.gz": "main-5cac10cadc5aa16e9b69.js.gz",
2074
- "main.css": "main-5cac10cadc5aa16e9b69.css",
2075
- "main.js": "main-5cac10cadc5aa16e9b69.js",
2071
+ "main-36a9ee6a9b0427a77d87.css.gz": "main-36a9ee6a9b0427a77d87.css.gz",
2072
+ "main-36a9ee6a9b0427a77d87.js.LICENSE.txt": "main-36a9ee6a9b0427a77d87.js.LICENSE.txt",
2073
+ "main-36a9ee6a9b0427a77d87.js.gz": "main-36a9ee6a9b0427a77d87.js.gz",
2074
+ "main.css": "main-36a9ee6a9b0427a77d87.css",
2075
+ "main.js": "main-36a9ee6a9b0427a77d87.js",
2076
2076
  "man.svg": "icons/man.svg",
2077
2077
  "manual-gearbox.svg": "icons/manual-gearbox.svg",
2078
2078
  "map-2.svg": "icons/map-2.svg",
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.47
4
+ version: 0.1.52
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-05-23 00:00:00.000000000 Z
11
+ date: 2021-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord-filter
@@ -118,6 +118,7 @@ files:
118
118
  - LICENSE
119
119
  - README.md
120
120
  - Rakefile
121
+ - app/controllers/concerns/motor/current_user_method.rb
121
122
  - app/controllers/concerns/motor/load_and_authorize_dynamic_resource.rb
122
123
  - app/controllers/concerns/motor/wrap_io_params.rb
123
124
  - app/controllers/motor/active_storage_attachments_controller.rb
@@ -1480,8 +1481,8 @@ files:
1480
1481
  - ui/dist/icons/zoom-money.svg.gz
1481
1482
  - ui/dist/icons/zoom-out.svg.gz
1482
1483
  - ui/dist/icons/zoom-question.svg.gz
1483
- - ui/dist/main-5cac10cadc5aa16e9b69.css.gz
1484
- - ui/dist/main-5cac10cadc5aa16e9b69.js.gz
1484
+ - ui/dist/main-36a9ee6a9b0427a77d87.css.gz
1485
+ - ui/dist/main-36a9ee6a9b0427a77d87.js.gz
1485
1486
  - ui/dist/manifest.json
1486
1487
  homepage:
1487
1488
  licenses: