dynamic_migrations 2.1.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +23 -0
  3. data/lib/dynamic_migrations/active_record/migrators/column.rb +21 -0
  4. data/lib/dynamic_migrations/active_record/migrators/foreign_key_constraint.rb +112 -0
  5. data/lib/dynamic_migrations/active_record/migrators/function.rb +108 -0
  6. data/lib/dynamic_migrations/active_record/migrators/index.rb +27 -0
  7. data/lib/dynamic_migrations/active_record/migrators/schema.rb +21 -0
  8. data/lib/dynamic_migrations/active_record/migrators/table.rb +21 -0
  9. data/lib/dynamic_migrations/active_record/migrators/trigger.rb +109 -0
  10. data/lib/dynamic_migrations/active_record/migrators/unique_constraint.rb +63 -0
  11. data/lib/dynamic_migrations/active_record/migrators/validation.rb +67 -0
  12. data/lib/dynamic_migrations/active_record/migrators.rb +64 -0
  13. data/lib/dynamic_migrations/name_helper.rb +13 -0
  14. data/lib/dynamic_migrations/postgres/generator/column.rb +92 -0
  15. data/lib/dynamic_migrations/postgres/generator/foreign_key_constraint.rb +84 -0
  16. data/lib/dynamic_migrations/postgres/generator/fragment.rb +30 -0
  17. data/lib/dynamic_migrations/postgres/generator/function.rb +77 -0
  18. data/lib/dynamic_migrations/postgres/generator/index.rb +101 -0
  19. data/lib/dynamic_migrations/postgres/generator/primary_key.rb +55 -0
  20. data/lib/dynamic_migrations/postgres/generator/schema.rb +19 -0
  21. data/lib/dynamic_migrations/postgres/generator/schema_migrations/section.rb +37 -0
  22. data/lib/dynamic_migrations/postgres/generator/schema_migrations.rb +92 -0
  23. data/lib/dynamic_migrations/postgres/generator/table.rb +122 -0
  24. data/lib/dynamic_migrations/postgres/generator/trigger.rb +101 -0
  25. data/lib/dynamic_migrations/postgres/generator/unique_constraint.rb +79 -0
  26. data/lib/dynamic_migrations/postgres/generator/validation.rb +87 -0
  27. data/lib/dynamic_migrations/postgres/generator.rb +359 -0
  28. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/functions.rb +68 -0
  29. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/columns.rb +72 -0
  30. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/foreign_key_constraints.rb +73 -0
  31. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/indexes.rb +73 -0
  32. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/primary_key.rb +49 -0
  33. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/triggers.rb +73 -0
  34. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/unique_constraints.rb +73 -0
  35. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables/validations.rb +73 -0
  36. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas/tables.rb +80 -0
  37. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations/schemas.rb +48 -0
  38. data/lib/dynamic_migrations/postgres/server/database/differences/to_migrations.rb +59 -0
  39. data/lib/dynamic_migrations/postgres/server/database/differences.rb +76 -16
  40. data/lib/dynamic_migrations/postgres/server/database/keys_and_unique_constraints_loader.rb +35 -9
  41. data/lib/dynamic_migrations/postgres/server/database/loaded_schemas_builder.rb +50 -26
  42. data/lib/dynamic_migrations/postgres/server/database/schema/function.rb +69 -0
  43. data/lib/dynamic_migrations/postgres/server/database/schema/functions.rb +63 -0
  44. data/lib/dynamic_migrations/postgres/server/database/schema/table/column.rb +6 -44
  45. data/lib/dynamic_migrations/postgres/server/database/schema/table/columns.rb +1 -1
  46. data/lib/dynamic_migrations/postgres/server/database/schema/table/foreign_key_constraint.rb +40 -5
  47. data/lib/dynamic_migrations/postgres/server/database/schema/table/index.rb +23 -9
  48. data/lib/dynamic_migrations/postgres/server/database/schema/table/primary_key.rb +21 -6
  49. data/lib/dynamic_migrations/postgres/server/database/schema/table/trigger.rb +151 -0
  50. data/lib/dynamic_migrations/postgres/server/database/schema/table/triggers.rb +66 -0
  51. data/lib/dynamic_migrations/postgres/server/database/schema/table/unique_constraint.rb +19 -9
  52. data/lib/dynamic_migrations/postgres/server/database/schema/table/validation.rb +20 -1
  53. data/lib/dynamic_migrations/postgres/server/database/schema/table.rb +15 -5
  54. data/lib/dynamic_migrations/postgres/server/database/schema/tables.rb +63 -0
  55. data/lib/dynamic_migrations/postgres/server/database/schema.rb +3 -49
  56. data/lib/dynamic_migrations/postgres/server/database/source.rb +21 -0
  57. data/lib/dynamic_migrations/postgres/server/database/structure_loader.rb +22 -112
  58. data/lib/dynamic_migrations/postgres/server/database/triggers_and_functions_loader.rb +131 -0
  59. data/lib/dynamic_migrations/postgres/server/database/validations_loader.rb +10 -4
  60. data/lib/dynamic_migrations/postgres/server/database.rb +2 -1
  61. data/lib/dynamic_migrations/postgres/server.rb +6 -0
  62. data/lib/dynamic_migrations/postgres.rb +1 -1
  63. data/lib/dynamic_migrations/version.rb +1 -1
  64. data/lib/dynamic_migrations.rb +47 -4
  65. metadata +44 -3
  66. data/lib/dynamic_migrations/postgres/data_types.rb +0 -320
