Class: Fixpoint
- Inherits:
-
Object
- Object
- Fixpoint
- Defined in:
- lib/fixpoint.rb
Overview
A fixpoint is a snapshot of the database contents. It is saved to the spec/fixpoints folder. A fixpoint (file) contains a mapping of table names to a list if their records.
Empty tables are stripped from files.
Make sure to run the tests in the right order: In a single RSpec file, you can use the order in which the tests are defined (‘RSpec.describe ’MyFeature’, order: :defined do`). However, tests in groups might follow a slightly different order (see relishapp.com/rspec/rspec-core/docs/configuration/overriding-global-ordering)
If you did a lot of changes to a test, you can remove a fixpoint file from its directory. It will be recreated when the test producing it runs again. Don’t forget re-running the tests _based on_ it because their fixpoints might have to change too. Example: You need to add something to the database’s seeds.rb. All subsequent fixpoints are missing the required entry. To update all fixpoints, just remove the whole ‘spec/fixpoints` folder and re-run all tests. Now all fixpoints should be updated. Be careful though, don’t just remove the fixpoints if you are not sure what is going on. A change in a fixpoint might point to an unintended change in code.
We need to be be careful to use let and let! with factories. Records might be created twice when using create in there (once by the fixpoint and once by the factory).
KNOWN ISSUES Under certain conditions you may get ‘duplicate key value violates unique constraint` because the primary key sequences are not updated correctly. If this happens, just add a Fixpoint.reset_pk_sequences! at the beginning of your test. We need to dig a little deeper here at some point…
LIMITATIONS The records in tables are ordered by their id. If there is no id for a table, we use database’s order (what the SELECT query returns). This order may be instable.
Direct Known Subclasses
Defined Under Namespace
Classes: Error
Constant Summary collapse
- FIXPOINT_FOLDER =
'fixpoints'- TABLES_TO_SKIP =
%w[ar_internal_metadata delayed_jobs schema_info schema_migrations].freeze
Instance Attribute Summary collapse
-
#records_in_tables ⇒ Object
readonly
the complete records in the tables.
Class Method Summary collapse
- .exists?(fixname) ⇒ Boolean
- .fixpoint_path(fixname) ⇒ Object
-
.from_database(conn) ⇒ Object
Creates a Fixpoint from the database contents.
- .from_file(fixname) ⇒ Object
- .remove(fixname) ⇒ Object
-
.reset_pk_sequences!(conn) ⇒ Object
reset primary key sequences for all tables useful when tests sometimes run before the storing the first fixpoint.
Instance Method Summary collapse
-
#initialize(records_in_tables) ⇒ Fixpoint
constructor
A new instance of Fixpoint.
- #load_into_database(conn) ⇒ Object
-
#records_for_table(table_name, ignore_columns = []) ⇒ Object
Returns the records for the given
table_nameas a list of Hashes. - #save_to_file(fixname) ⇒ Object
- #table_names ⇒ Object
Constructor Details
#initialize(records_in_tables) ⇒ Fixpoint
Returns a new instance of Fixpoint.
106 107 108 |
# File 'lib/fixpoint.rb', line 106 def initialize(records_in_tables) @records_in_tables = records_in_tables end |
Instance Attribute Details
#records_in_tables ⇒ Object (readonly)
the complete records in the tables
104 105 106 |
# File 'lib/fixpoint.rb', line 104 def records_in_tables @records_in_tables end |
Class Method Details
.exists?(fixname) ⇒ Boolean
36 37 38 |
# File 'lib/fixpoint.rb', line 36 def exists?(fixname) File.exist?(fixpoint_path(fixname)) end |
.fixpoint_path(fixname) ⇒ Object
64 65 66 67 68 69 70 |
# File 'lib/fixpoint.rb', line 64 def fixpoint_path(fixname) fspath = self.fixpoints_path raise Fixpoint::Error, 'Can not automatically infer the base path for the specs, please set `rspec_config.fixpoints_path` explicitly' if fspath.nil? raise Fixpoint::Error, "Please create the fixpoints folder (and maybe create a .gitkeep): #{fspath}" if !File.exist?(fspath) File.join(fspath, "#{fixname}.yml") end |
.from_database(conn) ⇒ Object
Creates a Fixpoint from the database contents. Empty tables are skipped.
48 49 50 |
# File 'lib/fixpoint.rb', line 48 def from_database(conn) new(read_database_records(conn)) end |
.from_file(fixname) ⇒ Object
40 41 42 43 44 45 |
# File 'lib/fixpoint.rb', line 40 def from_file(fixname) raise Fixpoint::Error, "The requested fixpoint (\"#{fixname}\") could not be found. Re-run the test which stores the fixpoint." unless exists?(fixname) file_path = fixpoint_path(fixname) new(YAML.load_file(file_path)) end |
.remove(fixname) ⇒ Object
52 53 54 |
# File 'lib/fixpoint.rb', line 52 def remove(fixname) FileUtils.rm_f(fixpoint_path(fixname)) end |
.reset_pk_sequences!(conn) ⇒ Object
reset primary key sequences for all tables useful when tests sometimes run before the storing the first fixpoint. these test might have incremented the id sequence already, so the ids in the fixpoints chance (which leads to differences).
59 60 61 62 |
# File 'lib/fixpoint.rb', line 59 def reset_pk_sequences!(conn) return unless conn.respond_to?(:reset_pk_sequence!) conn.tables.each { |table_name| conn.reset_pk_sequence!(table_name) } end |
Instance Method Details
#load_into_database(conn) ⇒ Object
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 |
# File 'lib/fixpoint.rb', line 110 def load_into_database(conn) # Here some more pointers on implementation details of fixtures: # - https://github.com/rails/rails/blob/2998672fc22f0d5e1a79a29ccb60d0d0e627a430/activerecord/lib/active_record/fixtures.rb#L612 # - http://api.rubyonrails.org/v5.2.4/classes/ActiveRecord/FixtureSet.html#method-c-create_fixtures # - https://github.com/rails/rails/blob/67feba0c822d64741d574dfea808c1a2feedbcfc/activerecord/test/cases/fixtures_test.rb # # Note from the past (useful if we want to get back to using Rails' +create_fixtures+ method) # we used to do: ActiveRecord::FixtureSet.create_fixtures(folder_path, filename_without_extension) # this will also clear the table # but we abandoned this approach because we want to one file per fixpoint (not one file per table) # ActiveRecord::FixtureSet.reset_cache # create_fixtures does use only the table name as cache key. we always invalidate the cache because we may want to read different fixpoints but with the same table names # let's remove all data conn.tables.each { |table| conn.select_all("DELETE FROM #{conn.quote_table_name(table)}") } # actually insert conn.insert_fixtures_set(@records_in_tables) self.class.reset_pk_sequences!(conn) end |
#records_for_table(table_name, ignore_columns = []) ⇒ Object
Returns the records for the given table_name as a list of Hashes. ignore_columns array of columns to remove from each record Hash.
Aside from having the form <tt>[:created_at, :updated_at]</tt>,
it can contain attributes scoped by a table name <tt>[:created_at, :updated_at, users: [:password_hash]]</tt>
143 144 145 |
# File 'lib/fixpoint.rb', line 143 def records_for_table(table_name, ignore_columns = []) strip_columns_from_records(@records_in_tables[table_name], table_name, ignore_columns) end |
#save_to_file(fixname) ⇒ Object
129 130 131 132 133 |
# File 'lib/fixpoint.rb', line 129 def save_to_file(fixname) file_path = self.class.fixpoint_path(fixname) FileUtils.mkdir_p(File.dirname(file_path)) File.write(file_path, contents_for_file) end |
#table_names ⇒ Object
135 136 137 |
# File 'lib/fixpoint.rb', line 135 def table_names @records_in_tables.keys end |