specify_cli 0.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (106) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +4 -0
  3. data/.rspec +1 -0
  4. data/Gemfile +17 -0
  5. data/Gemfile.lock +117 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.rdoc +43 -0
  8. data/Rakefile +15 -0
  9. data/bin/specify_cli +248 -0
  10. data/lib/specify.rb +45 -0
  11. data/lib/specify/branch_parser.rb +85 -0
  12. data/lib/specify/cli.rb +11 -0
  13. data/lib/specify/cli/database_setup.rb +46 -0
  14. data/lib/specify/cli/stubs.rb +63 -0
  15. data/lib/specify/cli/viewset.rb +21 -0
  16. data/lib/specify/configuration.rb +12 -0
  17. data/lib/specify/configuration/config.rb +120 -0
  18. data/lib/specify/configuration/db_config.rb +162 -0
  19. data/lib/specify/configuration/host_config.rb +37 -0
  20. data/lib/specify/database.rb +140 -0
  21. data/lib/specify/models.rb +43 -0
  22. data/lib/specify/models/accession.rb +33 -0
  23. data/lib/specify/models/agent.rb +138 -0
  24. data/lib/specify/models/app_resource_data.rb +32 -0
  25. data/lib/specify/models/app_resource_dir.rb +43 -0
  26. data/lib/specify/models/auto_numbering_scheme.rb +94 -0
  27. data/lib/specify/models/collecting_event.rb +38 -0
  28. data/lib/specify/models/collection.rb +67 -0
  29. data/lib/specify/models/collection_object.rb +127 -0
  30. data/lib/specify/models/createable.rb +21 -0
  31. data/lib/specify/models/determination.rb +63 -0
  32. data/lib/specify/models/discipline.rb +61 -0
  33. data/lib/specify/models/division.rb +26 -0
  34. data/lib/specify/models/geography.rb +5 -0
  35. data/lib/specify/models/geography/administrative_division.rb +32 -0
  36. data/lib/specify/models/geography/geographic_name.rb +66 -0
  37. data/lib/specify/models/geography/geography.rb +23 -0
  38. data/lib/specify/models/institution.rb +13 -0
  39. data/lib/specify/models/locality.rb +50 -0
  40. data/lib/specify/models/preparation.rb +53 -0
  41. data/lib/specify/models/preparation_type.rb +30 -0
  42. data/lib/specify/models/record_set.rb +55 -0
  43. data/lib/specify/models/record_set_item.rb +29 -0
  44. data/lib/specify/models/taxonomy.rb +6 -0
  45. data/lib/specify/models/taxonomy/common_name.rb +14 -0
  46. data/lib/specify/models/taxonomy/rank.rb +31 -0
  47. data/lib/specify/models/taxonomy/taxon.rb +54 -0
  48. data/lib/specify/models/taxonomy/taxonomy.rb +21 -0
  49. data/lib/specify/models/tree_queryable.rb +55 -0
  50. data/lib/specify/models/updateable.rb +20 -0
  51. data/lib/specify/models/user.rb +104 -0
  52. data/lib/specify/models/view_set_object.rb +32 -0
  53. data/lib/specify/number_format.rb +60 -0
  54. data/lib/specify/services.rb +18 -0
  55. data/lib/specify/services/service.rb +51 -0
  56. data/lib/specify/services/stub_generator.rb +291 -0
  57. data/lib/specify/services/view_loader.rb +177 -0
  58. data/lib/specify/session.rb +77 -0
  59. data/lib/specify/user_type.rb +61 -0
  60. data/lib/specify/version.rb +19 -0
  61. data/man/specify_cli-database.1 +60 -0
  62. data/man/specify_cli-database.1.html +137 -0
  63. data/man/specify_cli-database.1.ronn +53 -0
  64. data/man/specify_cli-repository.1 +55 -0
  65. data/man/specify_cli-repository.1.html +128 -0
  66. data/man/specify_cli-repository.1.ronn +42 -0
  67. data/man/specify_cli-stubs.1 +177 -0
  68. data/man/specify_cli-stubs.1.html +239 -0
  69. data/man/specify_cli-stubs.1.ronn +147 -0
  70. data/man/specify_cli-viewset.1 +92 -0
  71. data/man/specify_cli-viewset.1.html +154 -0
  72. data/man/specify_cli-viewset.1.ronn +72 -0
  73. data/man/specify_cli.1 +213 -0
  74. data/man/specify_cli.1.html +252 -0
  75. data/man/specify_cli.1.ronn +157 -0
  76. data/spec/branch_parser_spec.rb +94 -0
  77. data/spec/cli/stubs_spec.rb +44 -0
  78. data/spec/configuration/config_spec.rb +269 -0
  79. data/spec/configuration/db_config_spec.rb +299 -0
  80. data/spec/configuration/host_config_spec.rb +64 -0
  81. data/spec/database_spec.rb +83 -0
  82. data/spec/examples.txt +217 -0
  83. data/spec/helpers.rb +15 -0
  84. data/spec/models/app_resource_data_spec.rb +38 -0
  85. data/spec/models/app_resource_dir_spec.rb +8 -0
  86. data/spec/models/auto_numbering_scheme_spec.rb +78 -0
  87. data/spec/models/collection_object_spec.rb +92 -0
  88. data/spec/models/collection_spec.rb +32 -0
  89. data/spec/models/discipline_spec.rb +31 -0
  90. data/spec/models/record_set_spec.rb +18 -0
  91. data/spec/models/user_spec.rb +182 -0
  92. data/spec/models/view_set_object_spec.rb +70 -0
  93. data/spec/number_format_spec.rb +43 -0
  94. data/spec/services/stub_generator_spec.rb +635 -0
  95. data/spec/services/view_loader_spec.rb +436 -0
  96. data/spec/session_spec.rb +105 -0
  97. data/spec/spec_helper.rb +116 -0
  98. data/spec/support/db.yml +12 -0
  99. data/spec/support/stub.yaml +17 -0
  100. data/spec/support/stub_locality.yaml +19 -0
  101. data/spec/support/viewsets/paleo.views.xml +30 -0
  102. data/spec/support/viewsets/paleo.xml +30 -0
  103. data/spec/user_type_spec.rb +79 -0
  104. data/specify_cli.gemspec +27 -0
  105. data/specify_cli.rdoc +1 -0
  106. metadata +246 -0