@@ -0,0 +1,151 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DynamicMigrations
4
+ module Postgres
5
+ class Server
6
+ class Database
7
+ class Schema
8
+ class Table
9
+ # This class represents a postgres table trigger
10
+ class Trigger < Source
11
+ class ExpectedTableError < StandardError
12
+ end
13
+
14
+ class UnexpectedEventManipulationError < StandardError
15
+ end
16
+
17
+ class UnexpectedActionOrderError < StandardError
18
+ end
19
+
20
+ class UnexpectedActionStatementError < StandardError
21
+ end
22
+
23
+ class UnexpectedActionOrientationError < StandardError
24
+ end
25
+
26
+ class UnexpectedActionTimingError < StandardError
27
+ end
28
+
29
+ class ExpectedOldRecordsTableError < StandardError
30
+ end
31
+
32
+ class ExpectedNewRecordsTableError < StandardError
33
+ end
34
+
35
+ class ExpectedFunctionError < StandardError
36
+ end
37
+
38
+ attr_reader :table
39
+ attr_reader :name
40
+ attr_reader :event_manipulation
41
+ attr_reader :action_timing
42
+ attr_reader :action_order
43
+ attr_reader :action_condition
44
+ attr_reader :action_statement
45
+ attr_reader :action_orientation
46
+ attr_reader :function
47
+ attr_reader :action_reference_old_table
48
+ attr_reader :action_reference_new_table
49
+ attr_reader :description
50
+
51
+ # initialize a new object to represent a validation in a postgres table
52
+ def initialize source, table, name, action_timing:, event_manipulation:, action_order:, action_statement:, action_orientation:, function:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, description: nil
53
+ super source
54
+
55
+ unless table.is_a? Table
56
+ raise ExpectedTableError, table
57
+ end
58
+ @table = table
59
+
60
+ unless name.is_a? Symbol
61
+ raise ExpectedSymbolError, name
62
+ end
63
+ @name = name
64
+
65
+ unless [:before, :after].include? action_timing
66
+ raise UnexpectedActionTimingError, action_timing
67
+ end
68
+ @action_timing = action_timing
69
+
70
+ unless [:insert, :delete, :update].include? event_manipulation
71
+ raise UnexpectedEventManipulationError, event_manipulation
72
+ end
73
+ @event_manipulation = event_manipulation
74
+
75
+ unless action_order.is_a?(Integer) && action_order >= 1
76
+ raise UnexpectedActionOrderError, action_order
77
+ end
78
+ @action_order = action_order
79
+
80
+ unless action_condition.nil? || action_condition.is_a?(String)
81
+ raise ExpectedStringError, action_condition
82
+ end
83
+ @action_condition = action_condition
84
+
85
+ unless action_statement.is_a?(String) && action_statement[/\AEXECUTE FUNCTION [a-z]+(_[a-z]+)*\.[a-z]+(_[a-z]+)*\(\)\z/]
86
+ raise UnexpectedActionStatementError, "unexpected action statement `#{action_statement}`, currently only `EXECUTE FUNCTION function_name()` statements are supported"
87
+ end
88
+ @action_statement = action_statement
89
+
90
+ unless [:row, :statement].include? action_orientation
91
+ raise UnexpectedActionOrientationError, action_orientation
92
+ end
93
+ @action_orientation = action_orientation
94
+
95
+ unless function.is_a? Function
96
+ raise ExpectedFunctionError, function
97
+ end
98
+ # this should never happen, but adding it just in case
99
+ unless function.source == source
100
+ raise "Internal error - function source `#{function.source}` does not match trigger source `#{source}`"
101
+ end
102
+ @function = function
103
+ # associate this trigger with the function (so they are aware of each other)
104
+ @function.add_trigger self
105
+
106
+ unless action_reference_old_table.nil? || action_reference_old_table == :old_records
107
+ raise ExpectedOldRecordsTableError, "expected :old_records or nil, but got #{action_reference_old_table}"
108
+ end
109
+ @action_reference_old_table = action_reference_old_table
110
+
111
+ unless action_reference_new_table.nil? || action_reference_new_table == :new_records
112
+ raise ExpectedNewRecordsTableError, "expected :new_records or nil, but got #{action_reference_new_table}"
113
+ end
114
+ @action_reference_new_table = action_reference_new_table
115
+
116
+ unless description.nil?
117
+ raise ExpectedStringError, description unless description.is_a? String
118
+ @description = description
119
+ end
120
+ end
121
+
122
+ # return true if this has a description, otherwise false
123
+ def has_description?
124
+ !@description.nil?
125
+ end
126
+
127
+ def differences_descriptions other_trigger
128
+ descriptions = method_differences_descriptions other_trigger, [
129
+ :event_manipulation,
130
+ :action_timing,
131
+ :action_order,
132
+ :action_condition,
133
+ :action_statement,
134
+ :action_orientation,
135
+ :action_reference_old_table,
136
+ :action_reference_new_table
137
+ ]
138
+ # add the function differences descriptions
139
+ function.differences_descriptions(other_trigger.function).each do |description|
140
+ descriptions << "function_#{description}"
141
+ end
142
+ # return the combined differences
143
+ descriptions
144
+ end
145
+ end
146
+ end
147
+ end
148
+ end
149
+ end
150
+ end
151
+ end
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DynamicMigrations
4
+ module Postgres
5
+ class Server
6
+ class Database
7
+ class Schema
8
+ class Table < Source
9
+ # This module has all the tables methods for working with triggers
10
+ module Triggers
11
+ class TriggerDoesNotExistError < StandardError
12
+ end
13
+
14
+ class TriggerAlreadyExistsError < StandardError
15
+ end
16
+
17
+ # returns the trigger object for the provided trigger name, and raises an
18
+ # error if the trigger does not exist
19
+ def trigger name
20
+ raise ExpectedSymbolError, name unless name.is_a? Symbol
21
+ raise TriggerDoesNotExistError unless has_trigger? name
22
+ @triggers[name]
23
+ end
24
+
25
+ # returns true if this table has a trigger with the provided name, otherwise false
26
+ def has_trigger? name
27
+ raise ExpectedSymbolError, name unless name.is_a? Symbol
28
+ @triggers.key? name
29
+ end
30
+
31
+ # returns an array of this tables triggers
32
+ def triggers
33
+ @triggers.values
34
+ end
35
+
36
+ def triggers_hash
37
+ @triggers
38
+ end
39
+
40
+ # adds a new trigger to this table, and returns it
41
+ def add_trigger name, action_timing:, event_manipulation:, action_order:, action_statement:, action_orientation:, function:, action_condition: nil, action_reference_old_table: nil, action_reference_new_table: nil, description: nil
42
+ if has_trigger? name
43
+ raise(TriggerAlreadyExistsError, "Trigger #{name} already exists")
44
+ end
45
+ included_target = self
46
+ if included_target.is_a? Table
47
+ new_trigger = @triggers[name] = Trigger.new source, included_target, name, action_timing: action_timing, event_manipulation: event_manipulation, action_order: action_order, action_statement: action_statement, action_orientation: action_orientation, function: function, action_condition: action_condition, action_reference_old_table: action_reference_old_table, action_reference_new_table: action_reference_new_table, description: description
48
+ else
49
+ raise ModuleIncludedIntoUnexpectedTargetError, included_target
50
+ end
51
+ # sort the hash so that the triggers are in alphabetical order by name
52
+ sorted_triggers = {}
53
+ @triggers.keys.sort.each do |name|
54
+ sorted_triggers[name] = @triggers[name]
55
+ end
56
+ @triggers = sorted_triggers
57
+ # return the new trigger
58
+ new_trigger
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end
@@ -8,17 +8,12 @@ module DynamicMigrations
8
8
  class Table
