marty 9.3.0 → 9.3.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +4 -4
  2. data/.gemignore +2 -0
  3. data/.gitignore +3 -0
  4. data/.rubocop.yml +34 -1
  5. data/Gemfile +1 -0
  6. data/Rakefile +10 -0
  7. data/app/assets/javascripts/marty/cable.js +21 -9
  8. data/app/channels/application_cable/connection.rb +1 -1
  9. data/app/channels/marty/notification_channel.rb +4 -1
  10. data/app/components/marty/api_config_view.rb +1 -1
  11. data/app/components/marty/api_log_view.rb +1 -1
  12. data/app/components/marty/auth_app.rb +8 -1
  13. data/app/components/marty/auth_app/client/auth_app.js +6 -0
  14. data/app/components/marty/data_grid_view.rb +6 -6
  15. data/app/components/marty/extras/misc.rb +1 -1
  16. data/app/components/marty/log_view.rb +1 -1
  17. data/app/components/marty/posting_grid.rb +1 -1
  18. data/app/components/marty/script_form.rb +3 -3
  19. data/app/components/marty/user_view.rb +1 -1
  20. data/app/components/marty/users/user_view.rb +1 -1
  21. data/app/controllers/marty/application_controller.rb +7 -7
  22. data/app/controllers/marty/delayed_job_controller.rb +7 -4
  23. data/app/controllers/marty/diagnostic/controller.rb +1 -1
  24. data/app/controllers/marty/job_controller.rb +2 -2
  25. data/app/models/marty/api_auth.rb +3 -3
  26. data/app/models/marty/api_config.rb +1 -1
  27. data/app/models/marty/base_rule.rb +1 -1
  28. data/app/models/marty/config.rb +5 -5
  29. data/app/models/marty/data_grid.rb +3 -3
  30. data/app/models/marty/delorean_rule.rb +2 -2
  31. data/app/models/marty/grid_index_boolean.rb +2 -2
  32. data/app/models/marty/grid_index_int4range.rb +1 -1
  33. data/app/models/marty/grid_index_integer.rb +1 -1
  34. data/app/models/marty/grid_index_numrange.rb +1 -1
  35. data/app/models/marty/grid_index_string.rb +1 -1
  36. data/app/models/marty/import_type.rb +2 -2
  37. data/app/models/marty/notifications/notification.rb +2 -1
  38. data/app/models/marty/posting.rb +6 -6
  39. data/app/models/marty/posting_type.rb +2 -2
  40. data/app/models/marty/promise.rb +4 -3
  41. data/app/models/marty/script.rb +7 -7
  42. data/app/models/marty/tag.rb +5 -5
  43. data/app/models/marty/token.rb +1 -1
  44. data/app/models/marty/user.rb +11 -10
  45. data/app/models/marty/user_role.rb +2 -2
  46. data/app/models/marty/vw_promise.rb +2 -1
  47. data/app/services/marty/background_job/update_schedule.rb +1 -1
  48. data/app/services/marty/data_grid/constraint.rb +4 -3
  49. data/app/services/marty/jobs/schedule.rb +2 -2
  50. data/lib/marty/data_change.rb +1 -1
  51. data/lib/marty/data_conversion.rb +1 -1
  52. data/lib/marty/migrations.rb +2 -2
  53. data/lib/marty/rpc_call.rb +2 -1
  54. data/lib/marty/rule_script_set.rb +1 -3
  55. data/lib/marty/util.rb +2 -2
  56. data/lib/marty/version.rb +1 -1
  57. data/lib/tasks/scripts_tasks.rake +2 -2
  58. data/marty.gemspec +5 -1
  59. data/spec/controllers/job_controller_spec.rb +7 -7
  60. data/spec/controllers/rpc_controller_spec.rb +8 -8
  61. data/spec/controllers/rpc_import_spec.rb +3 -3
  62. data/spec/features/data_blame_report_spec.rb +1 -1
  63. data/spec/features/data_grid_spec.rb +101 -3
  64. data/spec/features/enum_values_report_spec.rb +1 -1
  65. data/spec/features/extjs_spec.rb +1 -1
  66. data/spec/features/jobs_dashboard_spec.rb +2 -2
  67. data/spec/features/log_view_spec.rb +1 -1
  68. data/spec/features/reporting_spec.rb +3 -3
  69. data/spec/features/scripting_spec.rb +3 -3
  70. data/spec/features/scripting_test_spec.rb +3 -3
  71. data/spec/features/user_list_report_spec.rb +1 -1
  72. data/spec/fixtures/misc/data_grid_6.txt +9 -0
  73. data/spec/fixtures/misc/data_grid_7.txt +7 -0
  74. data/spec/fixtures/misc/data_grid_8.txt +10 -0
  75. data/spec/fixtures/misc/data_grid_9.txt +10 -0
  76. data/spec/lib/data_blame_spec.rb +1 -1
  77. data/spec/lib/data_importer_spec.rb +4 -4
  78. data/spec/lib/delorean_query_spec.rb +1 -1
  79. data/spec/lib/logger_spec.rb +1 -1
  80. data/spec/lib/mcfly_model_spec.rb +2 -2
  81. data/spec/lib/table_report_spec.rb +1 -1
  82. data/spec/models/api_auth_spec.rb +2 -2
  83. data/spec/models/data_grid_spec.rb +3 -3
  84. data/spec/models/posting_spec.rb +12 -12
  85. data/spec/models/promise_spec.rb +1 -1
  86. data/spec/models/rule_spec.rb +1 -1
  87. data/spec/models/script_spec.rb +1 -1
  88. data/spec/other/diagnostic/delayed_job_version_spec.rb +1 -1
  89. data/spec/spec_helper.rb +3 -0
  90. data/spec/support/netzke.rb +2 -2
  91. data/spec/support/setup.rb +1 -1
  92. data/spec/support/simplecov_helper.rb +94 -0
  93. data/spec/support/users.rb +2 -2
  94. metadata +8 -3
  95. data/.gitlab-ci.yml +0 -117
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7fd7c45cf6825476a774ee84fe6b3f9bff603e022747ecc5079cda8a037a869b
4
- data.tar.gz: 48fdb90df6cb49e720fc4446975f8b55205eaa560a23af60a49b1d02769127cf
3
+ metadata.gz: a8acd94fdad2d0427eaea410e20f8df3acccec4ddb55d6932c8ac558a4f320cb
4
+ data.tar.gz: e6d14dc726a688d44bc575c512678522890c90b2cb848167f7dc2a2a661de034
5
5
  SHA512:
