auto-seeding 0.1.4 → 0.1.5

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.
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