From d94d4dcd89e1aba63a7c044ae6d94c28ddcc79c7 Mon Sep 17 00:00:00 2001 From: Koji Onishi Date: Tue, 1 Oct 2024 13:05:42 +0900 Subject: [PATCH 1/5] add date/time columns to fixtures for testing --- spec/fixtures/articles.yml | 9 +++++++++ spec/support/active_record_models.rb | 5 +++++ 2 files changed, 14 insertions(+) diff --git a/spec/fixtures/articles.yml b/spec/fixtures/articles.yml index 1260289..4ef33fb 100644 --- a/spec/fixtures/articles.yml +++ b/spec/fixtures/articles.yml @@ -5,12 +5,21 @@ _fixture: __article_36: title: Phoenix writer: __author_5 + published_date: 2024-04-01 + published_time: '10:30:00' + first_drafted_at: 2024-02-01T12:12:12 __article_52: title: Harry Potter writer: __author_4 + published_date: 2024-03-01 + published_time: '10:00:00' + first_drafted_at: 2024-02-01T10:10:10+09:00 __article_143: title: Princess Mononoke writer: __author_13 + published_date: 2024-05-01 + published_time: '09:00:00' + first_drafted_at: 2024-02-01T08:08:08-05:00 __article_88: title: Alice in Wonderland writer: __author_83 diff --git a/spec/support/active_record_models.rb b/spec/support/active_record_models.rb index e3fc5a4..07e3383 100644 --- a/spec/support/active_record_models.rb +++ b/spec/support/active_record_models.rb @@ -1,3 +1,5 @@ +# frozen_string_literal: true + ActiveRecord::Schema.define(version: Time.now) do create_table :authors, force: true do |t| t.string :name, null: false @@ -6,6 +8,9 @@ create_table :articles, force: true do |t| t.references :author t.string :title, null: false + t.date :published_date + t.time :published_time + t.datetime :first_drafted_at end create_table :content_holders, force: true do |t| From cf704606b0b0fc434ec37ac5e7b853b24e1038ad Mon Sep 17 00:00:00 2001 From: Koji Onishi Date: Tue, 1 Oct 2024 16:21:29 +0900 Subject: [PATCH 2/5] add yaml_column_permitted_classes option for custom deserialization --- lib/dumped_railers.rb | 13 ++++++++++--- lib/dumped_railers/configuration.rb | 5 +++-- lib/dumped_railers/file_helper.rb | 4 ++-- lib/dumped_railers/import.rb | 4 ++-- 4 files changed, 17 insertions(+), 9 deletions(-) diff --git a/lib/dumped_railers.rb b/lib/dumped_railers.rb index c06e84d..e3d4761 100644 --- a/lib/dumped_railers.rb +++ b/lib/dumped_railers.rb @@ -24,13 +24,19 @@ def dump!(*models, base_dir: nil, preprocessors: nil, ignorable_columns: nil) fixtures end - def import!(*paths, authorized_models: nil, before_save: nil, after_save: nil) + def import!(*paths, authorized_models: nil, before_save: nil, after_save: nil, yaml_column_permitted_classes: []) # make sure class-baseed caches starts with clean state DumpedRailers::RecordBuilder::FixtureRow::RecordStore.clear! DumpedRailers::RecordBuilder::DependencyTracker.clear! # override global config settings when options are specified - runtime_options = { authorized_models: authorized_models.presence }.compact.reverse_merge(import_options) + runtime_options = + { + authorized_models: authorized_models.presence, + yaml_column_permitted_classes: yaml_column_permitted_classes.presence, + } + .compact + .reverse_merge(import_options) before_save = Array(before_save).compact after_save = Array(after_save).compact @@ -40,6 +46,7 @@ def import!(*paths, authorized_models: nil, before_save: nil, after_save: nil) authorized_models: runtime_options[:authorized_models], before_save: before_save, after_save: after_save, + yaml_column_permitted_classes: runtime_options[:yaml_column_permitted_classes] ) fixture_handler.import_all! end @@ -55,7 +62,7 @@ def dump_options end def import_options - options.slice(:authorized_models) + options.slice(:authorized_models, :yaml_column_permitted_classes) end end diff --git a/lib/dumped_railers/configuration.rb b/lib/dumped_railers/configuration.rb index 1747e13..7439b8d 100644 --- a/lib/dumped_railers/configuration.rb +++ b/lib/dumped_railers/configuration.rb @@ -5,7 +5,7 @@ module DumpedRailers module Configuration extend Forwardable - def_delegators :@_config, :preprocessors, :ignorable_columns, :authorized_models + def_delegators :@_config, :preprocessors, :ignorable_columns, :authorized_models, :yaml_column_permitted_classes def configure yield config @@ -19,8 +19,9 @@ def options def configure_defaults! clear_configuration!( ignorable_columns: IGNORABLE_COLUMNS, - preprocessors: [], + preprocessors: [], authorized_models: :any, + yaml_column_permitted_classes: ActiveRecord.yaml_column_permitted_classes + [Date, Time, DateTime] ) end diff --git a/lib/dumped_railers/file_helper.rb b/lib/dumped_railers/file_helper.rb index 4dfb93b..55512bf 100644 --- a/lib/dumped_railers/file_helper.rb +++ b/lib/dumped_railers/file_helper.rb @@ -5,7 +5,7 @@ module DumpedRailers module FileHelper class << self - def read_fixtures(*paths) + def read_fixtures(*paths, yaml_column_permitted_classes: []) yaml_files = paths.flat_map { |path| if File.file?(path) path @@ -18,7 +18,7 @@ def read_fixtures(*paths) yaml_files.map { |file| raw_data = ::File.read(file) - YAML.load(raw_data) + YAML.safe_load(raw_data, permitted_classes: yaml_column_permitted_classes) } end diff --git a/lib/dumped_railers/import.rb b/lib/dumped_railers/import.rb index 5702cd0..293db29 100644 --- a/lib/dumped_railers/import.rb +++ b/lib/dumped_railers/import.rb @@ -6,14 +6,14 @@ module DumpedRailers class Import attr_reader :fixture_set - def initialize(*paths, authorized_models: [], before_save: [], after_save: []) + def initialize(*paths, authorized_models: [], before_save: [], after_save: [], yaml_column_permitted_classes: []) @before_save = before_save @after_save = after_save if (paths.first.is_a? Hash) @raw_fixtures = paths.first.values else - @raw_fixtures = FileHelper.read_fixtures(*paths) + @raw_fixtures = FileHelper.read_fixtures(*paths, yaml_column_permitted_classes: yaml_column_permitted_classes) end @fixture_set = RecordBuilder::FixtureSet.new(@raw_fixtures, authorized_models: authorized_models) From e2d385d19fe6a95e7eb15f4a4c2183481ad666a0 Mon Sep 17 00:00:00 2001 From: Koji Onishi Date: Tue, 1 Oct 2024 16:22:17 +0900 Subject: [PATCH 3/5] test against YAML desirialization permission options --- spec/lib/dumped_railers/configuration_spec.rb | 15 + spec/lib/dumped_railers/dump_spec.rb | 166 ++-- .../fixture_builder/record_spec.rb | 15 +- spec/lib/dumped_railers/import_spec.rb | 870 ++++++++++-------- spec/lib/dumped_railers_spec.rb | 158 +++- 5 files changed, 734 insertions(+), 490 deletions(-) diff --git a/spec/lib/dumped_railers/configuration_spec.rb b/spec/lib/dumped_railers/configuration_spec.rb index 56c8f07..e498985 100644 --- a/spec/lib/dumped_railers/configuration_spec.rb +++ b/spec/lib/dumped_railers/configuration_spec.rb @@ -101,6 +101,7 @@ config.ignorable_columns = [:uuid] config.preprocessors = [:foo, :bar] config.authorized_models = [:model1, :model2] + config.yaml_column_permitted_classes = [Date] config.a_random_option = 'something' end end @@ -110,6 +111,7 @@ ignorable_columns: [:uuid], preprocessors: [:foo, :bar], authorized_models: [:model1, :model2], + yaml_column_permitted_classes: [Date], a_random_option: 'something', ) } @@ -118,6 +120,7 @@ subject { klass.options[:ignorable_columns] << :published_at klass.options[:preprocessors] << :baz + klass.options[:yaml_column_permitted_classes] << Time } it 'does updates original configurations' do @@ -126,6 +129,7 @@ ignorable_columns: [:uuid, :published_at], preprocessors: [:foo, :bar, :baz], authorized_models: [:model1, :model2], + yaml_column_permitted_classes: [Date, Time], a_random_option: 'something', } ) @@ -141,10 +145,17 @@ config.ignorable_columns = [:uuid] config.preprocessors = [:foo, :bar] config.authorized_models = [:model1, :model2] + config.yaml_column_permitted_classes = [Date, Time] config.a_random_option = :something end end + it 'has preset options' do + expect { subject }.to change { klass.options.keys }.to contain_exactly( + *%i[ignorable_columns preprocessors authorized_models yaml_column_permitted_classes] + ) + end + it 'resets ignorable_columns' do expect { subject }.to change { klass.ignorable_columns }.to %w[id created_at updated_at] end @@ -157,6 +168,10 @@ expect { subject }.to change { klass.authorized_models }.to :any end + it 'resets yaml_column_permitted_classes' do + expect { subject }.to change { klass.yaml_column_permitted_classes }.to match_array(ActiveRecord.yaml_column_permitted_classes + [Date Time DateTime]) + end + it 'resets other options' do expect { subject }.to change { klass.instance_variable_get(:@_config).a_random_option }.to nil end diff --git a/spec/lib/dumped_railers/dump_spec.rb b/spec/lib/dumped_railers/dump_spec.rb index 5332c76..ef11670 100644 --- a/spec/lib/dumped_railers/dump_spec.rb +++ b/spec/lib/dumped_railers/dump_spec.rb @@ -7,9 +7,9 @@ let!(:author1) { Author.create!(name: 'William Shakespeare') } let!(:author2) { Author.create!(name: 'Shikibu Murasaki') } - let!(:article1) { Article.create!(title: 'Romeo and Juliet', writer: author1) } - let!(:article2) { Article.create!(title: 'King Lear', writer: author1) } - let!(:article3) { Article.create!(title: 'Genji Monogatari', writer: author2) } + let!(:article1) { Article.create!(title: 'Romeo and Juliet', writer: author1, published_date: '2020-10-10', published_time: '12:00:00', first_drafted_at: '2020-10-01 12:00:00') } + let!(:article2) { Article.create!(title: 'King Lear', writer: author1, published_date: '2021-10-10', published_time: '12:00:00', first_drafted_at: '2021-10-01 12:00:00') } + let!(:article3) { Article.create!(title: 'Genji Monogatari', writer: author2, published_date: '2022-10-10', published_time: '12:00:00', first_drafted_at: '2022-10-01 12:00:00') } let(:models) { [Author, Article] } context 'without any preprocessors' do @@ -19,42 +19,69 @@ is_expected.to match( 'authors' => { - '_fixture' => - { - 'model_class' => 'Author', - 'fixture_generated_by' => 'DumpedRailers', - }, - "__author_#{author1.id}" => { - 'id' => author1.id, + '_fixture' => + { + 'model_class' => 'Author', + 'fixture_generated_by' => 'DumpedRailers', + }, + "__author_#{author1.id}" => { + 'id' => author1.id, 'name' => author1.name - }, - "__author_#{author2.id}" => { - 'id' => author2.id, + }, + "__author_#{author2.id}" => { + 'id' => author2.id, 'name' => author2.name - }, - }, + }, + }, 'articles' => { - '_fixture' => - { - 'model_class' => 'Article', - 'fixture_generated_by' => 'DumpedRailers', - }, - "__article_#{article1.id}" => { - 'id' => article1.id, - 'title' => article1.title, + '_fixture' => + { + 'model_class' => 'Article', + 'fixture_generated_by' => 'DumpedRailers', + }, + "__article_#{article1.id}" => { + 'id' => article1.id, + 'title' => article1.title, 'writer' => "__author_#{author1.id}", - }, - "__article_#{article2.id}" => { - 'id' => article2.id, - 'title' => article2.title, + 'published_date' => have_attributes( + to_formatted_s: '2020-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2020-10-01 12:00:00') + ), + }, + "__article_#{article2.id}" => { + 'id' => article2.id, + 'title' => article2.title, 'writer' => "__author_#{author1.id}", - }, - "__article_#{article3.id}" => { - 'id' => article3.id, - 'title' => article3.title, + 'published_date' => have_attributes( + to_formatted_s: '2021-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2021-10-01 12:00:00') + ), + }, + "__article_#{article3.id}" => { + 'id' => article3.id, + 'title' => article3.title, 'writer' => "__author_#{author2.id}", - }, + 'published_date' => have_attributes( + to_formatted_s: '2022-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2022-10-01 12:00:00') + ), + }, } ) } @@ -78,37 +105,64 @@ is_expected.to match( 'authors' => { - '_fixture' => - { - 'model_class' => 'Author', - 'fixture_generated_by' => 'DumpedRailers', - }, - "__author_#{author1.id}" => { + '_fixture' => + { + 'model_class' => 'Author', + 'fixture_generated_by' => 'DumpedRailers', + }, + "__author_#{author1.id}" => { 'name' => author1.name.upcase - }, - "__author_#{author2.id}" => { + }, + "__author_#{author2.id}" => { 'name' => author2.name.upcase - }, - }, + }, + }, 'articles' => { - '_fixture' => - { - 'model_class' => 'Article', - 'fixture_generated_by' => 'DumpedRailers', - }, - "__article_#{article1.id}" => { - 'title' => article1.title, + '_fixture' => + { + 'model_class' => 'Article', + 'fixture_generated_by' => 'DumpedRailers', + }, + "__article_#{article1.id}" => { + 'title' => article1.title, 'writer' => "__author_#{author1.id}", - }, - "__article_#{article2.id}" => { - 'title' => article2.title, + 'published_date' => have_attributes( + to_formatted_s: '2020-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2020-10-01 12:00:00') + ), + }, + "__article_#{article2.id}" => { + 'title' => article2.title, 'writer' => "__author_#{author1.id}", - }, - "__article_#{article3.id}" => { - 'title' => article3.title, + 'published_date' => have_attributes( + to_formatted_s: '2021-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2021-10-01 12:00:00') + ), + }, + "__article_#{article3.id}" => { + 'title' => article3.title, 'writer' => "__author_#{author2.id}", - }, + 'published_date' => have_attributes( + to_formatted_s: '2022-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2022-10-01 12:00:00') + ), + }, } ) } diff --git a/spec/lib/dumped_railers/fixture_builder/record_spec.rb b/spec/lib/dumped_railers/fixture_builder/record_spec.rb index 9ebf828..e4a04ad 100644 --- a/spec/lib/dumped_railers/fixture_builder/record_spec.rb +++ b/spec/lib/dumped_railers/fixture_builder/record_spec.rb @@ -40,14 +40,21 @@ context 'when the record has a direct reference (using primary_key)' do let(:author) { Author.create!(name: 'Edgar Allan Poe') } - let(:record) { Article.create!(title: 'The Murders in the Rue Morgue', writer: author) } + let(:record) { Article.create!(title: 'The Murders in the Rue Morgue', writer: author, published_date: '2020-01-01', published_time: '12:00:00') } let(:model) { Article } it { is_expected.to match( - 'id' => record.id, - 'title' => 'The Murders in the Rue Morgue', - 'writer' => "__author_#{author.id}" + 'id' => record.id, + 'title' => 'The Murders in the Rue Morgue', + 'writer' => "__author_#{author.id}", + 'published_date' => have_attributes( + to_formatted_s: '2020-01-01' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => nil, ) } end diff --git a/spec/lib/dumped_railers/import_spec.rb b/spec/lib/dumped_railers/import_spec.rb index 95f14b2..06c53e6 100644 --- a/spec/lib/dumped_railers/import_spec.rb +++ b/spec/lib/dumped_railers/import_spec.rb @@ -6,278 +6,451 @@ described_class.new( *paths, authorized_models: authorized_models, + yaml_column_permitted_classes: yaml_column_permitted_classes, ) } subject { import_handler.import_all! } - context 'with full authorization' do + + context 'without permissions to relevant classes (Date, Time)' do let(:authorized_models) { :any } + let(:paths) { ['spec/fixtures/authors.yml', 'spec/fixtures/articles.yml'] } + context 'without any permissions' do + let(:yaml_column_permitted_classes) { [] } - context 'with fixture file paths' do - context 'with files that satisfies dependencies' do - let(:paths) { ['spec/fixtures/authors.yml', 'spec/fixtures/articles.yml'] } + it 'raises Psych::DisallowedClass' do + expect { subject }.to raise_error Psych::DisallowedClass + end + end - it 'generates corresponding records' do - expect { subject }.to change { Author.count }.by(5).and change { Article.count }.by(8) - end + context 'without partical permissions' do + let(:yaml_column_permitted_classes) { [Date] } - it 'generates author records' do - subject + it 'raises Psych::DisallowedClass' do + expect { subject }.to raise_error Psych::DisallowedClass + end + end + end - expect(Author.all).to contain_exactly( - have_attributes( - name: 'Osamu Tezuka' - ), - have_attributes( - name: 'J. K. Rowling' - ), - have_attributes( - name: 'Hayao Miyazaki' - ), - have_attributes( - name: 'Walt Disney' - ), - have_attributes( - name: 'John Ronald Reuel Tolkien' - ), - ) - end + context 'with permissions to relevant classes (Date, Time)' do + let(:yaml_column_permitted_classes) { [Date, Time] } - it 'generates article records with proper associations' do - subject + context 'with full authorization' do + let(:authorized_models) { :any } - expect(Article.all).to contain_exactly( - have_attributes( - title: 'Harry Potter', - writer: have_attributes( - name: 'J. K. Rowling' + context 'with fixture file paths' do + context 'with files that satisfies dependencies' do + let(:paths) { ['spec/fixtures/authors.yml', 'spec/fixtures/articles.yml'] } + + it 'generates corresponding records' do + expect { subject }.to change { Author.count }.by(5).and change { Article.count }.by(8) + end + + it 'generates author records' do + subject + + expect(Author.all).to contain_exactly( + have_attributes( + name: 'Osamu Tezuka' ), - ), - have_attributes( - title: 'Princess Mononoke', - writer: have_attributes( - name: 'Hayao Miyazaki' + have_attributes( + name: 'J. K. Rowling' ), - ), - have_attributes( - title: 'Sprited Away', - writer: have_attributes( + have_attributes( name: 'Hayao Miyazaki' ), - ), - have_attributes( - title: 'Alice in Wonderland', - writer: have_attributes( + have_attributes( name: 'Walt Disney' ), - ), - have_attributes( - title: 'Peter Pan', - writer: have_attributes( - name: 'Walt Disney' - ), - ), - have_attributes( - title: 'The Lord of the Rings', - writer: have_attributes( + have_attributes( name: 'John Ronald Reuel Tolkien' ), - ), - have_attributes( - title: 'Phoenix', - writer: have_attributes( - name: 'Osamu Tezuka' - ), - ), - have_attributes( - title: 'Black Jack', - writer: have_attributes( - name: 'Osamu Tezuka' - ), - ), - ) - end - end + ) + end - context 'with files that lack dependent records' do - let(:paths) { - [ - 'spec/fixtures/content_holders.yml', - 'spec/fixtures/text_contents.yml', - 'spec/fixtures/picture_contents.yml', - 'spec/fixtures/video_contents.yml', - ] - } + it 'generates article records with proper associations' do + subject - it 'raises RuntimeError' do - expect { subject }.to raise_error RuntimeError + expect(Article.all).to contain_exactly( + have_attributes( + title: 'Phoenix', + writer: have_attributes( + name: 'Osamu Tezuka' + ), + published_date: have_attributes( + to_formatted_s: '2024-04-01', + ), + published_time: have_attributes( + to_formatted_s: include('10:30:00'), + ), + first_drafted_at: have_attributes( + to_formatted_s: '2024-02-01 12:12:12 UTC', + ), + ), + have_attributes( + title: 'Harry Potter', + writer: have_attributes( + name: 'J. K. Rowling' + ), + published_date: have_attributes( + to_formatted_s: '2024-03-01', + ), + published_time: have_attributes( + to_formatted_s: include('10:00:00'), + ), + first_drafted_at: have_attributes( + to_formatted_s: '2024-02-01 01:10:10 UTC', + ), + ), + have_attributes( + title: 'Princess Mononoke', + writer: have_attributes( + name: 'Hayao Miyazaki' + ), + published_date: have_attributes( + to_formatted_s: '2024-05-01', + ), + published_time: have_attributes( + to_formatted_s: include('09:00:00'), + ), + first_drafted_at: have_attributes( + to_formatted_s: '2024-02-01 13:08:08 UTC', + ), + ), + have_attributes( + title: 'Sprited Away', + writer: have_attributes( + name: 'Hayao Miyazaki' + ), + ), + have_attributes( + title: 'Alice in Wonderland', + writer: have_attributes( + name: 'Walt Disney' + ), + ), + have_attributes( + title: 'Peter Pan', + writer: have_attributes( + name: 'Walt Disney' + ), + ), + have_attributes( + title: 'The Lord of the Rings', + writer: have_attributes( + name: 'John Ronald Reuel Tolkien' + ), + ), + have_attributes( + title: 'Black Jack', + writer: have_attributes( + name: 'Osamu Tezuka' + ), + ), + ) + end end - end - context 'with a directory' do - let(:paths) { ['spec/fixtures/'] } + context 'with files that lack dependent records' do + let(:paths) { + [ + 'spec/fixtures/content_holders.yml', + 'spec/fixtures/text_contents.yml', + 'spec/fixtures/picture_contents.yml', + 'spec/fixtures/video_contents.yml', + ] + } - it 'generates corresponding records' do - expect { subject }.to change { Author.count }.by(5) - .and change { Article.count }.by(8) - .and change { ContentHolder.count }.by(15) - .and change { TextContent.count }.by(9) - .and change { PictureContent.count }.by(3) - .and change { VideoContent.count }.by(3) + it 'raises RuntimeError' do + expect { subject }.to raise_error RuntimeError + end end - it 'generates author records' do - subject + context 'with a directory' do + let(:paths) { ['spec/fixtures/'] } - expect(Author.all).to contain_exactly( - have_attributes( - name: 'Osamu Tezuka' - ), - have_attributes( - name: 'J. K. Rowling' - ), - have_attributes( - name: 'Hayao Miyazaki' - ), - have_attributes( - name: 'Walt Disney' - ), - have_attributes( - name: 'John Ronald Reuel Tolkien' - ), - ) - end + it 'generates corresponding records' do + expect { subject }.to change { Author.count }.by(5) + .and change { Article.count }.by(8) + .and change { ContentHolder.count }.by(15) + .and change { TextContent.count }.by(9) + .and change { PictureContent.count }.by(3) + .and change { VideoContent.count }.by(3) + end - it 'generates article records with proper associations' do - subject + it 'generates author records' do + subject - expect(Article.all).to contain_exactly( - have_attributes( - title: 'Harry Potter', - writer: have_attributes( + expect(Author.all).to contain_exactly( + have_attributes( + name: 'Osamu Tezuka' + ), + have_attributes( name: 'J. K. Rowling' ), - ), - have_attributes( - title: 'Princess Mononoke', - writer: have_attributes( + have_attributes( name: 'Hayao Miyazaki' ), - ), - have_attributes( - title: 'Sprited Away', - writer: have_attributes( - name: 'Hayao Miyazaki' - ), - ), - have_attributes( - title: 'Alice in Wonderland', - writer: have_attributes( - name: 'Walt Disney' - ), - ), - have_attributes( - title: 'Peter Pan', - writer: have_attributes( + have_attributes( name: 'Walt Disney' ), - ), - have_attributes( - title: 'The Lord of the Rings', - writer: have_attributes( + have_attributes( name: 'John Ronald Reuel Tolkien' ), - ), - have_attributes( - title: 'Phoenix', - writer: have_attributes( - name: 'Osamu Tezuka' - ), - ), - have_attributes( - title: 'Black Jack', - writer: have_attributes( - name: 'Osamu Tezuka' - ), - ), - ) - end + ) + end - it 'generates content holders' do - subject + it 'generates article records with proper associations' do + subject - expect(ContentHolder.all).to contain_exactly( - have_attributes( - article: have_attributes( + expect(Article.all).to contain_exactly( + have_attributes( + title: 'Harry Potter', + writer: have_attributes( + name: 'J. K. Rowling' + ), + ), + have_attributes( + title: 'Princess Mononoke', + writer: have_attributes( + name: 'Hayao Miyazaki' + ), + ), + have_attributes( + title: 'Sprited Away', + writer: have_attributes( + name: 'Hayao Miyazaki' + ), + ), + have_attributes( + title: 'Alice in Wonderland', + writer: have_attributes( + name: 'Walt Disney' + ), + ), + have_attributes( + title: 'Peter Pan', + writer: have_attributes( + name: 'Walt Disney' + ), + ), + have_attributes( title: 'The Lord of the Rings', + writer: have_attributes( + name: 'John Ronald Reuel Tolkien' + ), ), - content: have_attributes( - body: "Where there's life there's hope, and need of vittles." + have_attributes( + title: 'Phoenix', + writer: have_attributes( + name: 'Osamu Tezuka' + ), ), - ), - have_attributes( - article: have_attributes( + have_attributes( title: 'Black Jack', + writer: have_attributes( + name: 'Osamu Tezuka' + ), ), - content: have_attributes( - body: "I don't know his real name, but they call him Black Jack." + ) + end + + it 'generates content holders' do + subject + + expect(ContentHolder.all).to contain_exactly( + have_attributes( + article: have_attributes( + title: 'The Lord of the Rings', + ), + content: have_attributes( + body: "Where there's life there's hope, and need of vittles." + ), + ), + have_attributes( + article: have_attributes( + title: 'Black Jack', + ), + content: have_attributes( + body: "I don't know his real name, but they call him Black Jack." + ), + ), + have_attributes( + article: have_attributes( + title: 'Alice in Wonderland', + ), + content: have_attributes( + body: 'This is an unbirthday party!' + ), + ), + have_attributes( + article: have_attributes( + title: 'Princess Mononoke', + ), + content: have_attributes( + body: 'To see with eyes unclouded by hate.' + ), + ), + have_attributes( + article: have_attributes( + title: 'Princess Mononoke', + ), + content: have_attributes( + body: 'You see everyone wants everything, that’s the way the world is.' + ), + ), + have_attributes( + article: have_attributes( + title: 'Phoenix', + ), + content: have_attributes( + body: "Life? Death? It's all meaningless!" + ), + ), + have_attributes( + article: have_attributes( + title: 'Harry Potter', + ), + content: have_attributes( + body: 'Of course it is happening inside your head, Harry, but why on earth should that mean that it is not real?' + ), + ), + have_attributes( + article: have_attributes( + title: 'Alice in Wonderland', + ), + content: have_attributes( + body: 'But how can one possibly pay attention to a book with no pictures in it?' + ), + ), + have_attributes( + content: have_attributes( + body: 'Our greatest natural resource is the minds of our children.' + ), + ), + have_attributes( + content: have_attributes( + file: { + 'url' => 'https://example.com/storage/the_hobbit/98x45672a.jpg', + 'metadata' => { + 'size' => 193450, + 'filename' => 'hobit.jpg', + } + } + ), + ), + have_attributes( + article: have_attributes( + title: 'Princess Mononoke', + ), + content: have_attributes( + file: { + 'url' => 'https://example.com/storage/princess_mononoke/98q3r28289s.png', + 'metadata' => { + 'size' => 1230890, + 'filename' => 'haku.png', + } + } + ), + ), + have_attributes( + article: have_attributes( + title: 'Phoenix', + ), + content: have_attributes( + file: { + 'url' => 'https://example.com/storage/phoenix/98m298sm912.jpg', + 'metadata' => { + 'size' => 823890, + 'filename' => 'phoenix.jpg', + } + } + ), + ), + have_attributes( + article: have_attributes( + title: 'Peter Pan', + ), + content: have_attributes( + file: { + 'url' => 'https://example.com/storage/peter_pan/9382sjdf8.mp4', + 'metadata' => { + 'size' => 9178348, + 'filename' => 'peter_and_wendy.mp4', + } + } + ), + ), + have_attributes( + article: have_attributes( + title: 'Harry Potter', + ), + content: have_attributes( + file: { + 'url' => 'https://example.com/storage/harry_potter/e9128347l.mp4', + 'metadata' => { + 'size' => 41735021, + 'filename' => 'hogwarts.mp4', + } + } + ), + ), + have_attributes( + article: have_attributes( + title: 'Sprited Away', + ), + content: have_attributes( + file: { + 'url' => 'https://example.com/storage/spritted_away/i012387413a.mp4', + 'metadata' => { + 'size' => 9747403, + 'filename' => 'yubaba.mp4', + } + } + ), ), - ), - have_attributes( - article: have_attributes( - title: 'Alice in Wonderland', + ) + end + + it 'generates text contents' do + subject + + expect(TextContent.all).to contain_exactly( + have_attributes( + body: "Where there's life there's hope, and need of vittles." ), - content: have_attributes( - body: 'This is an unbirthday party!' + have_attributes( + body: "I don't know his real name, but they call him Black Jack." ), - ), - have_attributes( - article: have_attributes( - title: 'Princess Mononoke', + have_attributes( + body: 'This is an unbirthday party!' ), - content: have_attributes( + have_attributes( body: 'To see with eyes unclouded by hate.' ), - ), - have_attributes( - article: have_attributes( - title: 'Princess Mononoke', - ), - content: have_attributes( + have_attributes( body: 'You see everyone wants everything, that’s the way the world is.' ), - ), - have_attributes( - article: have_attributes( - title: 'Phoenix', - ), - content: have_attributes( + have_attributes( body: "Life? Death? It's all meaningless!" ), - ), - have_attributes( - article: have_attributes( - title: 'Harry Potter', - ), - content: have_attributes( + have_attributes( body: 'Of course it is happening inside your head, Harry, but why on earth should that mean that it is not real?' ), - ), - have_attributes( - article: have_attributes( - title: 'Alice in Wonderland', - ), - content: have_attributes( + have_attributes( body: 'But how can one possibly pay attention to a book with no pictures in it?' ), - ), - have_attributes( - content: have_attributes( + have_attributes( body: 'Our greatest natural resource is the minds of our children.' ), - ), - have_attributes( - content: have_attributes( + ) + end + + it 'generates picture contents' do + subject + + expect(PictureContent.all).to contain_exactly( + have_attributes( file: { 'url' => 'https://example.com/storage/the_hobbit/98x45672a.jpg', 'metadata' => { @@ -286,12 +459,7 @@ } } ), - ), - have_attributes( - article: have_attributes( - title: 'Princess Mononoke', - ), - content: have_attributes( + have_attributes( file: { 'url' => 'https://example.com/storage/princess_mononoke/98q3r28289s.png', 'metadata' => { @@ -300,12 +468,7 @@ } } ), - ), - have_attributes( - article: have_attributes( - title: 'Phoenix', - ), - content: have_attributes( + have_attributes( file: { 'url' => 'https://example.com/storage/phoenix/98m298sm912.jpg', 'metadata' => { @@ -314,12 +477,14 @@ } } ), - ), - have_attributes( - article: have_attributes( - title: 'Peter Pan', - ), - content: have_attributes( + ) + end + + it 'generates video contents' do + subject + + expect(VideoContent.all).to contain_exactly( + have_attributes( file: { 'url' => 'https://example.com/storage/peter_pan/9382sjdf8.mp4', 'metadata' => { @@ -328,12 +493,7 @@ } } ), - ), - have_attributes( - article: have_attributes( - title: 'Harry Potter', - ), - content: have_attributes( + have_attributes( file: { 'url' => 'https://example.com/storage/harry_potter/e9128347l.mp4', 'metadata' => { @@ -342,12 +502,7 @@ } } ), - ), - have_attributes( - article: have_attributes( - title: 'Sprited Away', - ), - content: have_attributes( + have_attributes( file: { 'url' => 'https://example.com/storage/spritted_away/i012387413a.mp4', 'metadata' => { @@ -356,195 +511,93 @@ } } ), - ), - ) + ) + end end + end - it 'generates text contents' do - subject + context 'with in-memory fixtures' do + let(:fixtures) { + { + 'authors' => { + '_fixture' => + { + 'model_class' => 'Author', + 'fixture_generated_by' => 'DumpedRailers', + }, + '__author_1' => { + 'name' => 'William Shakespeare', + }, + '__author_2' => { + 'name' => 'Shikibu Murasaki', + }, + }, + 'articles' => { + '_fixture' => + { + 'model_class' => 'Article', + 'fixture_generated_by' => 'DumpedRailers', + }, + '__article_1' => { + 'title' => 'Romeo and Juliet', + 'writer' => '__author_1' + }, + '__article_2' => { + 'title' => 'King Lear', + 'writer' => '__author_1' + }, + '__article_3' => { + 'title' => 'Genji Monogatari', + 'writer' => '__author_2' + }, + } + } + } + let(:paths) { [fixtures] } - expect(TextContent.all).to contain_exactly( - have_attributes( - body: "Where there's life there's hope, and need of vittles." - ), - have_attributes( - body: "I don't know his real name, but they call him Black Jack." - ), - have_attributes( - body: 'This is an unbirthday party!' - ), - have_attributes( - body: 'To see with eyes unclouded by hate.' - ), - have_attributes( - body: 'You see everyone wants everything, that’s the way the world is.' - ), - have_attributes( - body: "Life? Death? It's all meaningless!" - ), - have_attributes( - body: 'Of course it is happening inside your head, Harry, but why on earth should that mean that it is not real?' - ), - have_attributes( - body: 'But how can one possibly pay attention to a book with no pictures in it?' - ), - have_attributes( - body: 'Our greatest natural resource is the minds of our children.' - ), - ) + it 'generates corresponding records' do + expect { subject }.to change { Author.count }.by(2).and change { Article.count }.by(3) end - it 'generates picture contents' do + it 'generates author records' do subject - expect(PictureContent.all).to contain_exactly( - have_attributes( - file: { - 'url' => 'https://example.com/storage/the_hobbit/98x45672a.jpg', - 'metadata' => { - 'size' => 193450, - 'filename' => 'hobit.jpg', - } - } - ), + expect(Author.all).to contain_exactly( have_attributes( - file: { - 'url' => 'https://example.com/storage/princess_mononoke/98q3r28289s.png', - 'metadata' => { - 'size' => 1230890, - 'filename' => 'haku.png', - } - } + name: 'William Shakespeare' ), have_attributes( - file: { - 'url' => 'https://example.com/storage/phoenix/98m298sm912.jpg', - 'metadata' => { - 'size' => 823890, - 'filename' => 'phoenix.jpg', - } - } + name: 'Shikibu Murasaki' ), ) end - it 'generates video contents' do + it 'generates article records with proper associations' do subject - expect(VideoContent.all).to contain_exactly( + expect(Article.all).to contain_exactly( have_attributes( - file: { - 'url' => 'https://example.com/storage/peter_pan/9382sjdf8.mp4', - 'metadata' => { - 'size' => 9178348, - 'filename' => 'peter_and_wendy.mp4', - } - } + title: 'Romeo and Juliet', + writer: have_attributes( + name: 'William Shakespeare' + ), ), have_attributes( - file: { - 'url' => 'https://example.com/storage/harry_potter/e9128347l.mp4', - 'metadata' => { - 'size' => 41735021, - 'filename' => 'hogwarts.mp4', - } - } + title: 'King Lear', + writer: have_attributes( + name: 'William Shakespeare' + ), ), have_attributes( - file: { - 'url' => 'https://example.com/storage/spritted_away/i012387413a.mp4', - 'metadata' => { - 'size' => 9747403, - 'filename' => 'yubaba.mp4', - } - } + title: 'Genji Monogatari', + writer: have_attributes( + name: 'Shikibu Murasaki' + ), ), ) end end end - - context 'with in-memory fixtures' do - let(:fixtures) { - { - 'authors' => { - '_fixture' => - { - 'model_class' => 'Author', - 'fixture_generated_by' => 'DumpedRailers', - }, - '__author_1' => { - 'name' => 'William Shakespeare', - }, - '__author_2' => { - 'name' => 'Shikibu Murasaki', - }, - }, - 'articles' => { - '_fixture' => - { - 'model_class' => 'Article', - 'fixture_generated_by' => 'DumpedRailers', - }, - '__article_1' => { - 'title' => 'Romeo and Juliet', - 'writer' => '__author_1' - }, - '__article_2' => { - 'title' => 'King Lear', - 'writer' => '__author_1' - }, - '__article_3' => { - 'title' => 'Genji Monogatari', - 'writer' => '__author_2' - }, - } - } - } - let(:paths) { [fixtures] } - - it 'generates corresponding records' do - expect { subject }.to change { Author.count }.by(2).and change { Article.count }.by(3) - end - - it 'generates author records' do - subject - - expect(Author.all).to contain_exactly( - have_attributes( - name: 'William Shakespeare' - ), - have_attributes( - name: 'Shikibu Murasaki' - ), - ) - end - - it 'generates article records with proper associations' do - subject - - expect(Article.all).to contain_exactly( - have_attributes( - title: 'Romeo and Juliet', - writer: have_attributes( - name: 'William Shakespeare' - ), - ), - have_attributes( - title: 'King Lear', - writer: have_attributes( - name: 'William Shakespeare' - ), - ), - have_attributes( - title: 'Genji Monogatari', - writer: have_attributes( - name: 'Shikibu Murasaki' - ), - ), - ) - end - end end describe 'callbacks' do @@ -553,11 +606,13 @@ *paths, authorized_models: :any, before_save: before_callbacks, - after_save: after_callbacks, + after_save: after_callbacks, + yaml_column_permitted_classes: yaml_column_permitted_classes, ) } let(:paths) { ['spec/fixtures/authors.yml', 'spec/fixtures/articles.yml'] } + let(:yaml_column_permitted_classes) { [Date, Time] } let(:before_callbacks) { [] } let(:after_callbacks) { [] } @@ -783,6 +838,7 @@ context 'when authorization granted for all the imported models' do let(:authorized_models) { [Author, Article] } let(:paths) { ['spec/fixtures/authors.yml', 'spec/fixtures/articles.yml'] } + let(:yaml_column_permitted_classes) { [Date, Time] } it 'does not raise RuntimeError' do expect { subject }.not_to raise_error @@ -796,6 +852,7 @@ context 'when authorization missing for part of the imported models' do let(:authorized_models) { [Author] } let(:paths) { ['spec/fixtures/authors.yml', 'spec/fixtures/articles.yml'] } + let(:yaml_column_permitted_classes) { [Date, Time] } it 'raises RuntimeError' do expect { subject }.to raise_error RuntimeError @@ -813,6 +870,7 @@ context 'with no authorization' do let(:authorized_models) { [] } let(:paths) { ['spec/fixtures/authors.yml', 'spec/fixtures/articles.yml'] } + let(:yaml_column_permitted_classes) { [Date, Time] } it 'raises RuntimeError' do expect { subject }.to raise_error RuntimeError diff --git a/spec/lib/dumped_railers_spec.rb b/spec/lib/dumped_railers_spec.rb index 4208689..fd18705 100644 --- a/spec/lib/dumped_railers_spec.rb +++ b/spec/lib/dumped_railers_spec.rb @@ -5,9 +5,9 @@ let!(:author1) { Author.create!(name: 'William Shakespeare') } let!(:author2) { Author.create!(name: 'Shikibu Murasaki') } - let!(:article1) { Article.create!(title: 'Romeo and Juliet', writer: author1) } - let!(:article2) { Article.create!(title: 'King Lear', writer: author1) } - let!(:article3) { Article.create!(title: 'Genji Monogatari', writer: author2) } + let!(:article1) { Article.create!(title: 'Romeo and Juliet', writer: author1, published_date: '2020-10-10', published_time: '12:00:00', first_drafted_at: '2020-10-01 12:00:00') } + let!(:article2) { Article.create!(title: 'King Lear', writer: author1, published_date: '2021-10-10', published_time: '12:00:00', first_drafted_at: '2021-10-01 12:00:00') } + let!(:article3) { Article.create!(title: 'Genji Monogatari', writer: author2, published_date: '2022-10-10', published_time: '12:00:00', first_drafted_at: '2022-10-01 12:00:00') } let(:models) { [Author, Article] } describe 'returned values' do @@ -15,36 +15,65 @@ it 'returns fixture data composed of record attributes by its model' do expect(subject).to match( - 'authors' => { + 'authors' => + { + '_fixture' => + { + 'model_class' => 'Author', + 'fixture_generated_by' => 'DumpedRailers', + }, + "__author_#{author1.id}" => { + 'name' => author1.name + }, + "__author_#{author2.id}" => { + 'name' => author2.name + }, + }, + 'articles' => + { '_fixture' => { - 'model_class' => 'Author', - 'fixture_generated_by' => 'DumpedRailers', - }, - "__author_#{author1.id}" => { - 'name' => author1.name - }, - "__author_#{author2.id}" => { - 'name' => author2.name - }, - }, - 'articles' => { - '_fixture' => - { - 'model_class' => 'Article', + 'model_class' => 'Article', 'fixture_generated_by' => 'DumpedRailers', }, "__article_#{article1.id}" => { - 'title' => article1.title, + 'title' => article1.title, 'writer' => "__author_#{author1.id}", + 'published_date' => have_attributes( + to_formatted_s: '2020-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2020-10-01 12:00:00') + ), }, "__article_#{article2.id}" => { - 'title' => article2.title, + 'title' => article2.title, 'writer' => "__author_#{author1.id}", + 'published_date' => have_attributes( + to_formatted_s: '2021-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2021-10-01 12:00:00') + ), }, "__article_#{article3.id}" => { - 'title' => article3.title, + 'title' => article3.title, 'writer' => "__author_#{author2.id}", + 'published_date' => have_attributes( + to_formatted_s: '2022-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2022-10-01 12:00:00') + ), }, } ) @@ -92,7 +121,7 @@ expect(File.exist?(fixture_file)) end - let(:fixture) { YAML.load_file(fixture_file) } + let(:fixture) { YAML.load_file(fixture_file, permitted_classes: described_class.yaml_column_permitted_classes) } it 'has the same attributes that the original records have' do expect(fixture).to match( @@ -105,14 +134,41 @@ "__article_#{article1.id}" => { 'title' => article1.title, 'writer' => "__author_#{author1.id}", + 'published_date' => have_attributes( + to_formatted_s: '2020-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2020-10-01 12:00:00') + ), }, "__article_#{article2.id}" => { 'title' => article2.title, 'writer' => "__author_#{author1.id}", + 'published_date' => have_attributes( + to_formatted_s: '2021-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2021-10-01 12:00:00') + ), }, "__article_#{article3.id}" => { 'title' => article3.title, 'writer' => "__author_#{author2.id}", + 'published_date' => have_attributes( + to_formatted_s: '2022-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2022-10-01 12:00:00') + ), }, } ) @@ -167,7 +223,7 @@ expect(File.exist?(fixture_file)) end - let(:fixture) { YAML.load_file(fixture_file) } + let(:fixture) { YAML.load_file(fixture_file, permitted_classes: described_class.yaml_column_permitted_classes) } it 'has upcased attributes' do expect(fixture).to match( @@ -181,16 +237,43 @@ 'id' => article1.id, 'title' => article1.title, 'writer' => "__author_#{author1.id}", + 'published_date' => have_attributes( + to_formatted_s: '2020-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2020-10-01 12:00:00') + ), }, "__article_#{article2.id}" => { 'id' => article2.id, 'title' => article2.title, 'writer' => "__author_#{author1.id}", + 'published_date' => have_attributes( + to_formatted_s: '2021-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2021-10-01 12:00:00') + ), }, "__article_#{article3.id}" => { 'id' => article3.id, 'title' => article3.title, 'writer' => "__author_#{author2.id}", + 'published_date' => have_attributes( + to_formatted_s: '2022-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2022-10-01 12:00:00') + ), }, } ) @@ -253,7 +336,7 @@ expect(File.exist?(fixture_file)) end - let(:fixture) { YAML.load_file(fixture_file) } + let(:fixture) { YAML.load_file(fixture_file, permitted_classes: described_class.yaml_column_permitted_classes) } it 'has upcased attributes' do expect(fixture).to match( @@ -266,14 +349,41 @@ "__article_#{article1.id}" => { 'title' => article1.title.upcase, 'writer' => "__author_#{author1.id}", + 'published_date' => have_attributes( + to_formatted_s: '2020-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2020-10-01 12:00:00') + ), }, "__article_#{article2.id}" => { 'title' => article2.title.upcase, 'writer' => "__author_#{author1.id}", + 'published_date' => have_attributes( + to_formatted_s: '2021-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2021-10-01 12:00:00') + ), }, "__article_#{article3.id}" => { 'title' => article3.title.upcase, 'writer' => "__author_#{author2.id}", + 'published_date' => have_attributes( + to_formatted_s: '2022-10-10' + ), + 'published_time' => have_attributes( + to_formatted_s: a_string_including('12:00:00') + ), + 'first_drafted_at' => have_attributes( + to_formatted_s: a_string_including('2022-10-01 12:00:00') + ), }, } ) From e9b817a3fcc4903dcbb332bf9933fd9db1a1fc54 Mon Sep 17 00:00:00 2001 From: Koji Onishi Date: Tue, 1 Oct 2024 17:08:40 +0900 Subject: [PATCH 4/5] add new option to README --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/README.md b/README.md index 2028c70..4ef4254 100644 --- a/README.md +++ b/README.md @@ -175,6 +175,48 @@ DumpedRailers.import!(fixture_path, before_save: before_callback, after_save: [a `before_save` / `after_save` can accept both single and multiple (array) arguments. +### Deserializing Custom Classes with YAML + +* YAML (Psych) does not permit to load random class objects for [security reasons](https://discuss.rubyonrails.org/t/cve-2022-32224-possible-rce-escalation-bug-with-serialized-columns-in-active-record/81017). +* By default, DumpedRailers handles all the objects that Rails permitts (i.e. [ActiveRecord.yaml_column_permitted_classes](https://guides.rubyonrails.org/configuring.html#config-active-record-yaml-column-permitted-classes)), plus Time, Date, and DateTime. +* DumpedRailers raises `Psych::DisallowedClass` error when non-permitted classes are detected. If you want DumpedRailsers handle other classes, you could specify `yaml_column_permitted_classes` option with configurations or import method's arguments. +* *Please use this option with extra care* for security - again, it is recommended to use this for development purpose only. + +```ruby +DumpedRailers.configure do |config| + config.ignorable_columns += [:published_on] # :published_on will be ignored *on top of* default settings. +end +``` + +#### Caveats +* If you wish to load Date, Time object, it would be easier to load it as a string. DumpedRailers will pass it to the specified ActiveRecord models and they typecast the raw string into the appropreate date/time object. + +* below columns (published_date, published_time, first_drafted_at) all will be passed as a string (as the value is surrounded by the quotes). Strings will be interperted to apropreate column type with ActiveRecord. + +```ruby +_fixture: + model_class: Article + fixture_generated_by: DumpedRailers +__article_1: + title: Harry Potter + published_date: '2024-03-01' + published_time: '10:00:00' + first_drafted_at: '2024-02-01T10:10:10+09:00' +``` + +* below fixture (without quotes) will be directly interperted to Date or Time via YAML module. It needs to have proper format that YAML can interpret. + +```ruby +_fixture: + model_class: Article + fixture_generated_by: DumpedRailers +__article_1: + title: Harry Potter + published_date: 2024-03-01 + published_time: 2000-01-01 10:00:00 + first_drafted_at: 2024-02-01T10:10:10+09:00 +``` + ### Configuration * All the settings can be configured by either configuration (global) or arguments (at runtime). From 3366b0c7efbe9a608ea8aec38f3923f242141477 Mon Sep 17 00:00:00 2001 From: Koji Onishi Date: Tue, 1 Oct 2024 17:23:34 +0900 Subject: [PATCH 5/5] use ActiveRecord.yaml_column_permitted_classes only when available --- lib/dumped_railers/configuration.rb | 11 ++++++++++- spec/lib/dumped_railers/configuration_spec.rb | 10 ++++++++-- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/lib/dumped_railers/configuration.rb b/lib/dumped_railers/configuration.rb index 7439b8d..1f7c489 100644 --- a/lib/dumped_railers/configuration.rb +++ b/lib/dumped_railers/configuration.rb @@ -17,11 +17,20 @@ def options IGNORABLE_COLUMNS = %w[id created_at updated_at] def configure_defaults! + default_yaml_column_permitted_classes = + # FIXME: this will be no longer needed when we drop support for older Rails versions + # https://discuss.rubyonrails.org/t/cve-2022-32224-possible-rce-escalation-bug-with-serialized-columns-in-active-record/81017 + if ActiveRecord.respond_to?(:yaml_column_permitted_classes) + ActiveRecord.yaml_column_permitted_classes + [Date, Time, DateTime] + else + [Date, Time, DateTime] + end + clear_configuration!( ignorable_columns: IGNORABLE_COLUMNS, preprocessors: [], authorized_models: :any, - yaml_column_permitted_classes: ActiveRecord.yaml_column_permitted_classes + [Date, Time, DateTime] + yaml_column_permitted_classes: default_yaml_column_permitted_classes, ) end diff --git a/spec/lib/dumped_railers/configuration_spec.rb b/spec/lib/dumped_railers/configuration_spec.rb index e498985..3c73d6d 100644 --- a/spec/lib/dumped_railers/configuration_spec.rb +++ b/spec/lib/dumped_railers/configuration_spec.rb @@ -168,8 +168,14 @@ expect { subject }.to change { klass.authorized_models }.to :any end - it 'resets yaml_column_permitted_classes' do - expect { subject }.to change { klass.yaml_column_permitted_classes }.to match_array(ActiveRecord.yaml_column_permitted_classes + [Date Time DateTime]) + if ActiveRecord.respond_to?(:yaml_column_permitted_classes) + it 'resets yaml_column_permitted_classes' do + expect { subject }.to change { klass.yaml_column_permitted_classes }.to match_array(ActiveRecord.yaml_column_permitted_classes + [Date, Time, DateTime]) + end + else + it 'resets yaml_column_permitted_classes' do + expect { subject }.to change { klass.yaml_column_permitted_classes }.to match_array([Date, Time, DateTime]) + end end it 'resets other options' do