9
9
  # This class represents a postgres table unique_constraint
10
10
  class UniqueConstraint < Source
11
- INDEX_TYPES = [:btree, :hash, :gist, :gin, :bring, :spgist]
12
-
13
11
  class ExpectedTableError < StandardError
14
12
  end
15
13
 
16
14
  class ExpectedArrayOfColumnsError < StandardError
17
15
  end
18
16
 
19
- class UnexpectedIndexTypeError < StandardError
20
- end
21
-
22
17
  class UnexpectedOrderError < StandardError
23
18
  end
24
19
 
@@ -30,12 +25,12 @@ module DynamicMigrations
30
25
 
31
26
  attr_reader :table
32
27
  attr_reader :name
33
- attr_reader :index_type
34
28
  attr_reader :deferrable
35
29
  attr_reader :initially_deferred
30
+ attr_reader :description
36
31
 
37
32
  # initialize a new object to represent a unique_constraint in a postgres table
38
- def initialize source, table, columns, name, index_type: :btree, deferrable: false, initially_deferred: false
33
+ def initialize source, table, columns, name, description: nil, deferrable: false, initially_deferred: false
39
34
  super source
40
35
  raise ExpectedTableError, table unless table.is_a? Table
41
36
  @table = table
@@ -53,8 +48,10 @@ module DynamicMigrations
53
48
  raise ExpectedSymbolError, name unless name.is_a? Symbol
