motor-admin 0.1.13 → 0.1.14

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: a9dca132c3a2481191219a518fe66e581988245cbe3212fb105c220285d1a5fa
4
- data.tar.gz: e2cb07a305545d2fb01e0c94101f6bdabd705628b8043b8a8a40b320427bc7e9
3
+ metadata.gz: 690872e084d43d0b2759f48a2d33b7399470b6dec88678248982daf0b0ca2e8a
4
+ data.tar.gz: 3949cd11e7773c19dd03c77dd1b52477ed282e3e40e9e688bf9018e7c93c2b13
5
5
  SHA512:
6
- metadata.gz: dcce9c0ad1fff7e751a91ab366174174421f038ee3ed848e32618d663d1955221295e0cc79904a0702b939630ab4e6b6873b2b141e01b6ac4bc8361233ef265f
7
- data.tar.gz: b20916892df27d1a6c5eef23452824034d0559e5954fe44726034269496409358a3755f3b53dcd489d61953743b511e66f4f93c4bea2ea894f6dae856575492b
6
+ metadata.gz: 244c24b44ab251e6a71fe9e4200062b8d6c3679effb03c902759f3ef389d87cf87f8cf7fcf5c48c15a0c4358fc0ee750964b03c17eccd2f3af7cba19a0282ba8
7
+ data.tar.gz: 9225e1067d918fccd2dbef1c661e5128299579bd05fe4b52738bcb1ca2cc90c7495cbd07a556661000654ff0e4f6fd3e76d8049d44e3748f759e39566f89ffeb
@@ -8,6 +8,7 @@ module Motor
8
8
 
9
9
  before_action :load_and_authorize_resource
10
10
  before_action :load_and_authorize_association
11
+ before_action :wrap_io_params
11
12
 
12
13
  def index
13
14
  @resources = Motor::ApiQuery.call(@resources, params)
@@ -98,7 +99,23 @@ module Motor
98
99
  end
99
100
 
100
101
  def resource_params
101
- params.require(:data).except(resource_class.primary_key).permit!
102
+ if params[:data].present?
103
+ params.require(:data).except(resource_class.primary_key).permit!
104
+ else
105
+ {}
106
+ end
107
+ end
108
+
109
+ def wrap_io_params(hash = params)
110
+ hash.each do |key, value|
111
+ if key == 'io'
112
+ hash[key] = StringIO.new(value.encode('ISO-8859-1'))
113
+ elsif value.is_a?(ActionController::Parameters)
114
+ wrap_io_params(value)
115
+ end
116
+ end
117
+
118
+ hash
102
119
  end
103
120
  end
104
121
  end
data/lib/motor/admin.rb CHANGED
@@ -8,10 +8,9 @@ module Motor
8
8
 
9
9
  initializer 'motor.alerts.scheduler' do
10
10
  config.after_initialize do |_app|
11
- next if defined?(Sidekiq) && Sidekiq.server?
11
+ next unless defined?(Rails::Server)
12
12
 
13
13
  Motor::Alerts::Scheduler::SCHEDULER_TASK.execute
14
- Motor::Alerts::ScheduledAlertsCache::UPDATE_ALERTS_TASK.execute
15
14
  end
16
15
  end
17
16
 
@@ -3,10 +3,6 @@
3
3
  module Motor
4
4
  module Alerts
5
5
  module ScheduledAlertsCache
6
- UPDATE_ALERTS_TASK = Concurrent::TimerTask.new(
7
- execution_interval: 2.minutes
8
- ) { Motor::Alerts::ScheduledAlertsCache.load_alerts }
9
-
10
6
  CACHE_STORE = ActiveSupport::Cache::MemoryStore.new(size: 5.megabytes)
11
7
 
12
8
  module_function
@@ -49,7 +49,8 @@ module Motor
49
49
 
50
50
  params[:fields].each do |key, fields|
51
51
  fields = fields.split(',') if fields.is_a?(String)
52
- fields_hash = build_fields_hash(model, fields)
52
+ reflection_class = model.reflections[key]&.klass
53
+ fields_hash = build_fields_hash(reflection_class || model, fields)
53
54
 
54
55
  if key == model_name || model_name.split('/').last == key
55
56
  json_params.merge!(fields_hash)