data/lib/specify.rb ADDED
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ # require 'specify/version.rb'
4
+
5
+ require 'date'
6
+ require 'fileutils'
7
+ require 'io/console'
8
+ require 'mysql2'
9
+ require 'open3'
10
+ require 'pathname'
11
+ require 'psych'
12
+ require 'readline'
13
+ require 'securerandom'
14
+ require 'sequel'
15
+
16
+ require_relative 'specify/branch_parser'
17
+ require_relative 'specify/cli'
18
+ require_relative 'specify/configuration'
19
+ require_relative 'specify/database'
20
+ require_relative 'specify/number_format'
21
+ require_relative 'specify/session'
22
+ require_relative 'specify/user_type'
23
+ require_relative 'specify/services'
24
+
25
+ # FIXME: causes warnings, but is also required for call from bash script
26
+ # raises name error for VERSION and DESCRIPTION constants otherwise
27
+ require_relative 'specify/version'
28
+
29
+ # A module that provides functionaliy to manage Specify app resources.
30
+ module Specify
31
+ GIT_CURRENT_BRANCH = 'git rev-parse --abbrev-ref HEAD'
32
+
33
+ BRANCH_ERROR = 'Branch name not parsable: '
34
+
35
+ # FileError is a module that contains errors for file operations.
36
+ module FileError
37
+ VIEWS_FILE = 'Files must be .views.xml files'
38
+ NO_FILE = "File not found"
39
+ end
40
+
41
+ # LoginError is a module that contains errors for User logins.
42
+ module LoginError
43
+ INCONSISTENT_LOGIN = 'User is already logged in to a different collection'
44
+ end
45
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Specify
4
+ # BranchParsers parse the information required to set a
5
+ # Specify::Service::ViewLoade#target for upload of _.views.xml_ files from a
6
+ # string that follows the convention <tt>Database/CollectionName/level</tt>.
7
+ #
8
+ # This can be the name of a git branch of a repository residing in a folder
9
+ # denoting the hostname.
10
+ class BranchParser
11
+ # The name of the collection. Must be an existing
12
+ # Specify::Model::Collection#name.
13
+ attr_reader :collection
14
+
15
+ # A Specify::Configuration::HostConfig.
16
+ attr_reader :config
17
+
18
+ # The name of a _Specify_ database.
19
+ attr_reader :database
20
+
21
+ # The name of a MySQL/MariaDB host.
22
+ attr_reader :host
23
+
24
+ # The name of a _Specify_ user (an existing Specify::Model::User#name).
25
+ attr_reader :user
26
+
27
+ # Creates a new instance of BranchParser for the current Git _HEAD_.
28
+ #
29
+ # +config+: a database configuration YAML file.
30
+ def self.current_branch(config)
31
+ stdout_str, stderr_str, status = Open3.capture3(GIT_CURRENT_BRANCH)
32
+ unless status.exitstatus.zero?
33
+ STDERR.puts "There was an error running #{GIT_CURRENT_BRANCH}"
34
+ STDERR.puts stderr_str
35
+ exit 1
36
+ end
37
+ branch = stdout_str.chomp
38
+ new(Dir.pwd, branch, config)
39
+ end
40
+
41
+ # Returns a new BranchParser with +view_file_path+ and +name+.
42
+ #
43
+ # +view_file_path+: the directory path of the _.vews.xml_ file (that path
44
+ # must be mapped to a host name in the +config+).
45
+ #
46
+ # +name+: a String with a branch name conforming to the convention
47
+ # <tt>Database/CollectionName/level</tt>.
48
+ #
49
+ # +config+: a database configuration YAML file.
50
+ def initialize(view_file_path, name, config = nil)
51
+ @config = Configuration::HostConfig.new(config)
52
+ @database, collection, @level, @user = *name.split('/')
53
+ raise ArgumentError, BRANCH_ERROR + name unless collection && level
54
+ @host = @config.resolve_host view_file_path
55
+ @collection = normalize_name collection
56
+ end
57
+
58
+ # Returns the attributes of +self+ as a hash.
59
+ def to_h
60
+ { host: host,
61
+ database: database,
62
+ collection: collection,
63
+ level: level }
64
+ end
65
+
66
+ # Returns the level to a Specify::Service::ViewLoader will upload.
67
+ def level
68
+ case @level
69
+ when 'collection', 'discipline'
70
+ @level.to_sym
71
+ when 'user'
72
+ { user: @user }
73
+ else
74
+ { user_type: @level.downcase.to_sym }
75
+ end
76
+ end
77
+
78
+ private
79
+
80
+ def normalize_name(name)
81
+ name.gsub(/([A-Z]+)([A-Z][a-z])/, '\1 \2')
82
+ .gsub(/([a-z\d])([A-Z])/, '\1 \2')
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'cli/database_setup'
4
+ require_relative 'cli/stubs'
5
+ require_relative 'cli/viewset'
6
+
7
+ module Specify
8
+ # The CLI module contains methods used by the command line interface.
9
+ module CLI
10
+ end
11
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Specify
4
+ module CLI
5
+ # Asks the user to configure a database.
6
+ def self.configure_database(config)
7
+ STDERR.puts "Configuring new database: #{config.database}"
8
+ config.user_name = require_input 'MySQL user name'
9
+ config.user_password = require_input 'password (blank for prompt)'
10
+ config.session_user = require_input 'Specify user (leave blank to skip)'
11
+ config.save
12
+ end
13
+
14
+ # Asks the user to configure a host.
15
+ def self.configure_host(config)
16
+ return unless proceed? "host #{config.host} not known"
17
+ config.port = require_input 'port number (leave blank for default)'
18
+ config.save
19
+ end
20
+
21
+ # Creates a new database configuratin YAML file.
22
+ def self.db_config!(file, global_options)
23
+ return if File.exist?(global_options[:db_config])
24
+ STDERR.puts "Creating new config file #{file}"
25
+ Specify::Configuration::Config.empty file do |config|
26
+ config.add_host global_options[:host], global_options[:port]
27
+ end
28
+ end
29
+
30
+ # Asks the user to proceed. Returns +true+ if the user answers answers with
31
+ # 'Yes'.
32
+ def self.proceed?(message)
33
+ STDERR.puts message
34
+ STDERR.print "Configure? (Y/n)"
35
+ return true if /^[Yy](es)?/.match Readline.readline(': ')
36
+ end
37
+
38
+ # Prompts the user for input with +message+.
39
+ def self.require_input(message)
40
+ STDERR.print message
41
+ answer = Readline.readline(': ')
42
+ return if answer.empty?
43
+ answer
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Specify
4
+ module CLI
5
+ # Transforms +arg+ (a String passed as a command line argument) containing
6
+ # hierarchical information (such as geographic or taxonomic) into a
7
+ # structured Hash that can be used to set the
8
+ # Specify::Service::StubGenerator#collecting_data= or
9
+ # Specify::Service::StubGenerator#determination=.
10
+ def self.arg_to_hash(arg)
11
+ return unless arg
12
+ arg.split(';')
13
+ .map { |pair| pair.split(':').map(&:strip) }
14
+ .to_h
15
+ .transform_keys { |key| key == 'locality' ? key.to_sym : key }
16
+ end
17
+
18
+ # Creates stub records with +generator+ (a Specify::Service::StubGenerator).
19
+ #
20
+ # +count+: the number of stub records to be created.
21
+ def self.make_stubs(generator, count)
22
+ STDERR.puts "started creating #{count} records"
23
+ STDERR.puts "cataloger: #{generator.cataloger}"
24
+ generator.database.transaction do
25
+ generator.create count
26
+ STDERR.puts "creating: #{generator.generated.last.catalog_number}"
27
+ end
28
+ STDERR.puts 'done'
29
+ puts "generated #{generator.generated.count} catalog numbers:"
30
+ puts '--------------------------'
31
+ generator.generated.each { |co| puts co.CatalogNumber }
32
+ end
33
+
34
+ # Returns a Hash for intialization of a Specify::Service::StubGenerator
35
+ # from +global_options+, +args+, and command +options+.
36
+ def self.wrap_args(global_options, args, options)
37
+ params = {}
38
+ stub_generator = {}
39
+ stub_generator[:host] = global_options[:host]
40
+ stub_generator[:database] = global_options[:database]
41
+ stub_generator[:collection] = args.shift
42
+ stub_generator[:specify_user] = global_options[:specify_user]
43
+ stub_generator[:config] = global_options[:db_config]
44
+ params[:stub_generator] = stub_generator
45
+ params.merge stub_parameters(options)
46
+ end
47
+
48
+ # Parses the parameters for stub records to created by a
49
+ # Specify::Service::StubGenerator from the command +options+.
50
+ def self.stub_parameters(options)
51
+ params = { 'dataset_name' => options[:dataset],
52
+ 'cataloger' => options[:cataloger],
53
+ 'accession' => options[:accession],
54
+ 'collecting_data' => arg_to_hash(options[:geography]),
55
+ 'default_locality_name' => options[:locality],
56
+ 'determination' => arg_to_hash(options[:taxon]) }
57
+ return params unless options[:preptype]
58
+ params['preparation'] = { type: options[:preptype],
59
+ count: options[:prepcount] }
60
+ params.compact
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Specify
4
+ module CLI
5
+ # Parses the _level_ for the Specify::Service::ViewLoader to upload
6
+ # _.vioews.xml_ files to from the command +options+.
7
+ def self.level(options)
8
+ if options[:d]
9
+ :discipline
10
+ elsif options[:c]
11
+ :collection
12
+ elsif options[:t]
13
+ { user_type: options[:t] }
14
+ elsif options[:u]
15
+ { user: options[:u] }
16
+ else
17
+ raise 'level required (use -d, -c, -t, or -u option)'
18
+ end
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'configuration/config'
4
+ require_relative 'configuration/db_config'
5
+ require_relative 'configuration/host_config'
6
+
7
+ module Specify
8
+ # Configuration is a module that contains classes that provide configuration
9
+ # facilities.
10
+ module Configuration
11
+ end
12
+ end
@@ -0,0 +1,120 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Specify
4
+ module Configuration
5
+ # Configurations wrap a database configuaratin file (_.rc.yaml_ file).
6
+ #
7
+ # Configuration is the superclass of the DBConfig and HostConfig classes.
8
+ class Config
9
+ # A Hash containing the directory-host-mapping parameters for the
10
+ # HostConfig subclass.
11
+ attr_reader :dir_names
12
+
13
+ # A Hash containing the database parameters for the DBConfig subclass.
14
+ attr_reader :hosts
15
+
16
+ # Returns a new empty Config for +file+ that can serve as a template.
17
+ #
18
+ # +file+: the YAML file containg the configuration
19
+ def self.empty(file, &block)
20
+ if File.exist?(file)
21
+ raise "#{file} exists, won't overwrite"
22
+ end
23
+ config = new file, dir_names: {}, hosts: {}, &block
24
+ config.save
25
+ config
26
+ end
27
+
28
+ # Returns a new Config for +file+ (a YAML file containg the
29
+ # configuration).
30
+ #
31
+ # <tt>dir_names</tt>: a Hash with directory names as keys, host names as
32
+ # values.
33
+ #
34
+ # +hosts+: a Hash with host configurations. The hash should have the
35
+ # structure:
36
+ # {
37
+ # :hosts => {
38
+ # 'hostname' => {
39
+ # :port => Integer,
40
+ # :databases => {
41
+ # 'database name' => {
42
+ # :db_user => {
43
+ # :name => 'mysql_user_name',
44
+ # :password => 'password'
45
+ # },
46
+ # :sp_user => 'specify_user_name'
47
+ # }
48
+ # }
49
+ # }
50
+ # }
51
+ # }
52
+ # Leave +:password+ out to be prompted.
53
+ def initialize(file = nil, dir_names: nil, hosts: nil)
54
+ @file = Pathname.new(file)
55
+ if dir_names || hosts
56
+ @dir_names = dir_names
57
+ @hosts = hosts
58
+ @params = { dir_names: dir_names, hosts: hosts }
59
+ else
60
+ @params = Psych.load_file(@file)
61
+ @dir_names = @params[:dir_names]
62
+ @hosts = @params[:hosts]
63
+ end
64
+ yield(self) if block_given?
65
+ @saved = nil
66
+ end
67
+
68
+ # Adds a configuration for the database with +name+ to the +host+
69
+ # configuration.
70
+ def add_database(name, host:)
71
+ add_host(host) unless hosts[host]
72
+ if hosts.dig host, :databases, name
73
+ raise "Database '#{name}' on '#{host}' already configured"
74
+ end
75
+ db = hosts[host][:databases][name] = db_template
76
+ yield(db) if block_given?
77
+ @saved = false
78
+ end
79
+
80
+ # Adds a configuration for the host with +name+.
81
+ def add_host(name, port = nil)
82
+ raise "Host '#{name}' already configured" if hosts[name]
83
+ hosts[name] = { port: port, databases: {} }
84
+ @saved = false
85
+ end
86
+
87
+ # Returns a Hash with the contents of the configuration YAML file.
88
+ def params
89
+ { dir_names: @dir_names, hosts: @hosts }
90
+ end
91
+
92
+ # Saves the current state to the YAML configuration file.
93
+ def save
94
+ File.open(@file, 'w') do |file|
95
+ file.write(Psych.dump(@params))
96
+ end
97
+ @saved = true
98
+ end
99
+
100
+ # Returns +false+ if the instance has been modified since the last save.
101
+ def saved?
102
+ @saved
103
+ end
104
+
105
+ # Marks the instance as modified.
106
+ def touch
107
+ @saved = false
108
+ end
109
+
110
+ private
111
+
112
+ def db_template
113
+ {
114
+ db_user: { name: nil, password: nil },
115
+ sp_user: nil
116
+ }
117
+ end
118
+ end
119
+ end
120
+ end
@@ -0,0 +1,162 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Specify
4
+ module Configuration
5
+ # DBConfigs are Specify:Database configurations.
6
+ class DBConfig < Config
7
+ # The name of the _Specify_ database.
8
+ attr_reader :database
9
+
10
+ # The name of the MySQL/MariaDB host for the _Specify_ database.
11
+ attr_reader :host
12
+
13
+ # The port for the MySQL/MariaDB server for the _Specify_ database.
14
+ attr_reader :port
15
+
16
+ # The MySQL/MariaDB user for the database. This is typically the _Specify_
17
+ # <em>master user</em>.
18
+ attr_reader :user_name
19
+
20
+ # An existing Specify::Model::User#name; the name of the
21
+ # Specify::Model::User that is logged in to #collection during a Session.
22
+ attr_reader :session_user
23
+
24
+ # Returns a new DBConfig for +database+ on +host+
25
+ #
26
+ # _file_: the YAML file (path) containg the configuration.
27
+ def initialize(host, database, file = nil)
28
+ super(file)
29
+ @host = host
30
+ @database = database
31
+ @port = hosts.dig @host, :port
32
+ @user_name = params&.dig :db_user, :name
33
+ @user_password = params&.dig :db_user, :password
34
+ @session_user = params&.fetch :sp_user, nil
35
+ @saved = known? ? true : false
36
+ end
37
+
38
+ # Returns the connection paramaters for the database as a Hash.
39
+ def connection
40
+ raise "#{database} on #{host} not configured" unless known?
41
+ { host: host,
42
+ port: port || 3306,
43
+ user: user_name,
44
+ password: @user_password }
45
+ end
46
+
47
+ # Returns +true+ if #user_name differs from the user +:name+ in the
48
+ # #params of the YAML file.
49
+ def changed_user?
50
+ params[:db_user][:name] != user_name
51
+ end
52
+
53
+ # Returns +true+ if the <em>user_password</em> attribute differs from the
54
+ # +:password+ in the #params of the YAML file.
55
+ def changed_password?
56
+ params[:db_user][:password] != @user_password
57
+ end
58
+
59
+ # Returns +true+ if #port differs from the +:port+ in the #params of the
60
+ # YAML file.
61
+ def changed_port?
62
+ hosts[host][:port] != port
63
+ end
64
+
65
+ # Returns +true+ if the #session_user differs from the <tt>:so_user</tt>
66
+ # in the #params of the YAML file.
67
+ def changed_session_user?
68
+ params[:sp_user] != session_user
69
+ end
70
+
71
+ # Sets the #database.
72
+ def database=(name)
73
+ @database = name
74
+ touch
75
+ end
76
+
77
+ # Returns a Hash with the MySQL/MariaDB user name and password.
78
+ def db_user
79
+ { name: @user_name, password: @user_password }
80
+ end
81
+
82
+ # Sets the #host
83
+ def host=(name)
84
+ @host = name
85
+ touch
86
+ end
87
+
88
+ # Returns +true+ if #host is known (has been configured), +false+
89
+ # otherwise.
90
+ def host?
91
+ hosts[host]
92
+ end
93
+
94
+ # Returns +true+ if #database is known (has been configured), +false+
95
+ # otherwise.
96
+ def known?
97
+ params ? true : false
98
+ end
99
+
100
+ # Returns a Hash with the parameters for the #host #database from the
101
+ # configuration YAML file.
102
+ def params
103
+ super.dig:hosts, @host, :databases, @database
104
+ end
105
+
106
+ # Sets the #port
107
+ def port=(number)
108
+ @port = number&.to_i
109
+ raise ArgumentError, "invalid port number: #{number}" unless port_valid?
110
+ touch
111
+ end
112
+
113
+ # Saves the current state to the YAML file.
114
+ def save
115
+ return true if saved?
116
+ host? ? update_host : save_new_host
117
+ super
118
+ end
119
+
120
+ # Sets #session_user.
121
+ def session_user=(name)
122
+ @session_user = name
123
+ touch
124
+ end
125
+
126
+ # Sets the #user_name.
127
+ def user_name=(name)
128
+ @user_name = name
129
+ touch
130
+ end
131
+
132
+ # Sets the MySQL/MariaDB <em>user_password</em>.
133
+ def user_password=(password)
134
+ @user_password = password
135
+ touch
136
+ end
137
+
138
+ private
139
+
140
+ def save_new_host
141
+ add_host host, port
142
+ add_database database, host: host
143
+ end
144
+
145
+ def update_database
146
+ params[:db_user][:name] = user_name if changed_user?
147
+ params[:db_user][:password] = @user_password if changed_password?
148
+ params[:sp_user] = session_user if changed_session_user?
149
+ end
150
+
151
+ def update_host
152
+ hosts[host][:port] = port if changed_port?
153
+ add_database database, host: host unless known?
154
+ update_database
155
+ end
156
+
157
+ def port_valid?
158
+ port.nil? || port.to_i.positive?
159
+ end
160
+ end
161
+ end
162
+ end