6
- metadata.gz: 81813549a4614fa1191c466a5506ec2d36796e21e6d45dec4eb8bf054e0614a01c1c2bf1a0539e2afdc980c81e22a32bcdce2749d27634bf65c6f491e058978d
7
- data.tar.gz: be097e58e928782e89761d4980218e417c2df6c5fa9a28233c4449feed3df1a5492a244ce7211cb604471e7188e08d5b8e923921948b7614dcc6a873cab60af6
6
+ metadata.gz: 03a0f36614dfde9a168825250d41a94c8c7a5912b83d2ee347fa0ed9fdaf10da1d08b28b0ff8497dccf4ef0999d76eadbfb8bd320fde1e4a6636a307297a8df7
7
+ data.tar.gz: '039559d4fd376ce6741a9fa8ea71a7276daba6df5050dcf3950f24e78cc2bb8c5944cb425e45dbc6ce4a7b6f32b5e69a0c27bd74f5dcf664271476f1da3b8771'
@@ -0,0 +1,2 @@
1
+ .gitlab-ci.yml
2
+ .gitlab
data/.gitignore CHANGED
@@ -42,3 +42,6 @@ Gemfile.lock
42
42
 
43
43
  # JS stuff
44
44
  /node_modules
45
+
46
+ # Ignore code coverage
47
+ coverage/
@@ -1,5 +1,7 @@
1
1
  inherit_from: .rubocop_todo.yml
2
- require: rubocop-performance
2
+ require:
3
+ - rubocop-performance
4
+ - rubocop-rails
3
5
 
4
6
  AllCops:
5
7
  TargetRubyVersion: 2.4.2
@@ -46,3 +48,34 @@ Style/SymbolArray:
46
48
  Security/MarshalLoad:
47
49
  Exclude:
48
50
  - 'lib/marty/cache_adapters/redis.rb'