54
49
  @name = name
55
50
 
56
- raise UnexpectedIndexTypeError, index_type unless INDEX_TYPES.include?(index_type)
57
- @index_type = index_type
51
+ unless description.nil?
52
+ raise ExpectedStringError, description unless description.is_a? String
53
+ @description = description
54
+ end
58
55
 
59
56
  raise ExpectedBooleanError, deferrable unless [true, false].include?(deferrable)
60
57
  @deferrable = deferrable
@@ -63,6 +60,11 @@ module DynamicMigrations
63
60
  @initially_deferred = initially_deferred
64
61
  end
65
62
 
63
+ # return true if this has a description, otherwise false
64
+ def has_description?
65
+ !@description.nil?
66
+ end
67
+
66
68
  # return an array of this unique_constraints columns
67
69
  def columns
68
70
  @columns.values
@@ -72,6 +74,14 @@ module DynamicMigrations
72
74
  @columns.keys
73
75
  end
74
76
 
77
+ def differences_descriptions other_unique_constraint
78
+ method_differences_descriptions other_unique_constraint, [
79
+ :column_names,
80
+ :deferrable,
81
+ :initially_deferred
82
+ ]
83
+ end
84
+
75
85
  private
76
86
 
77
87
  # used internally to set the columns from this objects initialize method
@@ -22,9 +22,10 @@ module DynamicMigrations
22
22
  attr_reader :check_clause
23
23
  attr_reader :deferrable
24
24
  attr_reader :initially_deferred
25
+ attr_reader :description
25
26
 
26
27
  # initialize a new object to represent a validation in a postgres table
27
- def initialize source, table, columns, name, check_clause, deferrable: false, initially_deferred: false
28
+ def initialize source, table, columns, name, check_clause, description: nil, deferrable: false, initially_deferred: false
28
29
  super source
29
30
  raise ExpectedTableError, table unless table.is_a? Table
30
31
  @table = table
