db_seed_dump 1.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.
@@ -0,0 +1,173 @@
1
+ class SeedDump
2
+ module DumpMethods
3
+ APPEND_FILE_MODE = 'a+'.freeze
4
+ OVERWRITE_FILE_MODE = 'w+'.freeze
5
+ include Enumeration
6
+
7
+ def dump(records, options = {})
8
+ return nil if records.count.zero?
9
+
10
+ io = open_io(options)
11
+
12
+ write_records_to_io(records, io, options)
13
+ ensure
14
+ io.close if io.present?
15
+ end
16
+
17
+ private
18
+
19
+ def dump_record(record, options)
20
+ attribute_strings = []
21
+
22
+ # We select only string attribute names to avoid conflict
23
+ # with the composite_primary_keys gem (it returns composite
24
+ # primary key attribute names as hashes).
25
+ record.attributes.select { |key| key.is_a?(String) || key.is_a?(Symbol) }.each do |attribute, value|
26
+ unless options[:exclude].include?(attribute.to_sym)
27
+ attribute_strings << dump_attribute_new(attribute, value,
28
+ options)
29
+ end
30
+ end
31
+
32
+ open_character, close_character = options[:import] ? ['[', ']'] : ['{', '}']
33
+
34
+ "#{open_character}#{attribute_strings.join(', ')}#{close_character}"
35
+ end
36
+
37
+ def dump_attribute_new(attribute, value, options)
38
+ options[:import] ? value_to_s(value) : "#{attribute}: #{value_to_s(value)}"
39
+ end
40
+
41
+ def value_to_s(value)
42
+ value = case value
43
+ when BigDecimal, IPAddr
44
+ value.to_s
45
+ when Date, Time, DateTime
46
+ value.to_s(:db)
47
+ when Range
48
+ range_to_string(value)
49
+ when ->(v) { v.class.ancestors.map(&:to_s).include?('RGeo::Feature::Instance') }
50
+ value.to_s
51
+ else
52
+ value
53
+ end
54
+
55
+ value.inspect
56
+ end
57
+
58
+ def range_to_string(object)
59
+ from = object.begin.respond_to?(:infinite?) && object.begin.infinite? ? '' : object.begin
60
+ to = object.end.respond_to?(:infinite?) && object.end.infinite? ? '' : object.end
61
+ "[#{from},#{to}#{object.exclude_end? ? ')' : ']'}"
62
+ end
63
+
64
+ def open_io(options)
65
+ if options[:file].present?
66
+ mode = options[:append] ? APPEND_FILE_MODE : OVERWRITE_FILE_MODE
67
+ file_path = if options[:file_split_limit]
68
+ file_path_with_file_index(options)
69
+ else
70
+ options[:file]
71
+ end
72
+
73
+ File.open(file_path, mode)
74
+ else
75
+ StringIO.new('', OVERWRITE_FILE_MODE)
76
+ end
77
+ end
78
+
79
+ def file_path_with_file_index(options)
80
+ base_name = File.basename(options[:file], '.*')
81
+ options[:file].reverse.sub(
82
+ base_name.reverse,
83
+ [
84
+ base_name,
85
+ (options[:current_file_index]&.to_i || 1)
86
+ ].join('_').reverse
87
+ ).reverse
88
+ end
89
+
90
+ def write_records_to_io(records, io, options)
91
+ options[:exclude] ||= %i[id created_at updated_at]
92
+
93
+ setup_io(io, options, records)
94
+
95
+ enumeration_method = if records.is_a?(ActiveRecord::Relation) || records.is_a?(Class)
96
+ :active_record_enumeration
97
+ else
98
+ :enumerable_enumeration
99
+ end
100
+
101
+ send(enumeration_method, records, io, options) do |record_strings, last_batch, file_split_required|
102
+ io.write(record_strings.join(",\n "))
103
+
104
+ io.write(",\n ") unless last_batch
105
+
106
+ if options[:file].present? && file_split_required
107
+ options[:current_file_index] = ((options[:current_file_index]&.to_i || 1) + 1)
108
+ io.write("\n]#{active_record_import_options(options)})\n")
109
+ io = open_io(options)
110
+ setup_io(io, options, records)
111
+ end
112
+ end
113
+
114
+ io.write("\n]#{active_record_import_options(options)})\n")
115
+
116
+ if options[:file].present?
117
+ nil
118
+ else
119
+ io.rewind
120
+ io.read
121
+ end
122
+ end
123
+
124
+ def setup_io(io, options, records)
125
+ method = chosen_creation_method(options)
126
+ io.write("#{model_for(records)}.#{method}(")
127
+ if options[:import]
128
+ io.write("[#{attribute_names(records, options).map { |name| name.to_sym.inspect }.join(', ')}], ")
129
+ end
130
+ io.write("[\n ")
131
+ end
132
+
133
+ def chosen_creation_method(options)
134
+ if options[:import]
135
+ 'import'
136
+ elsif options[:insert_all]
137
+ 'insert_all'
138
+ else
139
+ 'create!'
140
+ end
141
+ end
142
+
143
+ def active_record_import_options(options)
144
+ return unless options[:import].is_a?(Hash) || options[:import_options].present?
145
+
146
+ if options[:import].is_a?(Hash)
147
+ ', ' + options[:import].map { |key, value| "#{key}: #{value}" }.join(', ')
148
+ else
149
+ ', ' + options[:import_options]
150
+ end
151
+ end
152
+
153
+ def attribute_names(records, options)
154
+ attribute_names = if records.is_a?(ActiveRecord::Relation) || records.is_a?(Class)
155
+ records.attribute_names
156
+ else
157
+ records[0].attribute_names
158
+ end
159
+
160
+ attribute_names.reject { |name| options[:exclude].include?(name.to_sym) }
161
+ end
162
+
163
+ def model_for(records)
164
+ if records.is_a?(Class)
165
+ records
166
+ elsif records.respond_to?(:model)
167
+ records.model
168
+ else
169
+ records[0].class
170
+ end
171
+ end
172
+ end
173
+ end
@@ -0,0 +1,182 @@
1
+ class SeedDump
2
+ module Environment
3
+ def dump_using_environment(env = {})
4
+ Rails.application.eager_load!
5
+
6
+ models = retrieve_models(env) - retrieve_models_exclude(env)
7
+
8
+ current_file_index = 1
9
+ limit = retrieve_limit_value(env)
10
+ append = retrieve_append_value(env)
11
+
12
+ # Eliminate HABTM models that have the same underlying table; otherwise
13
+ # they'll be dumped twice, once in each direction. Probably should apply
14
+ # to all models, but it's possible there are edge cases in which this
15
+ # is not the right behavior.
16
+
17
+ habtm, non_habtm = models.partition { |m| m.name =~ /^HABTM_/ }
18
+ models = non_habtm + habtm.uniq(&:table_name)
19
+
20
+ resolved_dependencies = DependencyUnwrangler.new(models).evaluation_order - retrieve_models_exclude(env)
21
+ models = resolved_dependencies unless resolved_dependencies.empty?
22
+ models.each do |model|
23
+ model = model.limit(limit) if limit.present?
24
+ options = {
25
+ append: append,
26
+ batch_size: retrieve_batch_size_value(env),
27
+ exclude: retrieve_dump_all_value(env) ? [] : retrieve_exclude_value(env),
28
+ insert_all: retrieve_insert_all_value(env),
29
+ file_split_limit: retreive_file_split_limit_value(env),
30
+ file: retrieve_file_value(env),
31
+ import: retrieve_import_value(env),
32
+ current_file_index: current_file_index,
33
+ import_options: retrieve_import_options(env)
34
+ }
35
+
36
+ SeedDump.dump(model, options)
37
+
38
+ append = true # Always append for every model after the first
39
+ # (append for the first model is determined by
40
+ # the APPEND environment variable).
41
+ current_file_index = options[:current_file_index]
42
+ end
43
+ end
44
+
45
+ private
46
+
47
+ # Internal: Array of Strings corresponding to Active Record model class names
48
+ # that should be excluded from the dump.
49
+ ACTIVE_RECORD_INTERNAL_MODELS = ['ActiveRecord::SchemaMigration',
50
+ 'ActiveRecord::InternalMetadata'].freeze
51
+
52
+ # Internal: Retrieves an Array of Active Record model class constants to be
53
+ # dumped.
54
+ #
55
+ # If a "MODEL" or "MODELS" environment variable is specified, there will be
56
+ # an attempt to parse the environment variable String by splitting it on
57
+ # commmas and then converting it to constant.
58
+ #
59
+ # Model classes that do not have corresponding database tables or database
60
+ # records will be filtered out, as will model classes internal to Active
61
+ # Record.
62
+ #
63
+ # env - Hash of environment variables from which to parse Active Record
64
+ # model classes. The Hash is not optional but the "MODEL" and "MODELS"
65
+ # keys are optional.
66
+ #
67
+ # Returns the Array of Active Record model classes to be dumped.
68
+ def retrieve_models(env)
69
+ # Parse either the "MODEL" environment variable or the "MODELS"
70
+ # environment variable, with "MODEL" taking precedence.
71
+ models_env = env['MODEL'] || env['MODELS']
72
+
73
+ # If there was a use models environment variable, split it and
74
+ # convert the given model string (e.g. "User") to an actual
75
+ # model constant (e.g. User).
76
+ #
77
+ # If a models environment variable was not given, use descendants of
78
+ # ActiveRecord::Base as the target set of models. This should be all
79
+ # model classes in the project.
80
+ models = if models_env
81
+ models_env.split(',')
82
+ .collect { |x| x.strip.underscore.singularize.camelize.constantize }
83
+ else
84
+ ActiveRecord::Base.descendants
85
+ end
86
+
87
+ # Filter the set of models to exclude:
88
+ # - The ActiveRecord::SchemaMigration model which is internal to Rails
89
+ # and should not be part of the dumped data.
90
+ # - Models that don't have a corresponding table in the database.
91
+ # - Models whose corresponding database tables are empty.
92
+ filtered_models = models.select do |model|
93
+ !ACTIVE_RECORD_INTERNAL_MODELS.include?(model.to_s) && \
94
+ model.table_exists? && \
95
+ model.exists?
96
+ end
97
+ end
98
+
99
+ # Internal: Returns a Boolean indicating whether the value for the "APPEND"
100
+ # key in the given Hash is equal to the String "true" (ignoring case),
101
+ # false if no value exists.
102
+ def retrieve_append_value(env)
103
+ parse_boolean_value(env['APPEND'])
104
+ end
105
+
106
+ # Internal: Returns a Boolean indicating whether the value for the "FILE_SPLIT_COUNT"
107
+ # key in the given Hash is equal to the String "true" (ignoring case),
108
+ # false if no value exists.
109
+ def retreive_file_split_limit_value(env)
110
+ retrieve_integer_value('FILE_SPLIT_LIMIT', env)
111
+ end
112
+
113
+ # Internal: Returns a Boolean indicating whether the value for the "IMPORT"
114
+ # key in the given Hash is equal to the String "true" (ignoring case),
115
+ # false if no value exists.
116
+ def retrieve_import_value(env)
117
+ parse_boolean_value(env['IMPORT'])
118
+ end
119
+
120
+ def retrieve_import_options(env)
121
+ env['IMPORT_OPTIONS']
122
+ end
123
+
124
+ # Internal: Returns a Boolean indicating whether the value for the "INSERT_ALL"
125
+ # key in the given Hash is equal to the String "true" (ignoring case),
126
+ # false if no value exists.
127
+ def retrieve_insert_all_value(env)
128
+ parse_boolean_value(env['INSERT_ALL'])
129
+ end
130
+
131
+ # Internal: Returns a Boolean indicating whether the value for the "DUMP_ALL"
132
+ # key in the given Hash is equal to the String "true" (ignoring case),
133
+ # false if no value exists.
134
+ def retrieve_dump_all_value(env)
135
+ parse_boolean_value(env['DUMP_ALL'])
136
+ end
137
+
138
+ # Internal: Retrieves an Array of Class constants parsed from the value for
139
+ # the "MODELS_EXCLUDE" key in the given Hash, and an empty Array if such
140
+ # key exists.
141
+ def retrieve_models_exclude(env)
142
+ env['MODELS_EXCLUDE'].to_s
143
+ .split(',')
144
+ .collect { |x| x.strip.underscore.singularize.camelize.constantize }
145
+ end
146
+
147
+ # Internal: Retrieves an Integer from the value for the "LIMIT" key in the
148
+ # given Hash, and nil if no such key exists.
149
+ def retrieve_limit_value(env)
150
+ retrieve_integer_value('LIMIT', env)
151
+ end
152
+
153
+ # Internal: Retrieves an Array of Symbols from the value for the "EXCLUDE"
154
+ # key from the given Hash, and nil if no such key exists.
155
+ def retrieve_exclude_value(env)
156
+ env['EXCLUDE'] ? env['EXCLUDE'].split(',').map { |e| e.strip.to_sym } : nil
157
+ end
158
+
159
+ # Internal: Retrieves the value for the "FILE" key from the given Hash, and
160
+ # 'db/seeds.rb' if no such key exists.
161
+ def retrieve_file_value(env)
162
+ env['FILE'] || 'db/seeds.rb'
163
+ end
164
+
165
+ # Internal: Retrieves an Integer from the value for the "BATCH_SIZE" key in
166
+ # the given Hash, and nil if no such key exists.
167
+ def retrieve_batch_size_value(env)
168
+ retrieve_integer_value('BATCH_SIZE', env)
169
+ end
170
+
171
+ # Internal: Retrieves an Integer from the value for the given key in
172
+ # the given Hash, and nil if no such key exists.
173
+ def retrieve_integer_value(key, hash)
174
+ hash[key]&.to_i
175
+ end
176
+
177
+ # Internal: Parses a Boolean from the given value.
178
+ def parse_boolean_value(value)
179
+ value.to_s.casecmp('true').zero?
180
+ end
181
+ end
182
+ end
@@ -0,0 +1,7 @@
1
+ class SeedDump
2
+ class Railtie < Rails::Railtie
3
+ rake_tasks do
4
+ load 'tasks/seed_dump.rake'
5
+ end
6
+ end
7
+ end
data/lib/seed_dump.rb ADDED
@@ -0,0 +1,15 @@
1
+ require 'ipaddr'
2
+ require 'rgl/adjacency'
3
+ require 'rgl/topsort'
4
+
5
+ require 'seed_dump/dump_methods/enumeration'
6
+ require 'seed_dump/dump_methods'
7
+ require 'seed_dump/environment'
8
+ require 'seed_dump/dependency_unwrangler'
9
+
10
+ class SeedDump
11
+ extend Environment
12
+ extend DumpMethods
13
+
14
+ require 'seed_dump/railtie' if defined?(Rails)
15
+ end
@@ -0,0 +1,8 @@
1
+ namespace :db do
2
+ namespace :seed do
3
+ desc 'Dump records from the database into db/seeds.rb'
4
+ task dump: :environment do
5
+ SeedDump.dump_using_environment(ENV)
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,200 @@
1
+ require 'spec_helper'
2
+
3
+ describe SeedDump do
4
+ def expected_output(operation: 'create!', include_id: false, id_offset: 0)
5
+ output = "Sample.#{operation}([\n "
6
+
7
+ data = []
8
+ ((1 + id_offset)..(3 + id_offset)).each do |i|
9
+ data << "{#{include_id ? "id: #{i}, " : ''}string: \"string\", text: \"text\", integer: 42, float: 3.14, decimal: \"2.72\", datetime: \"1776-07-04 19:14:00\", time: \"2000-01-01 03:15:00\", date: \"1863-11-19\", binary: \"binary\", boolean: false}"
10
+ end
11
+
12
+ "#{output}#{data.join(",\n ")}\n])\n"
13
+ end
14
+
15
+ describe '.dump' do
16
+ before do
17
+ Rails.application.eager_load!
18
+
19
+ create_db
20
+
21
+ FactoryBot.create_list(:sample, 3)
22
+ end
23
+
24
+ context 'without file option' do
25
+ it 'returns the dump of the models passed in' do
26
+ described_class.dump(Sample).should eq(expected_output)
27
+ end
28
+ end
29
+
30
+ context 'with file option' do
31
+ before do
32
+ @filename = Tempfile.new(File.join(Dir.tmpdir, 'foo'), nil)
33
+ end
34
+
35
+ after do
36
+ File.unlink(@filename)
37
+ end
38
+
39
+ it 'dumps the models to the specified file' do
40
+ described_class.dump(Sample, file: @filename)
41
+
42
+ File.open(@filename) { |file| file.read.should eq(expected_output) }
43
+ end
44
+
45
+ context 'with append option' do
46
+ it 'appends to the file rather than overwriting it' do
47
+ described_class.dump(Sample, file: @filename)
48
+ described_class.dump(Sample, file: @filename, append: true)
49
+
50
+ File.open(@filename) { |file| file.read.should eq(expected_output + expected_output) }
51
+ end
52
+ end
53
+ end
54
+
55
+ context 'with file option and file split option' do
56
+ let(:file_path) { Tempfile.new('./foo').path }
57
+ let(:result_file_path) { [file_path, '1'].join('_') }
58
+
59
+ after do
60
+ File.unlink(result_file_path)
61
+ end
62
+
63
+ it 'stores the information in file_path with file index' do
64
+ described_class.dump(Sample, file: file_path, file_split_limit: 5)
65
+
66
+ File.open(result_file_path) { |file| expect(file.read).to eq(expected_output) }
67
+ end
68
+ end
69
+
70
+ context 'ActiveRecord relation' do
71
+ it 'returns nil if the count is 0' do
72
+ described_class.dump(EmptyModel).should be(nil)
73
+ end
74
+
75
+ context 'with an order parameter' do
76
+ it 'dumps the models in the specified order' do
77
+ Sample.delete_all
78
+ samples = 3.times { |i| FactoryBot.create(:sample, integer: i) }
79
+
80
+ described_class.dump(Sample.order('integer DESC')).should eq("Sample.create!([\n {string: \"string\", text: \"text\", integer: 2, float: 3.14, decimal: \"2.72\", datetime: \"1776-07-04 19:14:00\", time: \"2000-01-01 03:15:00\", date: \"1863-11-19\", binary: \"binary\", boolean: false},\n {string: \"string\", text: \"text\", integer: 1, float: 3.14, decimal: \"2.72\", datetime: \"1776-07-04 19:14:00\", time: \"2000-01-01 03:15:00\", date: \"1863-11-19\", binary: \"binary\", boolean: false},\n {string: \"string\", text: \"text\", integer: 0, float: 3.14, decimal: \"2.72\", datetime: \"1776-07-04 19:14:00\", time: \"2000-01-01 03:15:00\", date: \"1863-11-19\", binary: \"binary\", boolean: false}\n])\n")
81
+ end
82
+ end
83
+
84
+ context 'without an order parameter' do
85
+ it 'dumps the models sorted by primary key ascending' do
86
+ described_class.dump(Sample).should eq(expected_output)
87
+ end
88
+ end
89
+
90
+ context 'with a limit parameter' do
91
+ it 'dumps the number of models specified by the limit when the limit is smaller than the batch size' do
92
+ expected_output = "Sample.create!([\n {string: \"string\", text: \"text\", integer: 42, float: 3.14, decimal: \"2.72\", datetime: \"1776-07-04 19:14:00\", time: \"2000-01-01 03:15:00\", date: \"1863-11-19\", binary: \"binary\", boolean: false}\n])\n"
93
+
94
+ described_class.dump(Sample.limit(1)).should eq(expected_output)
95
+ end
96
+
97
+ it 'dumps the number of models specified by the limit when the limit is larger than the batch size but not a multiple of the batch size' do
98
+ Sample.delete_all
99
+ FactoryBot.create_list(:sample, 4)
100
+
101
+ described_class.dump(Sample.limit(3), batch_size: 2).should eq(
102
+ expected_output(include_id: false,
103
+ id_offset: 3)
104
+ )
105
+ end
106
+ end
107
+ end
108
+
109
+ context 'with a batch_size parameter' do
110
+ it 'does not raise an exception' do
111
+ described_class.dump(Sample, batch_size: 100)
112
+ end
113
+
114
+ it 'does not cause records to not be dumped' do
115
+ described_class.dump(Sample, batch_size: 2).should eq(expected_output)
116
+
117
+ described_class.dump(Sample, batch_size: 1).should eq(expected_output)
118
+ end
119
+ end
120
+
121
+ context 'Array' do
122
+ it 'returns the dump of the models passed in' do
123
+ described_class.dump(Sample.all.to_a, batch_size: 2).should eq(expected_output)
124
+ end
125
+
126
+ it 'returns nil if the array is empty' do
127
+ described_class.dump([]).should be(nil)
128
+ end
129
+ end
130
+
131
+ context 'with an exclude parameter' do
132
+ it 'excludes the specified attributes from the dump' do
133
+ expected_output = "Sample.create!([\n {text: \"text\", integer: 42, decimal: \"2.72\", time: \"2000-01-01 03:15:00\", date: \"1863-11-19\", binary: \"binary\", boolean: false},\n {text: \"text\", integer: 42, decimal: \"2.72\", time: \"2000-01-01 03:15:00\", date: \"1863-11-19\", binary: \"binary\", boolean: false},\n {text: \"text\", integer: 42, decimal: \"2.72\", time: \"2000-01-01 03:15:00\", date: \"1863-11-19\", binary: \"binary\", boolean: false}\n])\n"
134
+
135
+ described_class.dump(Sample,
136
+ exclude: %i[id created_at updated_at string float datetime]).should eq(expected_output)
137
+ end
138
+ end
139
+
140
+ context 'Range' do
141
+ it 'dumps a class with ranges' do
142
+ expected_output = "RangeSample.create!([\n {range_with_end_included: \"[1,3]\", range_with_end_excluded: \"[1,3)\", positive_infinite_range: \"[1,]\", negative_infinite_range: \"[,1]\", infinite_range: \"[,]\"}\n])\n"
143
+
144
+ described_class.dump([RangeSample.new]).should eq(expected_output)
145
+ end
146
+ end
147
+
148
+ context 'activerecord-insert-all' do
149
+ it 'dumps in the activerecord-insert-all format when insert-all is true' do
150
+ described_class.dump(Sample, insert_all: true).should eq(expected_output(operation: 'insert_all'))
151
+ end
152
+ end
153
+
154
+ context 'activerecord-import' do
155
+ it 'dumps in the activerecord-import format when import is true' do
156
+ described_class.dump(Sample, import: true, exclude: []).should eq <<~RUBY
157
+ Sample.import([:id, :string, :text, :integer, :float, :decimal, :datetime, :time, :date, :binary, :boolean, :created_at, :updated_at], [
158
+ [1, "string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false, "1969-07-20 20:18:00", "1989-11-10 04:20:00"],
159
+ [2, "string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false, "1969-07-20 20:18:00", "1989-11-10 04:20:00"],
160
+ [3, "string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false, "1969-07-20 20:18:00", "1989-11-10 04:20:00"]
161
+ ])
162
+ RUBY
163
+ end
164
+
165
+ it 'omits excluded columns if they are specified' do
166
+ described_class.dump(Sample, import: true, exclude: %i[id created_at updated_at]).should eq <<~RUBY
167
+ Sample.import([:string, :text, :integer, :float, :decimal, :datetime, :time, :date, :binary, :boolean], [
168
+ ["string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false],
169
+ ["string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false],
170
+ ["string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false]
171
+ ])
172
+ RUBY
173
+ end
174
+
175
+ context 'should add the params to the output if they are specified' do
176
+ it 'dumps in the activerecord-import format when import is true' do
177
+ described_class.dump(Sample, import: { validate: false }, exclude: []).should eq <<~RUBY
178
+ Sample.import([:id, :string, :text, :integer, :float, :decimal, :datetime, :time, :date, :binary, :boolean, :created_at, :updated_at], [
179
+ [1, "string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false, "1969-07-20 20:18:00", "1989-11-10 04:20:00"],
180
+ [2, "string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false, "1969-07-20 20:18:00", "1989-11-10 04:20:00"],
181
+ [3, "string", "text", 42, 3.14, "2.72", "1776-07-04 19:14:00", "2000-01-01 03:15:00", "1863-11-19", "binary", false, "1969-07-20 20:18:00", "1989-11-10 04:20:00"]
182
+ ], validate: false)
183
+ RUBY
184
+ end
185
+ end
186
+ end
187
+ end
188
+ end
189
+
190
+ class RangeSample
191
+ def attributes
192
+ {
193
+ 'range_with_end_included' => (1..3),
194
+ 'range_with_end_excluded' => (1...3),
195
+ 'positive_infinite_range' => (1..Float::INFINITY),
196
+ 'negative_infinite_range' => (-Float::INFINITY..1),
197
+ 'infinite_range' => (-Float::INFINITY..Float::INFINITY)
198
+ }
199
+ end
200
+ end