51
+
52
+ Rails/TimeZone:
53
+ EnforcedStyle: strict
54
+
55
+ Rails/OutputSafety:
56
+ Exclude:
57
+ - 'app/components/marty/main_auth_app.rb'
58
+
59
+ Rails/HelperInstanceVariable:
60
+ Exclude:
61
+ - 'app/helpers/marty/script_set.rb' # It's a class, not a helper
62
+
63
+ Rails/SkipsModelValidations:
64
+ Exclude:
65
+ - 'app/models/marty/promise.rb'
66
+
67
+ Rails/DynamicFindBy:
68
+ Exclude:
69
+ - 'app/models/marty/data_grid.rb' # Enum
70
+ - 'app/models/marty/enum.rb' # Enum
71
+ - 'spec/features/**/*'
72
+ - 'spec/support/**/*'
73
+
74
+ Rails/ApplicationRecord:
75
+ Enabled: false
76
+
77
+ Rails/ApplicationJob:
78
+ Enabled: false
79
+
80
+ Rails/ApplicationController:
81
+ Enabled: false
data/Gemfile CHANGED
@@ -37,6 +37,7 @@ group :development, :test do
37
37
  gem 'rubocop-performance', require: false
38
38
  gem 'rubocop-rails', require: false
39
39
  gem 'selenium-webdriver'
40
+ gem 'simplecov', require: false
40
41
  gem 'timecop'
41
42
  gem 'webdrivers'
42
43
  end
data/Rakefile CHANGED
@@ -26,3 +26,13 @@ load 'rails/tasks/engine.rake'
26
26
  Bundler::GemHelper.install_tasks
27
27
 
28
28
  task default: 'app:spec'
29
+
30
+ namespace :marty do
31
+ desc 'Merge the results of various SimpleCov coverage reports'
32
+ task merge_coverage_reports: :environment do
33
+ require_relative 'spec/support/simplecov_helper'
34
+ SimpleCovHelper.configure_profile
35
+ puts 'Merging code coverage reports...'
36
+ SimpleCovHelper.merge_all_results!
37
+ end
38
+ end
@@ -4,13 +4,25 @@
4
4
  (function() {
5
5
  this.RailsApp || (this.RailsApp = {});
6
6
 
7
- if (window.location.port === "") {
8
- RailsApp.cable = ActionCable.createConsumer(
9
- `ws://${window.location.hostname}/cable`
10
- );
11
- } else {
12
- RailsApp.cable = ActionCable.createConsumer(
13
- `ws://${window.location.hostname}:${window.location.port}/cable`
14
- );
15
- }
7
+ this.RailsApp.startActionCable = () => {
8
+ // Already started
9
+ if (RailsApp.cable) {
10
+ return false;
11
+ }
12
+
13
+ const protocol =
14
+ window.location.protocol.slice(0, 5) === "https" ? "wss" : "ws";
15
+
16
+ if (window.location.port === "") {
17
+ RailsApp.cable = ActionCable.createConsumer(
18
+ `${protocol}://${window.location.hostname}/cable`
19
+ );
20
+ } else {
21
+ RailsApp.cable = ActionCable.createConsumer(
22
+ `${protocol}://${window.location.hostname}:${window.location.port}/cable`
23
+ );
24
+ }
25
+
26
+ return true;
27
+ };
16
28
  }.call(this));
@@ -9,7 +9,7 @@ module ApplicationCable
9
9
  private
10
10
 
11
11
  def find_verified_user
12
- return unless cookies.signed[:user_id].present?
12
+ return if cookies.signed[:user_id].blank?
13
13
 
14
14
  ::Marty::User.find_by(id: cookies.signed[:user_id])
15
15
  end
@@ -1,7 +1,10 @@
1
1
  module Marty
2
2
  class NotificationChannel < ::ApplicationCable::Channel
3
3
  def subscribed
4
- reject && return unless current_user.present?
4
+ reject && return unless
5
+ Rails.application.config.marty.enable_action_cable
6
+
7
+ reject && return if current_user.blank?
5
8
  stream_from "marty_notifications_#{current_user.id}"
6
9
  end
7
10
  end
@@ -67,7 +67,7 @@ class Marty::ApiConfigView < Marty::Grid
67
67
  [:node, :attr].each do |a|
68
68
  attribute a do |c|
69
69
  c.width = 150
70
- c.setter = lambda { |r, v| r.send("#{a}=", v.blank? ? nil : v) }
70
+ c.setter = lambda { |r, v| r.send("#{a}=", v.presence) }
71
71
  end
72
72
  end
73
73
 