@@ -45,6 +46,11 @@ module DynamicMigrations
45
46
  raise ExpectedStringError, check_clause unless check_clause.is_a? String
46
47
  @check_clause = check_clause
47
48
 
49
+ unless description.nil?
50
+ raise ExpectedStringError, description unless description.is_a? String
51
+ @description = description
52
+ end
53
+
48
54
  raise ExpectedBooleanError, deferrable unless [true, false].include?(deferrable)
49
55
  @deferrable = deferrable
50
56
 
@@ -52,6 +58,11 @@ module DynamicMigrations
52
58
  @initially_deferred = initially_deferred
53
59
  end
54
60
 
61
+ # return true if this has a description, otherwise false
62
+ def has_description?
63
+ !@description.nil?
64
+ end
65
+
55
66
  # return an array of this validations columns
56
67
  def columns
57
68
  @columns.values
@@ -61,6 +72,14 @@ module DynamicMigrations
61
72
  @columns.keys
62
73
  end
63
74
 
75
+ def differences_descriptions other_validation
76
+ method_differences_descriptions other_validation, [
77
+ :check_clause,
78
+ :deferrable,
79
+ :initially_deferred
80
+ ]
81
+ end
82
+
64
83
  private
65
84
 
66
85
  # used internally to set the columns from this objects initialize method
@@ -13,10 +13,14 @@ module DynamicMigrations
13
13
  class PrimaryKeyDoesNotExistError < StandardError
14
14
  end
15
15
 
16
+ class PrimaryKeyAlreadyExistsError < StandardError
17
+ end
18
+
16
19
  include Columns
17
20
  include Validations
18
21
  include Indexes
19
22
  include ForeignKeyConstraints
23
+ include Triggers
20
24
  include UniqueConstraints
21
25
 
22
26
  attr_reader :schema
@@ -24,20 +28,25 @@ module DynamicMigrations
24
28
  attr_reader :description
25
29
 
26
30
  # initialize a new object to represent a postgres table
27
- def initialize source, schema, name, description = nil
31
+ def initialize source, schema, name, description: nil
28
32
  super source
33
+
29
34
  raise ExpectedSchemaError, schema unless schema.is_a? Schema
35
+ @schema = schema
36
+
30
37
  raise ExpectedSymbolError, name unless name.is_a? Symbol
38
+ @name = name
39
+
31
40
  unless description.nil?
32
41
  raise ExpectedStringError, description unless description.is_a? String
33
42
  @description = description
34
43
  end
35
- @schema = schema
36
- @name = name
44
+
37
45
  @columns = {}
38
46
  @validations = {}
39
47
  @indexes = {}
40
48
  @foreign_key_constraints = {}
49
+ @triggers = {}
41
50
  @unique_constraints = {}
42
51
  end
43
52
 
@@ -60,10 +69,11 @@ module DynamicMigrations
60
69
 
61
70
  # returns a primary key if one exists, else raises an error
62
71
  def primary_key
63
- unless @primary_key
72
+ pk = @primary_key
73
+ unless pk
64
74
  raise PrimaryKeyDoesNotExistError
65
75
  end
66
- @primary_key
76
+ pk
67
77
  end
68
78
  end
