db_seed_dump 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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