@@ -68,7 +69,7 @@ module Motor
68
69
  fields.each_with_object(fields_hash) do |field, acc|
69
70
  if field.in?(columns)
70
71
  acc['only'] << field
71
- else
72
+ elsif model.instance_methods.include?(field.to_sym)
72
73
  acc['methods'] << field
73
74
  end
74
75
  end
@@ -7,19 +7,39 @@ module Motor
7
7
 
8
8
  module_function
9
9
 
10
- def call(rel, params)
11
- return rel if params.blank?
10
+ def call(rel, param)
11
+ return rel if param.blank?
12
12
 
13
- normalized_params = build_params(params)
13
+ arel_order = build_arel_order(rel.klass, param)
14
+ join_params = build_join_params(rel.klass, param)
14
15
 
15
- rel.order(normalized_params)
16
+ rel.order(arel_order).left_joins(join_params)
16
17
  end
17
18
 
18
- def build_params(param)
19
- param.split(',').each_with_object({}) do |field, hash|
20
- direction, name = field.match(FIELD_PARSE_REGEXP).captures
19
+ def build_join_params(_model, param)
20
+ param.split(',').each_with_object({}) do |field, result|
21
+ key = field[FIELD_PARSE_REGEXP, 2]
22
+ *path, _ = key.split('.')
21
23
 
22
- hash[name] = direction.present? ? :desc : :asc
24
+ path.reduce(result) do |acc, fragment|
25
+ acc[fragment] = {}
26
+ end
27
+ end
28
+ end
29
+
30
+ def build_arel_order(model, param)
31
+ param.split(',').map do |field|
32
+ direction, key = field.match(FIELD_PARSE_REGEXP).captures
33
+ *path, field = key.split('.')
34
+
35
+ reflection_model =
36
+ path.reduce(model) do |acc, fragment|
37
+ acc.reflections[fragment].klass
38
+ end
39
+
40
+ arel_column = reflection_model.arel_table[field]
41
+
42
+ direction.present? ? arel_column.desc : arel_column.asc
23
43
  end
24
44
  end
25
45
  end
@@ -26,7 +26,7 @@ module Motor
26
26
  access_type: 'read_only',
27
27
  default_value: nil,
28
28
  validators: [],
29
- virtual: false
29
+ virtual: true
30
30
  },
31
31
  {
32
32
  name: 'name',
@@ -76,22 +76,80 @@ module Motor
76
76
  def fetch_columns(model)
77
77
  default_attrs = model.new.attributes
78
78
 
79
- model.columns.map do |column|
79
+ reference_columns = fetch_reference_columns(model)
80
+
81
+ table_columns =
82
+ model.columns.map do |column|
83
+ next if reference_columns.find { |c| c[:name] == column.name }
84
+
85
+ {
86
+ name: column.name,
87
+ display_name: column.name.humanize,
88
+ column_type: ActiveRecordUtils::Types::UNIFIED_TYPES[column.type.to_s] || column.type.to_s,
89
+ access_type: COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE),
90
+ default_value: default_attrs[column.name],
91
+ validators: fetch_validators(model, column.name),
92
+ reference: nil,
93
+ virtual: false
94
+ }
95
+ end.compact
96
+
97
+ reference_columns + table_columns
98
+ end
99
+
100
+ def build_column(model, column)
101
+ {
102
+ name: column.name,
103
+ display_name: column.name.humanize,
104
+ column_type: ActiveRecordUtils::Types::UNIFIED_TYPES[column.type.to_s] || column.type.to_s,
105
+ access_type: COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE),
106
+ default_value: default_attrs[column.name],
107
+ validators: fetch_validators(model, column.name),
108
+ reference: fetch_reference(model, column.name),
109
+ virtual: false
110
+ }
111
+ end
112
+
113
+ def fetch_reference_columns(model)
114
+ default_attrs = model.new.attributes
115
+
116
+ model.reflections.map do |name, ref|
117
+ next if !ref.has_one? && !ref.belongs_to?
118
+
119
+ begin
120
+ ref.klass
121
+ rescue StandardError
122
+ next
123
+ end
124
+
125
+ column_name = ref.belongs_to? ? ref.foreign_key : name
126
+
127
+ next if ref.klass.name == 'ActiveStorage::Blob'
128
+
129
+ is_attachment = ref.klass.name == 'ActiveStorage::Attachment'
130
+
80
131
  {
81
- name: column.name,
82
- display_name: column.name.humanize,
83
- column_type: ActiveRecordUtils::Types::UNIFIED_TYPES[column.type.to_s] || column.type.to_s,
84
- access_type: COLUMN_NAME_ACCESS_TYPES.fetch(column.name, ColumnAccessTypes::READ_WRITE),
85
- default_value: default_attrs[column.name],
86
- validators: fetch_validators(model, column.name),
132
+ name: column_name,
133
+ display_name: column_name.humanize,
134
+ column_type: is_attachment ? 'file' : 'integer',
135
+ access_type: ref.belongs_to? || is_attachment ? ColumnAccessTypes::READ_WRITE : ColumnAccessTypes::READ_ONLY,
136
+ default_value: default_attrs[column_name],
137
+ validators: fetch_validators(model, column_name),
138
+ reference: {
139
+ name: name,
140
+ model_name: ref.klass.name.underscore,
141
+ reference_type: ref.belongs_to? ? 'belongs_to' : 'has_one',
142
+ foreign_key: ref.foreign_key,
143
+ polymorphic: ref.polymorphic? || is_attachment
144
+ },
87
145
  virtual: false
88
146
  }