69
79
  end
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DynamicMigrations
4
+ module Postgres
5
+ class Server
6
+ class Database
7
+ class Schema < Source
8
+ module Tables
9
+ class TableAlreadyExistsError < StandardError
10
+ end
11
+
12
+ class TableDoesNotExistError < StandardError
13
+ end
14
+
15
+ # create and add a new table from a provided table name
16
+ def add_table table_name, description: nil
17
+ raise ExpectedSymbolError, table_name unless table_name.is_a? Symbol
18
+ if has_table? table_name
19
+ raise(TableAlreadyExistsError, "Table #{table_name} already exists")
20
+ end
21
+ included_target = self
22
+ if included_target.is_a? Schema
23
+ new_table = @tables[table_name] = Table.new source, included_target, table_name, description: description
24
+ else
25
+ raise ModuleIncludedIntoUnexpectedTargetError, included_target
26
+ end
27
+ # sort the hash so that the tables are in alphabetical order by name
28
+ sorted_tables = {}
29
+ @tables.keys.sort.each do |table_name|
30
+ sorted_tables[table_name] = @tables[table_name]
31
+ end
32
+ @tables = sorted_tables
33
+ # return the new table
34
+ new_table
35
+ end
36
+
37
+ # return a table by its name, raises an error if the table does not exist
38
+ def table table_name
39
+ raise ExpectedSymbolError, table_name unless table_name.is_a? Symbol
40
+ raise TableDoesNotExistError unless has_table? table_name
41
+ @tables[table_name]
42
+ end
43
+
44
+ # returns true/false representing if a table with the provided name exists
45
+ def has_table? table_name
46
+ raise ExpectedSymbolError, table_name unless table_name.is_a? Symbol
47
+ @tables.key? table_name
48
+ end
49
+
50
+ # returns an array of all tables in the schema
51
+ def tables
52
+ @tables.values
53
+ end
54
+
55
+ def tables_hash
56
+ @tables
57
+ end
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -11,11 +11,8 @@ module DynamicMigrations
11
11
  class ExpectedDatabaseError < StandardError
12
12
  end
13
13
 
14
- class TableAlreadyExistsError < StandardError
15
- end
16
-
17
- class TableDoesNotExistError < StandardError
18
- end
14
+ include Tables
15
+ include Functions
19
16
 
20
17
  attr_reader :database
21
18
  attr_reader :name
@@ -28,50 +25,7 @@ module DynamicMigrations
28
25
  @database = database
29
26
  @name = name
30
27
  @tables = {}
31
- end
32
-
33
- # create and add a new table from a provided table name
34
- def add_table table_name, description = nil
35
- raise ExpectedSymbolError, table_name unless table_name.is_a? Symbol
36
- if has_table? table_name
37
- raise(TableAlreadyExistsError, "Table #{table_name} already exists")
38
- end
39
- included_target = self
40
- if included_target.is_a? Schema
41
- new_table = @tables[table_name] = Table.new source, included_target, table_name, description
42
- else
43
- raise ModuleIncludedIntoUnexpectedTargetError, included_target
44
- end
45
- # sort the hash so that the tables are in alphabetical order by name
46
- sorted_tables = {}
47
- @tables.keys.sort.each do |table_name|
48
- sorted_tables[table_name] = @tables[table_name]
49
- end
50
- @tables = sorted_tables
51
- # return the new table
52
- new_table
53
- end
54
-
55
- # return a table by its name, raises an error if the table does not exist
56
- def table table_name
57
- raise ExpectedSymbolError, table_name unless table_name.is_a? Symbol
58
- raise TableDoesNotExistError unless has_table? table_name
59
- @tables[table_name]
60
- end
61
-
62
- # returns true/false representing if a table with the provided name exists
63
- def has_table? table_name
64
- raise ExpectedSymbolError, table_name unless table_name.is_a? Symbol
65
- @tables.key? table_name
66
- end
67
-
68
- # returns an array of all tables in the schema
69
- def tables
70
- @tables.values
71
- end
72
-
73
- def tables_hash
74
- @tables
28
+ @functions = {}
75
29
  end
76
30
  end
77
31
  end
@@ -30,6 +30,27 @@ module DynamicMigrations
30
30
  raise ExpectedSymbolError, "expected Symbol but got #{value}"
31
31
  end
32
32
  end
33
+
34
+ private
35
+
36
+ # used to compare two objects and return a description of the differences
37
+ # it calls the method names provided on each object and compares the results
38
+ # and returns a human readable description of the differences
39
+ def method_differences_descriptions other_object, method_names
40
+ lines = []
41
+ ([:name] + method_names + [:description]).each do |method_name|
42
+ original_value = send method_name
43
+ updated_value = other_object.send method_name
44
+ if original_value != updated_value
45
+ lines << "#{method_name} changed from `#{original_value}` to `#{updated_value}`"
46
+ end
47
+ end
48
+
49
+ # return an array of lines, not a finalized string, this is because this
50
+ # method is typically called as part of a larger procedure that will compare
51
+ # other aspects of the object too
52
+ lines
53
+ end
33
54
  end
34
55
  end
35
56
  end