@@ -103,7 +103,7 @@ class Marty::ApiLogView < Marty::Grid
103
103
  c.field_config = {
104
104
  xtype: :displayfield,
105
105
  }
106
- c.getter = lambda { |r| Time.at(r.timestamp) }
106
+ c.getter = lambda { |r| Time.zone.at(r.timestamp) }
107
107
  c.sorting_scope = lambda { |r, dir| r.order('timestamp ' + dir.to_s) }
108
108
 
109
109
  # FIXME?: The UI AR/PG DateTime workaround requires the timestamp to be cast
@@ -5,6 +5,13 @@
5
5
  require 'marty/notifications/window'
6
6
 
7
7
  class Marty::AuthApp < Marty::SimpleApp
8
+ def configure(c)
9
+ super
10
+
11
+ enable_action_cable = Rails.application.config.marty.enable_action_cable
12
+ client_config[:marty_enable_action_cable] = enable_action_cable
13
+ end
14
+
8
15
  client_class do |c|
9
16
  c.include :auth_app
10
17
  end
@@ -44,7 +51,7 @@ class Marty::AuthApp < Marty::SimpleApp
44
51
  def unread_notifications_count
45
52
  user = Mcfly.whodunnit
46
53
 
47
- return 0 unless user.present?
54
+ return 0 if user.blank?
48
55
 
49
56
  user.unread_web_notifications_count
50
57
  end
@@ -118,7 +118,13 @@
118
118
  },
119
119
 