89
- end
147
+ end.compact
90
148
  end
91
149
 
92
150
  def fetch_associations(model)
93
151
  model.reflections.map do |name, ref|
94
- next if ref.polymorphic? && !ref.belongs_to?
152
+ next if ref.has_one? || ref.belongs_to?
95
153
 
96
154
  begin
97
155
  ref.klass
@@ -108,8 +166,6 @@ module Motor
108
166
  display_name: name.humanize,
109
167
  slug: name.underscore,
110
168
  model_name: model_class.name.underscore,
111
- model_slug: Utils.slugify(model_class),
112
- association_type: fetch_association_type(ref),
113
169
  foreign_key: ref.foreign_key,
114
170
  polymorphic: ref.polymorphic? || model_class.name == 'ActiveStorage::Attachment',
115
171
  visible: true
@@ -117,21 +173,6 @@ module Motor
117
173
  end.compact
118
174
  end
119
175
 
120
- def fetch_association_type(association)
121
- case association.association_class.to_s
122
- when 'ActiveRecord::Associations::HasManyAssociation',
123
- 'ActiveRecord::Associations::HasManyThroughAssociation'
124
- 'has_many'
125
- when 'ActiveRecord::Associations::HasOneAssociation',
126
- 'ActiveRecord::Associations::HasOneThroughAssociation'
127
- 'has_one'
128
- when 'ActiveRecord::Associations::BelongsToAssociation'
129
- 'belongs_to'
130
- else
131
- raise ArgumentError, 'Unknown association type'
132
- end
133
- end
134
-
135
176
  def fetch_validators(model, column_name)
136
177
  model.validators_on(column_name).map do |validator|
137
178
  case validator
@@ -13,6 +13,7 @@ module Motor
13
13
  COLUMN_DEFAULTS = {
14
14
  access_type: 'read_write',
15
15
  default_value: nil,
16
+ reference: nil,
16
17
  validators: []
17
18
  }.with_indifferent_access
18
19
 
@@ -2,6 +2,18 @@
2
2
 
3
3
  module Motor
4
4
  module ReorderSchema
5
+ COLUMNS_DEFAULT_ORDER_WEIGHTS = {
6
+ id: 0,
7
+ updated_at: 2,
8
+ edited_at: 2,
9
+ created_at: 3,
10
+ inserted_at: 3,
11
+ deleted_at: 4,
12
+ archived_at: 4
13
+ }.with_indifferent_access
14
+
15
+ COLUMNS_DEFAULT_ORDER_WEIGHT = 1
16
+
5
17
  module_function
6
18
 
7
19
  # @param schema [Array<HashWithIndifferentAccess>]
@@ -19,7 +31,7 @@ module Motor
19
31
  scopes_order = configs["resources.#{model[:name]}.scopes.order"]
20
32
 
