auto-seeding 0.1.4 → 0.1.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 55b1d2585f1d0df108b79d8df59c08fba396a4e0
4
- data.tar.gz: 1e369657c4836f5019a9f7b42b44c63e7be04861
3
+ metadata.gz: 959a395df405f309c48515a254490e92c6f6b08c
4
+ data.tar.gz: e1f48a26d1d196ff4c5581a0e750ed240792c65c
5
5
  SHA512:
6
- metadata.gz: 6a62fc30f1c4acd766ba2216e8d5c147672c2d7a6947df735097bf5e5239f16c96ed34879dcc24bb419b30c61c5ae96ac91f12e6a3dba3b4765c865449348ab0
7
- data.tar.gz: 25fd7522b909b4dfd55711e41a5381e94c7aca40f65758f52ef11e7b5914a6bf9e6eb79b1878fef910cfd45954eec63c3516b49ef8208188ec3af96bb199902e
6
+ metadata.gz: bc92862725d1ce097d198b8aed277eefe2816f293bf1ef0a59e4a9871649d18eb0757ed78fbf32f9074526cab6c4f0e68a1217dd5aba2a6b77719dd5663b3f8d
7
+ data.tar.gz: 3b884c46b754398e1a058eeb92436fc4e8aa57d090a7a48f4e759486f99add265253bddde82dd5a58bed25ebb953e8586f6be0c88a8fa33f759664f9cfe42345
@@ -0,0 +1 @@
1
+ _misc/
@@ -0,0 +1,7 @@
1
+ language: ruby
2
+ cache: bundler
3
+ sudo: false
4
+ rvm:
5
+ - 2.2.7
6
+ - 2.3.4
7
+ - 2.4.1
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
@@ -0,0 +1,120 @@
1
+ # Auto-seeding [![Gem Version](https://badge.fury.io/rb/auto-seeding.svg)](https://badge.fury.io/rb/auto-seeding) [![Build Status](https://travis-ci.org/blocknotes/auto-seeding.svg)](https://travis-ci.org/blocknotes/auto-seeding)
2
+
3
+ A component to auto generate seed data with ActiveRecord using a set of predefined or custom rules respecting models validations.
4
+
5
+ ## Install
6
+
7
+ - Add to Gemfile: `gem 'auto-seeding'` (better in *development* group)
8
+ - Edit the seed task:
9
+
10
+ ```rb
11
+ auto_seeding = AutoSeeding::Seeder.new
12
+ 3.times.each do
13
+ auto_seeding.update( Author.new ).save!
14
+ end
15
+ ```
16
+
17
+ ### Options
18
+
19
+ - **conf/seeder**: seeding source [*nil* | *:faker* | *:ffaker*] (*:faker* requires Faker gem, *:ffaker* requires FFaker gem)
20
+ - **conf/file**: load seed configuration from a local file
21
+ - **auto_create**: array of nested associations to create while seeding (useful for has_one associations), ex. [*:profile*]
22
+ - **ignore_attrs**: ignore some attributes, ex. [*:id*, *updated_at*]
23
+ - **skip_associations**: array of nested associations to skip while seeding, ex. [*:posts*]
24
+ - **sources**: configure sources rules for autoseed data
25
+
26
+ Conf file: see [data folder](https://github.com/blocknotes/auto-seeding/tree/master/lib/auto-seeding/data)
27
+
28
+ Global options (shared between instances):
29
+
30
+ ```rb
31
+ AutoSeeding::Seeder.config({
32
+ skip_associations: [:versions],
33
+ conf: {
34
+ seeder: :ffaker,
35
+ },
36
+ })
37
+ ```
38
+
39
+ Instance options:
40
+
41
+ ```rb
42
+ autoseeding = AutoSeeding::Seeder.new({
43
+ auto_create: [:profile], # array of symbols
44
+ conf: {
45
+ file: 'test/conf.yml', # string
46
+ seeder: :faker, # symbol - :faker or :ffaker
47
+ },
48
+ ignore_attrs: [:id], # array of symbols - ignored attributes
49
+ skip_associations: [:author], # array of symbols - ignored nested associations
50
+ sources: { # hash - keys: types, fields
51
+ types: { # hash - override basic types rules
52
+ integer: {
53
+ source_model: 'Random',
54
+ source_method: 'rand',
55
+ source_args: '0..100',
56
+ }
57
+ },
58
+ fields: [ # array of hashes - override fields rules
59
+ {
60
+ in: ['name'],
61
+ source_model: 'Faker::StarWars',
62
+ source_method: 'character',
63
+ type: 'string'
64
+ },
65
+ {
66
+ regexp: '^(.+_|)title(|_.+)$',
67
+ source_model: 'Faker::Book',
68
+ source_method: 'title',
69
+ post_process: '->( val ) { val + " (seeding)" }',
70
+ type: 'string'
71
+ }
72
+ ]
73
+ }
74
+ })
75
+ ```
76
+
77
+ ## Notes
78
+
79
+ Generated data can be manipulated easily before saving:
80
+
81
+ ```rb
82
+ obj = auto_seeding.update( Author.new )
83
+ obj.name = 'John Doe'
84
+ obj.save!
85
+ ```
86
+
87
+ Field names can be changed using *append* and *prepend* options - example using Carrierwave remote url property:
88
+
89
+ ```rb
90
+ AutoSeeding::Seeder.new({
91
+ sources: {
92
+ fields: [
93
+ {
94
+ regexp: '^(.+_|)photo(|_.+)$|^(.+_|)image(|_.+)$',
95
+ source_model: 'Faker::Avatar',
96
+ source_method: 'image',
97
+ prepend: 'remote_',
98
+ append: '_url',
99
+ type: 'string'
100
+ }
101
+ ]
102
+ }
103
+ }
104
+ ```
105
+
106
+ To avoid problems with PaperTrail use:
107
+
108
+ `AutoSeeding::Seeder.config({ skip_associations: [:versions] })`
109
+
110
+ ## Do you like it? Star it!
111
+
112
+ If you use this component just star it. A developer is more motivated to improve a project when there is some interest.
113
+
114
+ ## Contributors
115
+
116
+ - [Mattia Roccoberton](http://blocknot.es) - creator, maintainer
117
+
118
+ ## License
119
+
120
+ [MIT](LICENSE.txt)
@@ -0,0 +1,8 @@
1
+ require 'rake/testtask'
2
+
3
+ Rake::TestTask.new do |t|
4
+ t.libs << 'test'
5
+ end
6
+
7
+ desc "Run tests"
8
+ task :default => :test
@@ -0,0 +1,24 @@
1
+ # coding: utf-8
2
+ # frozen_string_literal: true
3
+ lib = File.expand_path( '../lib/', __FILE__ )
4
+ $LOAD_PATH.unshift lib unless $LOAD_PATH.include?( lib )
5
+ require 'auto-seeding'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = AutoSeeding::NAME
9
+ spec.version = AutoSeeding::VERSION.join('.')
10
+ spec.date = AutoSeeding::DATE
11
+ spec.summary = AutoSeeding::INFO
12
+ spec.description = AutoSeeding::DESC
13
+ spec.authors = AutoSeeding::AUTHORS.map { |a| a[0] }.flatten
14
+ spec.email = AutoSeeding::AUTHORS.first[1]
15
+ spec.files = `git ls-files -z`.split("\x0")
16
+ spec.require_paths = ['lib']
17
+ spec.homepage = 'https://github.com/blocknotes/auto-seeding'
18
+ spec.license = 'MIT'
19
+ spec.add_dependency 'activerecord', '~> 5'
20
+ spec.add_development_dependency 'rake', '~> 12'
21
+ spec.add_development_dependency 'minitest', '~> 5'
22
+ spec.add_development_dependency 'faker', '~> 1'
23
+ spec.add_development_dependency 'ffaker', '~> 2'
24
+ end
@@ -7,6 +7,6 @@ module AutoSeeding
7
7
  INFO = 'Auto seeding component for ActiveRecord'.freeze
8
8
  DESC = 'A component to auto generate seed data with ActiveRecord using a set of predefined or custom rules respecting models validations'.freeze
9
9
  AUTHORS = [ [ 'Mattia Roccoberton', 'mat@blocknot.es', 'http://blocknot.es' ] ].freeze
10
- DATE = '2017-09-10'.freeze
11
- VERSION = [ 0, 1, 4 ].freeze
10
+ DATE = '2017-09-30'.freeze
11
+ VERSION = [ 0, 1, 5 ].freeze
12
12
  end
@@ -0,0 +1,47 @@
1
+ sources:
2
+ types:
3
+ boolean:
4
+ source_model: AutoSeeding::Source
5
+ source_method: random_boolean
6
+ date:
7
+ source_model: Date
8
+ source_method: today
9
+ post_process: ->( val ) { val + rand( -30..30 ) }
10
+ datetime:
11
+ source_model: Time
12
+ source_method: now
13
+ post_process: ->( val ) { ( val + rand( -MONTH_SECONDS..MONTH_SECONDS ) ).to_datetime }
14
+ decimal:
15
+ source_model: Random
16
+ source_method: rand
17
+ float:
18
+ source_model: Random
19
+ source_method: rand
20
+ integer:
21
+ source_model: Random
22
+ source_method: rand
23
+ # source_args: 20..25
24
+ string:
25
+ source_model: AutoSeeding::Source
26
+ source_method: random_string
27
+ text:
28
+ source_model: AutoSeeding::Source
29
+ source_method: random_string
30
+ source_args: 50
31
+ time:
32
+ source_model: Time
33
+ source_method: now
34
+ post_process: ->( val ) { val + rand( 0..DAY_SECONDS ) }
35
+ timestamp:
36
+ source_model: Time
37
+ source_method: now
38
+ post_process: ->( val ) { ( val + rand( -MONTH_SECONDS..MONTH_SECONDS ) ).to_datetime }
39
+ fields:
40
+ -
41
+ in:
42
+ - name
43
+ - title
44
+ source_model: AutoSeeding::Source
45
+ source_method: random_string
46
+ source_args: 5
47
+ type: string
@@ -0,0 +1,75 @@
1
+ sources:
2
+ types:
3
+ boolean:
4
+ source_model: AutoSeeding::Source
5
+ source_method: random_boolean
6
+ date:
7
+ source_model: Date
8
+ source_method: today
9
+ post_process: ->( val ) { val + rand( -30..30 ) }
10
+ datetime:
11
+ source_model: Time
12
+ source_method: now
13
+ post_process: ->( val ) { ( val + rand( -MONTH_SECONDS..MONTH_SECONDS ) ).to_datetime }
14
+ decimal:
15
+ source_model: Random
16
+ source_method: rand
17
+ float:
18
+ source_model: Random
19
+ source_method: rand
20
+ integer:
21
+ source_model: Random
22
+ source_method: rand
23
+ string:
24
+ source_model: Faker::Hipster
25
+ source_method: sentence
26
+ text:
27
+ source_model: Faker::Hipster
28
+ source_method: paragraph
29
+ time:
30
+ source_model: Time
31
+ source_method: now
32
+ post_process: ->( val ) { val + rand( 0..DAY_SECONDS ) }
33
+ timestamp:
34
+ source_model: Time
35
+ source_method: now
36
+ post_process: ->( val ) { ( val + rand( -MONTH_SECONDS..MONTH_SECONDS ) ).to_datetime }
37
+ fields:
38
+ # -
39
+ # in:
40
+ # - name
41
+ # - title
42
+ # source_model: Faker::Hipster
43
+ # source_method: word
44
+ # type: string
45
+ -
46
+ regexp: ^(.+_|)email(|_.+)$
47
+ source_model: Faker::Internet
48
+ source_method: safe_email
49
+ type: string
50
+ -
51
+ regexp: ^(.+_|)name(|_.+)$
52
+ source_model: Faker::Hipster
53
+ source_method: word
54
+ type: string
55
+ -
56
+ regexp: ^(.+_|)title(|_.+)$
57
+ source_model: Faker::Book
58
+ source_method: genre
59
+ type: string
60
+ -
61
+ regexp: ^(.+_|)url(|_.+)$
62
+ source_model: Faker::Internet
63
+ source_method: url
64
+ source_args: example.com
65
+ type: string
66
+ # source_args:
67
+ # - example.com
68
+ # - /foobar.html
69
+ # Models
70
+ -
71
+ model: ^(.+_|)admin(|_.+)$|^(.+_|)author(|_.+)$|^(.+_|)user(|_.+)$
72
+ regexp: ^(.+_|)name(|_.+)$
73
+ source_model: Faker::Book
74
+ source_method: author
75
+ type: string
@@ -0,0 +1,69 @@
1
+ sources:
2
+ types:
3
+ boolean:
4
+ source_model: FFaker::Boolean
5
+ source_method: random
6
+ date:
7
+ source_model: Date
8
+ source_method: today
9
+ post_process: ->( val ) { val + rand( -30..30 ) }
10
+ datetime:
11
+ source_model: Time
12
+ source_method: now
13
+ post_process: ->( val ) { ( val + rand( -MONTH_SECONDS..MONTH_SECONDS ) ).to_datetime }
14
+ decimal:
15
+ source_model: Random
16
+ source_method: rand
17
+ float:
18
+ source_model: Random
19
+ source_method: rand
20
+ integer:
21
+ source_model: Random
22
+ source_method: rand
23
+ string:
24
+ source_model: FFaker::Book
25
+ source_method: genre
26
+ text:
27
+ source_model: FFaker::Book
28
+ source_method: description
29
+ time:
30
+ source_model: Time
31
+ source_method: now
32
+ post_process: ->( val ) { val + rand( 0..DAY_SECONDS ) }
33
+ timestamp:
34
+ source_model: Time
35
+ source_method: now
36
+ post_process: ->( val ) { ( val + rand( -MONTH_SECONDS..MONTH_SECONDS ) ).to_datetime }
37
+ fields:
38
+ # -
39
+ # in:
40
+ # - title
41
+ # source_model: FFaker::Book
42
+ # source_method: title
43
+ # type: string
44
+ -
45
+ regexp: ^(.+_|)email(|_.+)$
46
+ source_model: FFaker::Internet
47
+ source_method: safe_email
48
+ type: string
49
+ -
50
+ regexp: ^(.+_|)name(|_.+)$
51
+ source_model: FFaker::Movie
52
+ source_method: title
53
+ type: string
54
+ -
55
+ regexp: ^(.+_|)title(|_.+)$
56
+ source_model: FFaker::Book
57
+ source_method: title
58
+ type: string
59
+ -
60
+ regexp: ^(.+_|)url(|_.+)$
61
+ source_model: FFaker::Internet
62
+ source_method: http_url
63
+ type: string
64
+ -
65
+ model: ^(.+_|)admin(|_.+)$|^(.+_|)author(|_.+)$|^(.+_|)user(|_.+)$
66
+ regexp: ^(.+_|)name(|_.+)$
67
+ source_model: FFaker::Name
68
+ source_method: name
69
+ type: string
@@ -0,0 +1,171 @@
1
+ # coding: utf-8
2
+ module AutoSeeding
3
+ class Seeder
4
+ TYPES = [:boolean, :date, :datetime, :float, :decimal, :integer, :string, :text, :time, :timestamp]
5
+
6
+ attr_reader :options, :sources
7
+
8
+ @@globals = { conf: {}, sources: {} }
9
+
10
+ def initialize( opts = {} )
11
+ options = { conf: @@globals[:conf], sources: @@globals[:sources] }
12
+ AutoSeeding::_deep_merge!( options, opts )
13
+
14
+ @columns = {}
15
+ @models = {}
16
+ @extra_validations = { confirmations: [] }
17
+ options[:ignore_attrs] ||= [:id, :created_at, :updated_at]
18
+ options[:ignore_attrs] += @@globals[:ignore_attrs] if @@globals[:ignore_attrs]
19
+ options[:skip_associations] ||= []
20
+ options[:skip_associations] += @@globals[:skip_associations] if @@globals[:skip_associations]
21
+
22
+ path = options[:conf][:file]
23
+ if path
24
+ options[:conf].delete :seeder
25
+ else
26
+ yml_file = if options[:conf][:seeder] == :ffaker
27
+ puts 'warning: seeder set to ffaker but FFaker is not available' unless defined? FFaker
28
+ 'ffaker.yml'
29
+ elsif options[:conf][:seeder] == :faker
30
+ puts 'warning: seeder set to faker but Faker is not available' unless defined? Faker
31
+ 'faker.yml'
32
+ else
33
+ 'basic.yml'
34
+ end
35
+ path = Pathname.new( File.dirname __FILE__ ).join( 'data', yml_file ).to_s
36
+ end
37
+
38
+ # Random.srand( options[:conf][:seed_number] ? options[:conf][:seed_number].to_i : Random.new_seed ) # NOTE: problems here
39
+
40
+ yml = Seeder::symbolize_keys YAML.load_file( path )
41
+ # @sources = yml[:sources].merge( options[:sources] ? options[:sources] : {} )
42
+ @sources = yml[:sources].dup
43
+ AutoSeeding::_deep_merge!( @sources, options[:sources] ) if options[:sources]
44
+ @sources[:fields] ||= {}
45
+ @sources[:fields].map! { |s| Seeder::symbolize_keys s }
46
+ @sources[:fields].sort! { |a, b| ( a['model'] || a[:model] ) ? -1 : ( ( b['model'] || b[:model] ) ? 1 : 0 ) }
47
+ @options = options
48
+
49
+ self
50
+ end
51
+
52
+ def update( object )
53
+ model = object.class
54
+
55
+ model.content_columns.each do |column|
56
+ col = column.name.to_sym
57
+ next if @options[:ignore_attrs].include? col
58
+ @columns[col] ||= {
59
+ validators: prepare_validators( model._validators[col] )
60
+ }
61
+
62
+ found = false
63
+ @sources[:fields].each do |f|
64
+ if f[:model]
65
+ next unless Regexp.new( f[:model], Regexp::IGNORECASE ).match( model.to_s )
66
+ end
67
+ if( f[:in] && f[:in].include?( col.to_s ) ) ||
68
+ ( f[:regexp] && Regexp.new( f[:regexp], Regexp::IGNORECASE ).match( col.to_s ) )
69
+ col_ = ( f[:prepend] || f[:append] ) ? ( f[:prepend].to_s + col.to_s + f[:append].to_s ) : col.to_s
70
+ @columns[col][:src] ||= Source.new( col, f[:type] ? f[:type].to_sym : :string, @columns[col][:validators], f )
71
+ object.send( col_ + '=', @columns[col][:src].gen )
72
+ found = true
73
+ break
74
+ end
75
+ end
76
+ next if found
77
+
78
+ if TYPES.include? column.type
79
+ object.send( col.to_s + '=', ( @columns[col][:src] ||= Source.new( col, column.type, @columns[col][:validators], @sources[:types][column.type] ) ).gen )
80
+ end
81
+ end
82
+
83
+ # Setup associations
84
+ model._reflections.each do |association, data|
85
+ next if @options[:skip_associations].include? association.to_sym
86
+ model2 = data.klass
87
+ if @options[:auto_create] && @options[:auto_create].include?( association.to_sym )
88
+ auto_seeding = AutoSeeding::Seeder.new( { conf: { seeder: @options[:seeder] || @@globals[:conf][:seeder] }, skip_associations: [model.to_s.underscore.to_sym] } )
89
+ object.send( association + '=', auto_seeding.update( model2.new ) )
90
+ else
91
+ @models[model2.table_name] ||= model2.all
92
+ if data.is_a?( ActiveRecord::Reflection::ThroughReflection ) # many-to-many
93
+ sam = @models[model2.table_name].sample( rand( 5 ) )
94
+ object.send( association ).push( sam ) if sam.any?
95
+ elsif data.parent_reflection && data.parent_reflection.is_a?( ActiveRecord::Reflection::HasAndBelongsToManyReflection )
96
+ next
97
+ else
98
+ sam = @models[model2.table_name].sample
99
+ object.send( association + '=', sam ) if sam
100
+ end
101
+ end
102
+ end
103
+
104
+ # Extra validations
105
+ @extra_validations[:confirmations].each do |field|
106
+ object.send field.to_s+'_confirmation=', object[field]
107
+ end
108
+
109
+ object
110
+ end
111
+
112
+ def self.config( options = nil )
113
+ @@globals = options ? options : { conf: {}, sources: {} }
114
+ end
115
+
116
+ protected
117
+
118
+ def prepare_validators( validators )
119
+ ret = {}
120
+ validators.each do |validator|
121
+ case validator.class.name.split( '::' ).last
122
+ when 'AcceptanceValidator'
123
+ ret[:accept] = validator.options[:accept].is_a?( Array ) ? validator.options[:accept] : [ validator.options[:accept] ]
124
+ when 'ConfirmationValidator'
125
+ # ret[:confirmation] = validator.options[:confirmation]
126
+ @extra_validations[:confirmations] += validator.attributes
127
+ # case_sensitive - TODO: not implemented
128
+ when 'ExclusionValidator'
129
+ ret[:not_in] = validator.options[:in] # TODO: not implemented
130
+ when 'InclusionValidator'
131
+ ret[:in] = validator.options[:in]
132
+ when 'LengthValidator'
133
+ if validator.options[:is]
134
+ ret[:length_minimum] = validator.options[:is]
135
+ ret[:length_maximum] = validator.options[:is]
136
+ else
137
+ ret[:length_minimum] = validator.options[:minimum]
138
+ ret[:length_maximum] = validator.options[:maximum]
139
+ end
140
+ when 'NumericalityValidator'
141
+ ret[:num_gt] = validator.options[:greater_than]
142
+ ret[:num_gte] = validator.options[:greater_than_or_equal_to]
143
+ ret[:equal_to] = validator.options[:equal_to]
144
+ ret[:num_lt] = validator.options[:less_than]
145
+ ret[:num_lte] = validator.options[:less_than_or_equal_to]
146
+ # ret[:other_than] = validator.options[:other_than] # TODO: not implemented
147
+ # ret[:odd] = validator.options[:odd] # TODO: not implemented
148
+ # ret[:even] = validator.options[:even] # TODO: not implemented
149
+ when 'PresenceValidator'
150
+ # ret[:presence] = true # TODO: not implemented
151
+ when 'UniquenessValidator'
152
+ ret[:uniqueness] = true
153
+ else
154
+ # p validator.class.name.split( '::' ).last # DEBUG
155
+ end
156
+ end
157
+ ret
158
+ end
159
+
160
+ def self.symbolize_keys( obj )
161
+ if obj.is_a?( Hash )
162
+ obj2 ||= {}
163
+ obj.each do |k, v|
164
+ obj2[k.to_sym] = symbolize_keys( v )
165
+ end
166
+ return obj2
167
+ end
168
+ obj
169
+ end
170
+ end
171
+ end
@@ -0,0 +1,114 @@
1
+ # coding: utf-8
2
+ module AutoSeeding
3
+ class Source
4
+ DAY_SECONDS = ( 60 * 60 * 24 )
5
+ MIN_INT = 0
6
+ MAX_INT = 1_000_000
7
+ MIN_FLOAT = 0.0
8
+ MAX_FLOAT = 1000.0
9
+ MONTH_SECONDS = ( DAY_SECONDS * 30 ).freeze
10
+ VOWELS = %w(a e i o u).freeze
11
+ CONSONANTS = (('a'..'z').to_a - VOWELS).freeze
12
+
13
+ def initialize( column, type, rules, options = {} )
14
+ @column = column
15
+ @type = type
16
+ @rules = rules ? rules : {}
17
+ @options = options || {}
18
+ if @options[:source_model] && @options[:source_method]
19
+ @source_class = Object.const_get @options[:source_model]
20
+ @source_method = @options[:source_method].to_sym
21
+ @source_args = @options[:source_args]
22
+ else
23
+ @source_class = AutoSeeding::Source
24
+ @source_method = :random_string
25
+ @source_args = nil
26
+ end
27
+ @uniqueness = {}
28
+ self
29
+ end
30
+
31
+ def gen
32
+ @retry = 100
33
+ process @type
34
+ end
35
+
36
+ def self.random_boolean
37
+ [false, true].sample
38
+ end
39
+
40
+ def self.random_string( words = 10 )
41
+ (1..rand(words)+1).map do
42
+ (0..rand(10)+1).map do |i|
43
+ i % 2 == 0 ? CONSONANTS.sample : VOWELS.sample
44
+ end.join
45
+ end.join( ' ' ).capitalize
46
+ end
47
+
48
+ protected
49
+
50
+ def process( type = nil )
51
+ value =
52
+ if @rules[:equal_to]
53
+ @rules[:equal_to]
54
+ elsif @rules[:in]
55
+ @rules[:in].sample
56
+ elsif @rules[:accept]
57
+ @rules[:accept].sample
58
+ else
59
+ case type
60
+ when :float, :decimal
61
+ min = @rules[:num_gte] ? @rules[:num_gte].to_f : ( @rules[:num_gt] ? ( @rules[:num_gt].to_f + 0.1 ) : MIN_FLOAT )
62
+ max = @rules[:num_lte] ? @rules[:num_lte].to_f : ( @rules[:num_lt] ? ( @rules[:num_lt].to_f - 0.1 ) : MAX_FLOAT )
63
+ @source_class.send( @source_method, @source_args ? eval( @source_args ) : (min .. max) )
64
+ when :integer
65
+ min = @rules[:num_gte] ? @rules[:num_gte].to_i : ( @rules[:num_gt] ? ( @rules[:num_gt].to_i + 1 ) : MIN_INT )
66
+ max = @rules[:num_lte] ? @rules[:num_lte].to_i : ( @rules[:num_lt] ? ( @rules[:num_lt].to_i - 1 ) : MAX_INT )
67
+ @source_class.send( @source_method, @source_args ? eval( @source_args ) : (min .. max) )
68
+ when :string, :text
69
+ @source_class.send( @source_method, *@source_args ).to_s
70
+ else
71
+ @source_class.send( @source_method, *@source_args )
72
+ end
73
+ end
74
+
75
+ if @options[:post_process]
76
+ post_process = eval @options[:post_process]
77
+ value = post_process.call( value )
78
+ end
79
+
80
+ # validations
81
+ case type
82
+ when :float, :decimal
83
+ value = ( @rules[:num_gt].to_f + 0.1 ) if @rules[:num_gt] && ( value <= @rules[:num_gt].to_f )
84
+ value = @rules[:num_gte].to_f if @rules[:num_gte] && ( value < @rules[:num_gte].to_f )
85
+ value = ( @rules[:num_lt].to_f - 0.1 ) if @rules[:num_lt] && ( value >= @rules[:num_lt].to_f )
86
+ value = @rules[:num_lte].to_f if @rules[:num_lte] && ( value > @rules[:num_lte].to_f )
87
+ when :integer
88
+ value = ( @rules[:num_gt].to_i + 1 ) if @rules[:num_gt] && ( value <= @rules[:num_gt].to_i )
89
+ value = @rules[:num_gte].to_i if @rules[:num_gte] && ( value < @rules[:num_gte].to_i )
90
+ value = ( @rules[:num_lt].to_i - 1 ) if @rules[:num_lt] && ( value >= @rules[:num_lt].to_i )
91
+ value = @rules[:num_lte].to_i if @rules[:num_lte] && ( value > @rules[:num_lte].to_i )
92
+ when :string, :text
93
+ value = value.ljust( @rules[:length_minimum], '-' ) if @rules[:length_minimum]
94
+ value = value.slice( 0..( @rules[:length_maximum] - 1 ) ) if @rules[:length_maximum]
95
+ end
96
+
97
+ if @rules[:not_in] && @rules[:not_in].include?( value )
98
+ @retry -= 1
99
+ return @retry > 0 ? process( type ) : raise( Exception.new( 'Reserved value' ) )
100
+ end
101
+
102
+ if @rules[:uniqueness]
103
+ @uniqueness[@column] ||= {}
104
+ if @uniqueness[@column].has_key?( value )
105
+ return ( @retry -= 1 ) > 0 ? process( type ) : raise( Exception.new( 'Value already taken' ) )
106
+ else
107
+ @uniqueness[@column][value] = true
108
+ end
109
+ end
110
+
111
+ value
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,25 @@
1
+ module AutoSeeding
2
+ # From activesupport
3
+ def self._deep_merge( hash, other_hash, &block )
4
+ _deep_merge!( hash.dup, other_hash, &block )
5
+ end
6
+
7
+ # From activesupport
8
+ def self._deep_merge!( hash, other_hash, &block )
9
+ other_hash.each_pair do |current_key, other_value|
10
+ this_value = hash[current_key]
11
+
12
+ hash[current_key] = if this_value.is_a?(Hash) && other_value.is_a?(Hash)
13
+ _deep_merge( this_value, other_value, &block )
14
+ else
15
+ if block_given? && key?(current_key)
16
+ block.call(current_key, this_value, other_value)
17
+ else
18
+ other_value
19
+ end
20
+ end
21
+ end
22
+
23
+ hash
24
+ end
25
+ end
@@ -0,0 +1,8 @@
1
+ sources:
2
+ fields:
3
+ -
4
+ regexp: ^(.+_|)name(|_.+)$
5
+ source_model: AutoSeeding::Source
6
+ source_method: random_string
7
+ source_args: 1 # test field
8
+ type: string
@@ -0,0 +1,63 @@
1
+ # require 'pry'
2
+ require 'minitest/autorun'
3
+ require 'ostruct'
4
+ require 'pathname'
5
+ require 'yaml'
6
+ require_relative '../lib/auto-seeding'
7
+
8
+ # class TestObject
9
+ # # def initialize
10
+ # # super
11
+ # # end
12
+
13
+ # def self.content_columns
14
+ # [OpenStruct.new({
15
+ # name: 'test',
16
+ # })]
17
+ # end
18
+
19
+ # def self._reflections
20
+ # []
21
+ # end
22
+
23
+ # def self._validators
24
+ # {
25
+ # test: []
26
+ # }
27
+ # end
28
+ # end
29
+
30
+ describe 'AutoSeeding basic' do
31
+ before do
32
+ @auto_seeding = AutoSeeding::Seeder.new
33
+ end
34
+
35
+ describe 'When generating a value of any type' do
36
+ it 'must return a correct value' do
37
+ @auto_seeding.sources[:types].each do |type, data|
38
+ value = AutoSeeding::Source.new( :test1, type, nil, data ).gen
39
+ case type
40
+ when :boolean
41
+ assert( value.is_a?( FalseClass ) || value.is_a?( TrueClass ) )
42
+ when :date
43
+ assert_instance_of( Date, value )
44
+ when :datetime, :timestamp
45
+ assert_instance_of( DateTime, value )
46
+ when :float, :decimal
47
+ assert_instance_of( Float, value )
48
+ when :integer
49
+ assert_instance_of( Fixnum, value )
50
+ when :string, :text
51
+ assert_instance_of( String, value )
52
+ v = value.strip
53
+ assert( v.length > 0 )
54
+ assert( !v.empty? )
55
+ when :time
56
+ assert_instance_of( Time, value )
57
+ else
58
+ assert( false, "Invalid type: #{type}" )
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,45 @@
1
+ # require 'pry'
2
+ require 'minitest/autorun'
3
+ require 'faker'
4
+ require 'ostruct'
5
+ require 'pathname'
6
+ require 'yaml'
7
+ require_relative '../lib/auto-seeding'
8
+
9
+ describe 'AutoSeeding using Faker' do
10
+ before do
11
+ AutoSeeding::Seeder.config # reset global options
12
+ @test_faker = AutoSeeding::Seeder.new({
13
+ seeder: :faker
14
+ })
15
+ end
16
+
17
+ describe 'When generating a value of any type' do
18
+ it 'must return a correct value' do
19
+ @test_faker.sources[:types].each do |type, data|
20
+ value = AutoSeeding::Source.new( :test1, type, nil, data ).gen
21
+ case type
22
+ when :boolean
23
+ assert( value.is_a?( FalseClass ) || value.is_a?( TrueClass ) )
24
+ when :date
25
+ assert_instance_of( Date, value )
26
+ when :datetime, :timestamp
27
+ assert_instance_of( DateTime, value )
28
+ when :float, :decimal
29
+ assert_instance_of( Float, value )
30
+ when :integer
31
+ assert_instance_of( Fixnum, value )
32
+ when :string, :text
33
+ assert_instance_of( String, value )
34
+ v = value.strip
35
+ assert( v.length > 0 )
36
+ assert( !v.empty? )
37
+ when :time
38
+ assert_instance_of( Time, value )
39
+ else
40
+ assert( false, "Invalid type: #{type}" )
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,45 @@
1
+ # require 'pry'
2
+ require 'minitest/autorun'
3
+ require 'ffaker'
4
+ require 'ostruct'
5
+ require 'pathname'
6
+ require 'yaml'
7
+ require_relative '../lib/auto-seeding'
8
+
9
+ describe 'AutoSeeding using FFaker' do
10
+ before do
11
+ AutoSeeding::Seeder.config # reset global options
12
+ @test_ffaker = AutoSeeding::Seeder.new({
13
+ seeder: :ffaker
14
+ })
15
+ end
16
+
17
+ describe 'When generating a value of any type' do
18
+ it 'must return a correct value' do
19
+ @test_ffaker.sources[:types].each do |type, data|
20
+ value = AutoSeeding::Source.new( :test1, type, nil, data ).gen
21
+ case type
22
+ when :boolean
23
+ assert( value.is_a?( FalseClass ) || value.is_a?( TrueClass ) )
24
+ when :date
25
+ assert_instance_of( Date, value )
26
+ when :datetime, :timestamp
27
+ assert_instance_of( DateTime, value )
28
+ when :float, :decimal
29
+ assert_instance_of( Float, value )
30
+ when :integer
31
+ assert_instance_of( Fixnum, value )
32
+ when :string, :text
33
+ assert_instance_of( String, value )
34
+ v = value.strip
35
+ assert( v.length > 0 )
36
+ assert( !v.empty? )
37
+ when :time
38
+ assert_instance_of( Time, value )
39
+ else
40
+ assert( false, "Invalid type: #{type}" )
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,27 @@
1
+ # require 'pry'
2
+ require 'minitest/autorun'
3
+ require 'ostruct'
4
+ require 'pathname'
5
+ require 'yaml'
6
+ require 'ffaker'
7
+ require_relative '../lib/auto-seeding'
8
+
9
+ describe 'AutoSeeding global config' do
10
+ before do
11
+ AutoSeeding::Seeder.config({
12
+ skip_associations: [:versions],
13
+ conf: {
14
+ seeder: :ffaker,
15
+ },
16
+ })
17
+ end
18
+
19
+ describe 'When using config method' do
20
+ it 'must merge the internal options' do
21
+ auto_seeding = AutoSeeding::Seeder.new
22
+ assert_equal( auto_seeding.options[:conf][:seeder], :ffaker )
23
+ auto_seeding2 = AutoSeeding::Seeder.new
24
+ assert_equal( auto_seeding2.options[:conf][:seeder], :ffaker )
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,49 @@
1
+ # require 'pry'
2
+ require 'minitest/autorun'
3
+ require 'ostruct'
4
+ require 'pathname'
5
+ require 'yaml'
6
+ require_relative '../lib/auto-seeding'
7
+
8
+ describe 'AutoSeeding Options' do
9
+ before do
10
+ AutoSeeding::Seeder.config # reset global options
11
+ @test_options = AutoSeeding::Seeder.new({
12
+ auto_create: [:profile], # array of symbols - nested associations to create while seeding
13
+ conf: {
14
+ file: 'test/conf.yml', # string - local seed configuration file
15
+ seeder: :faker, # symbol - :faker or :ffaker
16
+ },
17
+ ignore_attrs: [:id], # array of symbols - ignored attributes
18
+ skip_associations: [:author], # array of symbols - ignored nested associations
19
+ sources: { # hash - keys: types, fields
20
+ types: { # hash - override basic types rules
21
+ integer: {
22
+ source_model: Random,
23
+ source_method: 'rand',
24
+ source_args: 0..100,
25
+ }
26
+ },
27
+ # fields: [ # array of hashes - overried fields rules
28
+ # {
29
+ # in: ['name'],
30
+ # source_model: 'Faker::Hipster',
31
+ # source_method: 'word',
32
+ # type: 'string'
33
+ # }
34
+ # ]
35
+ }
36
+ })
37
+ end
38
+
39
+ describe 'When using constructor options hash' do
40
+ it 'must merge the internal options' do
41
+ assert_nil( @test_options.options[:conf][:seeder] ) # overriden by file option
42
+ assert_equal( @test_options.options[:auto_create], [:profile] )
43
+ assert_equal( @test_options.options[:ignore_attrs], [:id] )
44
+ assert_equal( @test_options.options[:skip_associations], [:author] )
45
+ assert_equal( @test_options.sources[:fields][0][:source_args], 1 )
46
+ assert_equal( @test_options.sources[:types].keys, [:integer] )
47
+ end
48
+ end
49
+ end
@@ -0,0 +1,167 @@
1
+ # require 'pry'
2
+ require 'minitest/autorun'
3
+ require 'active_record'
4
+ require 'ostruct'
5
+ require 'pathname'
6
+ require 'yaml'
7
+ require_relative '../lib/auto-seeding'
8
+
9
+ AutoSeeding::Seeder.class_eval do
10
+ attr_reader :columns, :extra_validations
11
+ end
12
+
13
+ class TestObject # < ActiveRecord::Base
14
+ include ActiveRecord::Validations
15
+
16
+ FIELDS = {
17
+ privacy: :boolean,
18
+ privacy2: :boolean,
19
+ email: :string,
20
+ email2: :string,
21
+ letter: :string,
22
+ letter2: :string,
23
+ title: :string,
24
+ title2: :text,
25
+ title3: :text,
26
+ title4: :text,
27
+ title5: :text,
28
+ integer: :float,
29
+ number: :integer,
30
+ number2: :float,
31
+ number3: :decimal,
32
+ number4: :integer,
33
+ unique: :string,
34
+ unique2: :integer
35
+ }.freeze
36
+ FIELDS_EXTRA = [:email_confirmation, :email_confirmation2].freeze
37
+ FIELDS_SET = ( FIELDS.keys + FIELDS_EXTRA ).map { |field| ( field.to_s + '=' ).to_sym }.freeze
38
+
39
+ validates :privacy, acceptance: true
40
+ validates :privacy2, acceptance: { accept: true }
41
+ validates :email, confirmation: true
42
+ validates :email_confirmation, presence: true
43
+ validates :email2, confirmation: { case_sensitive: false }
44
+ validates :letter, exclusion: { in: %w(a b c) }, length: { is: 1 }
45
+ validates :letter2, inclusion: { in: %w(a b c) }, length: { is: 1 }
46
+ validates :title, length: { in: 15..20 }
47
+ validates :title2, length: { within: 15..20 }
48
+ validates :title3, length: { minimum: 15 }
49
+ validates :title4, length: { maximum: 20 }
50
+ validates :title5, length: { is: 18 }
51
+ validates :integer, numericality: { only_integer: true }
52
+ validates :number, numericality: true
53
+ validates :number2, numericality: { greater_than: 15, less_than_or_equal_to: 20 }
54
+ validates :number3, numericality: { greater_than_or_equal_to: 15, less_than: 20 }
55
+ validates :number4, numericality: { equal_to: 18 }
56
+ validates :unique, uniqueness: true
57
+ validates :unique2, uniqueness: { scope: :email }
58
+
59
+ def initialize
60
+ @data = {}
61
+ end
62
+
63
+ def []( field )
64
+ @data[field.to_sym]
65
+ end
66
+
67
+ def self.content_columns
68
+ FIELDS.map do |field, type|
69
+ OpenStruct.new( { name: field.to_s, type: type } )
70
+ end
71
+ end
72
+
73
+ def self._reflections
74
+ []
75
+ end
76
+
77
+ protected
78
+
79
+ def method_missing( method, *args, &block )
80
+ if FIELDS.keys.include?( method ) || FIELDS_EXTRA.include?( method )
81
+ self[method]
82
+ elsif FIELDS_SET.include?( method )
83
+ # p method
84
+ @data[method.to_s.chop.to_sym] = args[0]
85
+ else
86
+ # p method
87
+ super
88
+ end
89
+ end
90
+
91
+ def respond_to?( method, include_private = false )
92
+ FIELDS.keys.include?( method ) || FIELDS_EXTRA.include?( method ) || FIELDS_SET.include?( method ) || super
93
+ end
94
+ end
95
+
96
+ describe 'Check validators' do
97
+ before do
98
+ AutoSeeding::Seeder.config # reset global options
99
+ @test_validators = AutoSeeding::Seeder.new
100
+ end
101
+
102
+ describe 'When update method is called' do
103
+ it 'must set the validators options' do
104
+ @test_validators.update( TestObject.new )
105
+ cols = @test_validators.columns
106
+
107
+ ## Validation Helpers
108
+ # --- acceptance --------------------------------------------------------
109
+ assert_equal cols[:privacy][:validators][:accept], ['1', true]
110
+ assert_equal cols[:privacy2][:validators][:accept], [true]
111
+ # --- confirmation ------------------------------------------------------
112
+ assert_equal @test_validators.extra_validations[:confirmations], [:email, :email2]
113
+ # --- exclusion ---------------------------------------------------------
114
+ assert_equal cols[:letter][:validators][:not_in], ['a', 'b', 'c']
115
+ # --- format ------------------------------------------------------------
116
+ # TODO: not implemented
117
+ # --- inclusion ---------------------------------------------------------
118
+ assert_equal cols[:letter2][:validators][:in], ['a', 'b', 'c']
119
+ # --- length ------------------------------------------------------------
120
+ assert_equal cols[:title][:validators][:length_minimum], 15
121
+ assert_equal cols[:title][:validators][:length_maximum], 20
122
+ assert_equal cols[:title2][:validators][:length_minimum], 15
123
+ assert_equal cols[:title2][:validators][:length_maximum], 20
124
+ assert_equal cols[:title3][:validators][:length_minimum], 15
125
+ assert_equal cols[:title4][:validators][:length_maximum], 20
126
+ assert_equal cols[:title5][:validators][:length_minimum], 18
127
+ assert_equal cols[:title5][:validators][:length_maximum], 18
128
+ # --- numericality ------------------------------------------------------
129
+ # cols[:number] # -> set type to integer ?
130
+ # cols[:integer] # -> set type to integer ?
131
+ assert_equal cols[:number2][:validators][:num_gt], 15
132
+ assert_equal cols[:number2][:validators][:num_lte], 20
133
+ assert_equal cols[:number3][:validators][:num_gte], 15
134
+ assert_equal cols[:number3][:validators][:num_lt], 20
135
+ assert_equal cols[:number4][:validators][:equal_to], 18
136
+ # --- presence ----------------------------------------------------------
137
+ # TODO: not implemented
138
+ # --- absence -----------------------------------------------------------
139
+ # TODO: not implemented
140
+ # --- uniqueness --------------------------------------------------------
141
+ assert cols[:unique][:validators][:uniqueness]
142
+ assert cols[:unique2][:validators][:uniqueness]
143
+ # --- validates_with ----------------------------------------------------
144
+ # TODO: not implemented
145
+ # --- validates_each ----------------------------------------------------
146
+ # TODO: not implemented
147
+ end
148
+
149
+ it 'must generate values respecting validations rules' do
150
+ obj = @test_validators.update( TestObject.new )
151
+ assert obj.privacy == '1' || obj.privacy == true
152
+ assert obj.privacy2
153
+ assert obj.email == obj.email_confirmation
154
+ assert obj.email2 == obj.email2_confirmation
155
+ assert !( ['a', 'b', 'c'].include?( obj.letter ) )
156
+ assert ['a', 'b', 'c'].include?( obj.letter2 )
157
+ assert obj.title.length >= 15 && obj.title.length <= 20
158
+ assert obj.title2.length >= 15 && obj.title2.length <= 20
159
+ assert obj.title3.length >= 15
160
+ assert obj.title4.length <= 20
161
+ assert obj.title5.length == 18
162
+ assert obj.number2 > 15 && obj.number2 <= 20
163
+ assert obj.number3 >= 15 && obj.number3 < 20
164
+ assert obj.number4 == 18
165
+ end
166
+ end
167
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: auto-seeding
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.4
4
+ version: 0.1.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mattia Roccoberton
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2017-09-10 00:00:00.000000000 Z
11
+ date: 2017-09-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -87,7 +87,26 @@ executables: []
87
87
  extensions: []
88
88
  extra_rdoc_files: []
89
89
  files:
90
+ - ".gitignore"
91
+ - ".travis.yml"
92
+ - Gemfile
93
+ - README.md
94
+ - Rakefile
95
+ - auto-seeding.gemspec
90
96
  - lib/auto-seeding.rb
97
+ - lib/auto-seeding/data/basic.yml
98
+ - lib/auto-seeding/data/faker.yml
99
+ - lib/auto-seeding/data/ffaker.yml
100
+ - lib/auto-seeding/seeder.rb
101
+ - lib/auto-seeding/source.rb
102
+ - lib/auto-seeding/utils.rb
103
+ - test/conf.yml
104
+ - test/test_basic.rb
105
+ - test/test_faker.rb
106
+ - test/test_ffaker.rb
107
+ - test/test_globals.rb
108
+ - test/test_options.rb
109
+ - test/test_validators.rb
91
110
  homepage: https://github.com/blocknotes/auto-seeding
92
111
  licenses:
93
112
  - MIT