120
120
  netzkeInitComponentCallback() {
121
+ if (!this.config.clientConfig.marty_enable_action_cable) {
122
+ return;
123
+ }
124
+
121
125
  try {
126
+ RailsApp.startActionCable();
127
+
122
128
  const subscription = RailsApp.cable.subscriptions.subscriptions.find(
123
129
  (sub) => sub.identifier === '{"channel":"Marty::NotificationChannel"}'
124
130
  );
@@ -116,9 +116,9 @@ module Marty; class DataGridView < McflyGridPanel
116
116
  Marty::RoleType.from_nice_names(plist)
117
117
  end
118
118
  dg.permissions = {
119
- view: view.present? ? view : [],
120
- edit_data: edit_data.present? ? edit_data : [],
121
- edit_all: edit_all.present? ? edit_all : [],
119
+ view: view.presence || [],
120
+ edit_data: edit_data.presence || [],
121
+ edit_all: edit_all.presence || [],
122
122
  }
123
123
  dg.save!
124
124
  end
@@ -142,7 +142,7 @@ module Marty; class DataGridView < McflyGridPanel
142
142
  endpoint :edit_window__edit_form__submit do |params|
143
143
  data = ActiveSupport::JSON.decode(params[:data])
144
144
 
145
- dg = DataGrid.find_by_id(data['id'])
145
+ dg = DataGrid.find_by(id: data['id'])
146
146
 
147
147
  begin
148
148
  dg.update_from_import(data['name'], data['export'])
@@ -169,7 +169,7 @@ module Marty; class DataGridView < McflyGridPanel
169
169
  endpoint :show_grid do |params|
170
170
  record_id = params[:record_id]
171
171
 
172
- dg = DataGrid.find_by_id(record_id)
172
+ dg = DataGrid.find_by(id: record_id)
173
173
 
174
174
  return client.netzke_notify('No data grid.') unless dg
175
175
 
@@ -193,7 +193,7 @@ module Marty; class DataGridView < McflyGridPanel
193
193
  endpoint :edit_grid do |params|
194
194
  record_id = params[:record_id]
195
195
 
196
- dg = DataGrid.find_by_id(record_id)
196
+ dg = DataGrid.find_by(id: record_id)
197
197
 
198
198
  return client.netzke_notify('No data grid.') unless dg
199
199
 
@@ -13,7 +13,7 @@ module Marty::Extras::Misc
13
13
  renderer: "function(v){return ('0' + v).slice (-2);}",
14
14
  # FIXME: a little bogus since this is computed statically. lambda
15
15
  # didn't work.
16
- default_value: Date.today.month
16
+ default_value: Time.zone.today.month
17
17
  }
18
18
  def self.numberfield_cfg(decimal_places)
19
19
  {
@@ -63,7 +63,7 @@ class Marty::LogView < Marty::Grid
63
63
  c.field_config = {
64
64
  xtype: :displayfield,
65
65
  }
66
- c.getter = lambda { |r| Time.at(r.timestamp) }
66
+ c.getter = lambda { |r| Time.zone.at(r.timestamp) }
67
67
  c.sorting_scope = lambda { |r, dir| r.order('timestamp ' + dir.to_s) }
68
68
 
69
69
  # FIXME?: The UI AR/PG DateTime workaround requires the timestamp to be cast
@@ -42,7 +42,7 @@ class Marty::PostingGrid < Marty::Grid
42
42
  # Prepare an HTML popup with session details such that the
43
43
  # contents can be easily pasted into a spreadsheet.
44
44
 
45
- pt = Marty::Posting.find_by_id(record_id)
45
+ pt = Marty::Posting.find_by(id: record_id)
46
46
 
47
47
  dt = pt.created_dt.to_s == 'Infinity' ? '---' :
48
48
  pt.created_dt.strftime('%Y-%m-%d %I:%M %p')
@@ -61,7 +61,7 @@ class Marty::ScriptForm < Marty::Form
61
61
  data[k] = nil if v.blank? || v == 'null'
62
62
  end
63
63
 
64
- @record = script = Marty::Script.find_by_id(data['id'])
64
+ @record = script = Marty::Script.find_by(id: data['id'])
65
65
 
66
66
  unless script
67
67
  client.netzke_notify 'no record'
@@ -81,7 +81,7 @@ class Marty::ScriptForm < Marty::Form
81
81
  end
82
82
 
83
83
  begin
84
- dev = Marty::Tag.find_by_name('DEV')
84
+ dev = Marty::Tag.find_by(name: 'DEV')
85
85
  Marty::ScriptSet.new(dev).parse_check(script.name, data['body'])
86
86
  rescue Delorean::ParseError => e
87
87
  client.netzke_notify e.message
@@ -106,7 +106,7 @@ class Marty::ScriptForm < Marty::Form
106
106
  return client.netzke_notify('Permission Denied') unless
107
107
  self.class.has_any_perm?
108
108
 
109
- script = Marty::Script.find_by_id(script_id)
109
+ script = Marty::Script.find_by(id: script_id)
110
110
 
111
111
  return client.netzke_notify('bad script') unless script
112
112
 
@@ -31,7 +31,7 @@ module Marty; class UserView < Marty::Grid
31
31
  end
32
32
 
33
33
  def self.set_roles(roles, user)
34
- roles = [] unless roles.present?
34
+ roles = [] if roles.blank?
35
35
 
36
36
  roles = ::Marty::RoleType.from_nice_names(roles)
37
37
 
@@ -34,7 +34,7 @@ module Marty
34
34
  end
35
35
 
36
36
  def self.set_roles(roles, user)
37
- roles = [] unless roles.present?
37
+ roles = [] if roles.blank?
38
38
 
39
39
  roles = ::Marty::RoleType.from_nice_names(roles)
40
40
 
@@ -24,7 +24,7 @@ class Marty::ApplicationController < ActionController::Base
24
24
  reset_session
25
25
  reset_signed_cookies
26
26
  else
27
- session[:atime] = Time.now.utc.to_i
27
+ session[:atime] = Time.zone.now.utc.to_i
28
28
  end
29
29
  end
30
30
  end
@@ -35,13 +35,13 @@ class Marty::ApplicationController < ActionController::Base
35
35
 
36
36
  if session_lifetime
37
37
  return true unless session[:ctime] &&
38
- (Time.now.utc.to_i -
38
+ (Time.zone.now.utc.to_i -
39
39
  session[:ctime].to_i <= session_lifetime.to_i * 60)
40
40
  end
41
41
 
42
42
  if session_timeout
43
43
  return true unless session[:atime] &&
44
- (Time.now.utc.to_i - session[:atime].to_i <= session_timeout.to_i * 60)
44
+ (Time.zone.now.utc.to_i - session[:atime].to_i <= session_timeout.to_i * 60)
45
45
  end
46
46
 
47
47
  false
@@ -49,8 +49,8 @@ class Marty::ApplicationController < ActionController::Base
49
49
 
50
50
  def start_user_session(user)
51
51
  session[:user_id] = user.id
52
- session[:ctime] = Time.now.utc.to_i
53
- session[:atime] = Time.now.utc.to_i
52
+ session[:ctime] = Time.zone.now.utc.to_i
53
+ session[:atime] = Time.zone.now.utc.to_i
54
54
 
55
55
  set_signed_cookies
56
56
  end
@@ -119,12 +119,12 @@ class Marty::ApplicationController < ActionController::Base
119
119
 
120
120
  def failed_authentication(login)
121
121
  logger.info("Failed authentication for '#{login}' " +
122
- "from #{request.remote_ip} at #{Time.now.utc}")
122
+ "from #{request.remote_ip} at #{Time.zone.now.utc}")
123
123
  end
124
124
 
125
125
  def successful_authentication(user)
126
126
  logger.info("Successful authentication for '#{user.login}' " +
127
- "from #{request.remote_ip} at #{Time.now.utc}")
127
+ "from #{request.remote_ip} at #{Time.zone.now.utc}")
128
128
  set_user(user)
129
129
  end
130
130
 
@@ -1,4 +1,6 @@
1
- class Marty::DelayedJobController < ActionController::Base
1
+ class Marty::DelayedJobController < ApplicationController
2
+ # FIXME: We probably don't need this endpoint anymore.
3
+ # It's not used by lambda
2
4
  def trigger
3
5
  work_off_job if delayed_job.present?
4
6
  render json: { status: :ok }, status: :ok
@@ -7,7 +9,7 @@ class Marty::DelayedJobController < ActionController::Base
7
9
  private
8
10
 
9
11
  def delayed_job
10
- return unless params['id'].present?
12
+ return if params['id'].blank?
11
13
 
12
14
  @delayed_job ||= ::Delayed::Job.find_by(id: params['id'])
13
15
  end
@@ -15,8 +17,9 @@ class Marty::DelayedJobController < ActionController::Base
15
17
  def work_off_job
16
18
  return if delayed_job.locked_at.present?
17
19
 
18
- ::Delayed::Job.where(id: delayed_job.id).
19
- update_all(locked_at: ::Delayed::Job.db_time_now, locked_by: 'Lambda')
20
+ ::Delayed::Job.find_by(id: delayed_job.id)&.update!(
21
+ locked_at: ::Delayed::Job.db_time_now, locked_by: 'Lambda'
22
+ )
20
23
 
21
24
  w = ::Delayed::Worker.new
22
25
  w.run(delayed_job)
@@ -8,7 +8,7 @@ module Marty::Diagnostic; class Controller < ActionController::Base
8
8
  def op
9
9
  @result = Reporter.run(request)
10
10
  rescue NameError
11
- render file: 'public/400', formats: [:html], status: 400, layout: false
11
+ render file: 'public/400', formats: [:html], status: :bad_request, layout: false
12
12
  else
13
13
  respond_to do |format|
14
14
  format.html { @result = display_parameters }
@@ -1,8 +1,8 @@
1
- class Marty::JobController < ActionController::Base
1
+ class Marty::JobController < ApplicationController
2
2
  def download
3
3
  job_id = params['job_id']
4
4
 
5
- promise = Marty::Promise.find_by_id(job_id)
5
+ promise = Marty::Promise.find_by(id: job_id)
6
6
 
7
7
  if promise
8
8
  format = promise.cformat
@@ -3,7 +3,7 @@ class Marty::ApiAuth < Marty::Base
3
3
 
4
4
  KEY_SIZE = 19
5
5
 
6
- validates_presence_of :app_name, :api_key, :script_name
6
+ validates :app_name, :api_key, :script_name, presence: true
7
7
 
8
8
  class ApiAuthValidator < ActiveModel::Validator
9
9
  def validate(api)
@@ -18,8 +18,8 @@ class Marty::ApiAuth < Marty::Base
18
18
  validates_with ApiAuthValidator
19
19
 
20
20
  mcfly_validates_uniqueness_of :api_key, scope: [:script_name]
21
- validates_uniqueness_of :app_name, scope: [:script_name,
22
- :obsoleted_dt]
21
+ validates :app_name, uniqueness: { scope: [:script_name,
22
+ :obsoleted_dt] }
23
23
 
24
24
  before_validation do
25
25
  self.api_key = Marty::ApiAuth.generate_key if
@@ -1,5 +1,5 @@
1
1
  class Marty::ApiConfig < Marty::Base
2
- validates_presence_of :script
2
+ validates :script, presence: true
3
3
 
4
4
  def self.lookup(script, node, attr)
5
5
  res = where('node IS NULL OR node = ?', node).