21
33
  model.merge(
22
- columns: sort_by_name(model[:columns], columns_order, sort_alphabetically: false),
34
+ columns: sort_by_name(sort_columns(model[:columns]), columns_order, sort_alphabetically: false),
23
35
  associations: sort_by_name(model[:associations], associations_order),
24
36
  actions: sort_by_name(model[:actions], actions_order, sort_alphabetically: false),
25
37
  tabs: sort_by_name(model[:tabs], tabs_order, sort_alphabetically: false),
@@ -44,6 +56,14 @@ module Motor
44
56
  end
45
57
  end
46
58
 
59
+ def sort_columns(columns)
60
+ columns.each_with_object([]) do |column, acc|
61
+ weight = COLUMNS_DEFAULT_ORDER_WEIGHTS.fetch(column[:name], COLUMNS_DEFAULT_ORDER_WEIGHT)
62
+
63
+ (acc[weight] ||= []) << column
64
+ end.flatten.compact
65
+ end
66
+
47
67
  # @return [Hash<String, HashWithIndifferentAccess>]
48
68
  def load_configs
49
69
  Motor::Config.all.each_with_object({}) do |config, acc|
@@ -57,11 +57,16 @@ module Motor
57
57
  "$#{index}"
58
58
  end
59
59
 
60
- [
61
- format(WITH_STATEMENT_TEMPLATE, sql_body: sql.strip.gsub(/;\z/, ''), limit: limit),
62
- 'SQL',
63
- variables.map { |variable_name, default_value| variables_hash[variable_name] || default_value }
64
- ]
60
+ attributes =
61
+ variables.map do |variable_name, default_value|
62
+ ActiveRecord::Relation::QueryAttribute.new(
63
+ variable_name,
64
+ variables_hash[variable_name] || default_value,
65
+ ActiveRecord::Type::Value.new
66
+ )
67
+ end
68
+
69
+ [format(WITH_STATEMENT_TEMPLATE, sql_body: sql.strip.gsub(/;\z/, ''), limit: limit), 'SQL', attributes]
65
70
  end
66
71
  end
67
72
  end
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.13'
4
+ VERSION = '0.1.14'
5
5
  end
@@ -5,9 +5,9 @@
5
5
  "fonts/ionicons.ttf?v=3.0.0-alpha.3": "fonts/ionicons.ttf",
6
6
  "fonts/ionicons.woff2?v=3.0.0-alpha.3": "fonts/ionicons.woff2",
7
7
  "fonts/ionicons.woff?v=3.0.0-alpha.3": "fonts/ionicons.woff",
8
- "main-8f36a2746422efd1e8a8.css.gz": "main-8f36a2746422efd1e8a8.css.gz",
9
- "main-8f36a2746422efd1e8a8.js.LICENSE.txt": "main-8f36a2746422efd1e8a8.js.LICENSE.txt",
10
- "main-8f36a2746422efd1e8a8.js.gz": "main-8f36a2746422efd1e8a8.js.gz",
11
- "main.css": "main-8f36a2746422efd1e8a8.css",
12
- "main.js": "main-8f36a2746422efd1e8a8.js"
8
+ "main-f7c7f445c53544d2d7b8.css.gz": "main-f7c7f445c53544d2d7b8.css.gz",
9
+ "main-f7c7f445c53544d2d7b8.js.LICENSE.txt": "main-f7c7f445c53544d2d7b8.js.LICENSE.txt",
10
+ "main-f7c7f445c53544d2d7b8.js.gz": "main-f7c7f445c53544d2d7b8.js.gz",
11
+ "main.css": "main-f7c7f445c53544d2d7b8.css",
12
+ "main.js": "main-f7c7f445c53544d2d7b8.js"
13
13
  }
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.13
4
+ version: 0.1.14
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-04-29 00:00:00.000000000 Z
11
+ date: 2021-04-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord-filter
@@ -213,8 +213,8 @@ files:
213
213
  - lib/motor/ui_configs.rb
214
214
  - lib/motor/version.rb
215
215
  - ui/dist/fonts/ionicons.woff2
216
- - ui/dist/main-8f36a2746422efd1e8a8.css.gz
217
- - ui/dist/main-8f36a2746422efd1e8a8.js.gz
216
+ - ui/dist/main-f7c7f445c53544d2d7b8.css.gz
217
+ - ui/dist/main-f7c7f445c53544d2d7b8.js.gz
218
218
  - ui/dist/manifest.json
219
219
  homepage:
220
220
  licenses: