dirty_seed 0.1.4
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +44 -0
- data/Rakefile +22 -0
- data/lib/dirty_seed.rb +23 -0
- data/lib/dirty_seed/assigners/dirty_assigner.rb +32 -0
- data/lib/dirty_seed/assigners/dirty_boolean.rb +14 -0
- data/lib/dirty_seed/assigners/dirty_date.rb +14 -0
- data/lib/dirty_seed/assigners/dirty_float.rb +28 -0
- data/lib/dirty_seed/assigners/dirty_integer.rb +86 -0
- data/lib/dirty_seed/assigners/dirty_string.rb +49 -0
- data/lib/dirty_seed/assigners/dirty_time.rb +14 -0
- data/lib/dirty_seed/data_model.rb +80 -0
- data/lib/dirty_seed/dirty_association.rb +106 -0
- data/lib/dirty_seed/dirty_attribute.rb +64 -0
- data/lib/dirty_seed/dirty_model.rb +123 -0
- data/lib/dirty_seed/engine.rb +11 -0
- data/lib/dirty_seed/exceptions.rb +6 -0
- data/lib/dirty_seed/method_missing_helper.rb +49 -0
- data/lib/dirty_seed/sorter.rb +118 -0
- data/lib/dirty_seed/version.rb +6 -0
- data/lib/tasks/dirty_seed_tasks.rake +8 -0
- data/spec/dummy/Rakefile +6 -0
- data/spec/dummy/app/assets/config/manifest.js +3 -0
- data/spec/dummy/app/assets/stylesheets/application.css +15 -0
- data/spec/dummy/app/channels/application_cable/channel.rb +4 -0
- data/spec/dummy/app/channels/application_cable/connection.rb +4 -0
- data/spec/dummy/app/controllers/application_controller.rb +2 -0
- data/spec/dummy/app/helpers/application_helper.rb +2 -0
- data/spec/dummy/app/javascript/packs/application.js +15 -0
- data/spec/dummy/app/jobs/application_job.rb +7 -0
- data/spec/dummy/app/mailers/application_mailer.rb +4 -0
- data/spec/dummy/app/models/alfa.rb +5 -0
- data/spec/dummy/app/models/application_record.rb +3 -0
- data/spec/dummy/app/models/bravo.rb +3 -0
- data/spec/dummy/app/models/charlie.rb +5 -0
- data/spec/dummy/app/models/delta.rb +4 -0
- data/spec/dummy/app/models/echo.rb +3 -0
- data/spec/dummy/app/models/foxtrot.rb +2 -0
- data/spec/dummy/app/models/golf.rb +2 -0
- data/spec/dummy/app/models/hotel.rb +4 -0
- data/spec/dummy/app/models/india.rb +4 -0
- data/spec/dummy/app/models/juliett.rb +21 -0
- data/spec/dummy/app/models/kilo.rb +2 -0
- data/spec/dummy/app/views/layouts/application.html.erb +14 -0
- data/spec/dummy/app/views/layouts/mailer.html.erb +13 -0
- data/spec/dummy/app/views/layouts/mailer.text.erb +1 -0
- data/spec/dummy/bin/rails +4 -0
- data/spec/dummy/bin/rake +4 -0
- data/spec/dummy/bin/setup +33 -0
- data/spec/dummy/config.ru +5 -0
- data/spec/dummy/config/application.rb +18 -0
- data/spec/dummy/config/boot.rb +5 -0
- data/spec/dummy/config/cable.yml +10 -0
- data/spec/dummy/config/database.yml +25 -0
- data/spec/dummy/config/environment.rb +5 -0
- data/spec/dummy/config/environments/development.rb +62 -0
- data/spec/dummy/config/environments/production.rb +112 -0
- data/spec/dummy/config/environments/test.rb +49 -0
- data/spec/dummy/config/initializers/application_controller_renderer.rb +8 -0
- data/spec/dummy/config/initializers/assets.rb +12 -0
- data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/dummy/config/initializers/content_security_policy.rb +28 -0
- data/spec/dummy/config/initializers/cookies_serializer.rb +5 -0
- data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/spec/dummy/config/initializers/inflections.rb +27 -0
- data/spec/dummy/config/initializers/mime_types.rb +4 -0
- data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/spec/dummy/config/locales/en.yml +33 -0
- data/spec/dummy/config/puma.rb +38 -0
- data/spec/dummy/config/routes.rb +3 -0
- data/spec/dummy/config/spring.rb +6 -0
- data/spec/dummy/config/storage.yml +34 -0
- data/spec/dummy/db/development.sqlite3 +0 -0
- data/spec/dummy/db/migrate/20200923100317_create_alfas.rb +15 -0
- data/spec/dummy/db/migrate/20200923100328_create_bravos.rb +12 -0
- data/spec/dummy/db/migrate/20200923100329_create_charlies.rb +9 -0
- data/spec/dummy/db/migrate/20200923100330_create_deltas.rb +10 -0
- data/spec/dummy/db/migrate/20200923100331_create_echos.rb +9 -0
- data/spec/dummy/db/migrate/20200923100332_create_foxtrots.rb +9 -0
- data/spec/dummy/db/migrate/20200923100334_create_hotels.rb +7 -0
- data/spec/dummy/db/migrate/20200923100335_create_indias.rb +11 -0
- data/spec/dummy/db/migrate/20200923100336_create_julietts.rb +11 -0
- data/spec/dummy/db/migrate/20200923100337_create_kilos.rb +13 -0
- data/spec/dummy/db/schema.rb +105 -0
- data/spec/dummy/db/test.sqlite3 +0 -0
- data/spec/dummy/log/development.log +217 -0
- data/spec/dummy/log/test.log +95379 -0
- data/spec/dummy/public/404.html +67 -0
- data/spec/dummy/public/422.html +67 -0
- data/spec/dummy/public/500.html +66 -0
- data/spec/dummy/public/apple-touch-icon-precomposed.png +0 -0
- data/spec/dummy/public/apple-touch-icon.png +0 -0
- data/spec/dummy/public/favicon.ico +0 -0
- data/spec/dummy/spec/factories/bars.rb +8 -0
- data/spec/dummy/spec/factories/foos.rb +7 -0
- data/spec/dummy/spec/models/alfa_spec.rb +6 -0
- data/spec/dummy/spec/models/bravo_spec.rb +6 -0
- data/spec/dummy/spec/models/charlie_spec.rb +6 -0
- data/spec/dummy/spec/models/delta_spec.rb +6 -0
- data/spec/dummy/spec/models/echo_spec.rb +6 -0
- data/spec/dummy/tmp/development_secret.txt +1 -0
- data/spec/lib/dirty_seed/assigners/dirty_assigner_spec.rb +13 -0
- data/spec/lib/dirty_seed/assigners/dirty_boolean_spec.rb +13 -0
- data/spec/lib/dirty_seed/assigners/dirty_date_spec.rb +15 -0
- data/spec/lib/dirty_seed/assigners/dirty_float_spec.rb +47 -0
- data/spec/lib/dirty_seed/assigners/dirty_integer_spec.rb +49 -0
- data/spec/lib/dirty_seed/assigners/dirty_string_spec.rb +15 -0
- data/spec/lib/dirty_seed/assigners/dirty_time_spec.rb +15 -0
- data/spec/lib/dirty_seed/data_model_spec.rb +72 -0
- data/spec/lib/dirty_seed/dirty_association_spec.rb +64 -0
- data/spec/lib/dirty_seed/dirty_attribute_spec.rb +49 -0
- data/spec/lib/dirty_seed/dirty_model_spec.rb +102 -0
- data/spec/lib/dirty_seed/sorter_spec.rb +46 -0
- data/spec/lib/tasks/dirty_seed_tasks_spec.rb +21 -0
- data/spec/rails_helper.rb +44 -0
- data/spec/support/helpers.rb +30 -0
- metadata +282 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DirtySeed
|
|
4
|
+
# Represents an Active Record association
|
|
5
|
+
class DirtyAssociation
|
|
6
|
+
extend ::DirtySeed::MethodMissingHelper
|
|
7
|
+
forward_missing_methods_to :reflections
|
|
8
|
+
|
|
9
|
+
attr_reader :dirty_model, :reflection
|
|
10
|
+
|
|
11
|
+
# Initializes an instance
|
|
12
|
+
# @param dirty_model [DirtySeed::DirtyModel]
|
|
13
|
+
# @param reflection [ActiveRecord::Reflection::BelongsToReflection]
|
|
14
|
+
# @return [DirtySeed::DirtyAssociation]
|
|
15
|
+
def initialize(dirty_model, reflection)
|
|
16
|
+
@dirty_model = dirty_model
|
|
17
|
+
@reflection = reflection
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Assigns a random value to the association
|
|
21
|
+
# @param instance [Object] an instance of a class inheriting from ApplicationRecord
|
|
22
|
+
# @return [void]
|
|
23
|
+
def assign_value(instance)
|
|
24
|
+
return if associated_models.empty?
|
|
25
|
+
|
|
26
|
+
instance.public_send(:"#{name}=", value)
|
|
27
|
+
rescue ArgumentError => e
|
|
28
|
+
@errors << e
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns a random instance matching the reflection
|
|
32
|
+
# @return [Object] an instance of a class inheriting from ApplicationRecord
|
|
33
|
+
def value
|
|
34
|
+
random_model = associated_models.sample
|
|
35
|
+
random_id = random_model.pluck(:id).sample
|
|
36
|
+
random_model.find_by(id: random_id)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns the reflection name
|
|
40
|
+
# @return [String]
|
|
41
|
+
def name
|
|
42
|
+
reflection.name
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns the attribute containing the foreign key
|
|
46
|
+
# @return [Symbol]
|
|
47
|
+
def attribute
|
|
48
|
+
:"#{name}_id"
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns the attribute containing the foreign type (for polymorphic associations)
|
|
52
|
+
# @example
|
|
53
|
+
# Given Bar.belongs_to(:barable, polymorphic: true)
|
|
54
|
+
# And self.model == Bar
|
|
55
|
+
# Then it returns barable_type
|
|
56
|
+
# @return [Symbol]
|
|
57
|
+
def type_key
|
|
58
|
+
reflection.foreign_type&.to_sym
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# Returns or defines associated_models
|
|
62
|
+
# @return [Array<Class>] a class inheriting from ApplicationRecord
|
|
63
|
+
def associated_models
|
|
64
|
+
polymorphic? ? polymorphic_associations : regular_associations
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
# Returns true if the reflection is polymorphic
|
|
68
|
+
# @example
|
|
69
|
+
# Given Bar.belongs_to(:barable, polymorphic: true)
|
|
70
|
+
# And self.model == Bar
|
|
71
|
+
# Then it returns true
|
|
72
|
+
# @return [Boolean]
|
|
73
|
+
def polymorphic?
|
|
74
|
+
reflection.options[:polymorphic]
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
# Returns the reflected models for a regular association
|
|
80
|
+
# @example
|
|
81
|
+
# Given Bar.belongs_to(:foo)
|
|
82
|
+
# And Foo.has_many(:bars)
|
|
83
|
+
# And self.model == Bar
|
|
84
|
+
# Then it returns [Foo]
|
|
85
|
+
# @return [Array<Class>] a class inheriting from ApplicationRecord
|
|
86
|
+
def regular_associations
|
|
87
|
+
[reflection.klass]
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Returns the reflected models for a polymorphic association
|
|
91
|
+
# @example
|
|
92
|
+
# Given Bar.belongs_to(:barable, polymorphic: true)
|
|
93
|
+
# And Foo.has_many(:bars, as: :barable)
|
|
94
|
+
# And Zed.has_many(:bars, as: :barable)
|
|
95
|
+
# And #model is Bar
|
|
96
|
+
# Then it returns [Foo, Zed]
|
|
97
|
+
# @return [Array<Class>] a class inheriting from ApplicationRecord
|
|
98
|
+
def polymorphic_associations
|
|
99
|
+
DirtySeed::DataModel.active_record_models.select do |active_record_model|
|
|
100
|
+
active_record_model.reflections.values.any? do |arm_reflection|
|
|
101
|
+
arm_reflection.options[:as]&.to_sym == name
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DirtySeed
|
|
4
|
+
# Represents an Active Record attribute
|
|
5
|
+
class DirtyAttribute
|
|
6
|
+
extend ::DirtySeed::MethodMissingHelper
|
|
7
|
+
forward_missing_methods_to :column
|
|
8
|
+
|
|
9
|
+
attr_reader :dirty_model, :column
|
|
10
|
+
|
|
11
|
+
# Initializes an instance
|
|
12
|
+
# @param dirty_model [DirtySeed::DirtyModel]
|
|
13
|
+
# @param column [ActiveRecord::ConnectionAdapters::Column]
|
|
14
|
+
# @return [DirtySeed::DirtyAttribute]
|
|
15
|
+
def initialize(dirty_model, column)
|
|
16
|
+
@dirty_model = dirty_model
|
|
17
|
+
@column = column
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Assigns a value to the attribute
|
|
21
|
+
# @param instance [Object] an instance of a class inheriting from ApplicationRecord
|
|
22
|
+
# @param sequence [Integer]
|
|
23
|
+
# @return [void]
|
|
24
|
+
def assign_value(instance, sequence)
|
|
25
|
+
instance.assign_attributes(name => value(sequence))
|
|
26
|
+
rescue ArgumentError => e
|
|
27
|
+
dirty_model.errors << e
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns a value matching type and validators
|
|
31
|
+
# @param sequence [Integer]
|
|
32
|
+
# @return [Object, nil]
|
|
33
|
+
def value(sequence)
|
|
34
|
+
assigner = "DirtySeed::Assigners::Dirty#{type.capitalize}".constantize
|
|
35
|
+
assigner.new(self, sequence).value
|
|
36
|
+
# If attribute type is not currently handled (json, array...) return nil
|
|
37
|
+
rescue NameError
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns attribute name
|
|
42
|
+
# @return [Symbol]
|
|
43
|
+
def name
|
|
44
|
+
column.name.to_sym
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Returns attribute type
|
|
48
|
+
# @return [Symbol]
|
|
49
|
+
def type
|
|
50
|
+
return :float if column.sql_type_metadata.type == :decimal
|
|
51
|
+
return :time if column.sql_type_metadata.type == :datetime
|
|
52
|
+
|
|
53
|
+
column.sql_type_metadata.type
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns an validators related to the current attribute
|
|
57
|
+
# @return [Array<ActiveModel::Validations::EachValidators>]
|
|
58
|
+
def validators
|
|
59
|
+
dirty_model.validators.select do |validator|
|
|
60
|
+
validator.attributes.include? name
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DirtySeed
|
|
4
|
+
# Represents an Active Record model
|
|
5
|
+
class DirtyModel
|
|
6
|
+
extend ::DirtySeed::MethodMissingHelper
|
|
7
|
+
forward_missing_methods_to :model
|
|
8
|
+
|
|
9
|
+
attr_reader :model, :seeded
|
|
10
|
+
attr_writer :errors
|
|
11
|
+
|
|
12
|
+
PROTECTED_COLUMNS = %w[
|
|
13
|
+
id
|
|
14
|
+
type
|
|
15
|
+
created_at
|
|
16
|
+
updated_at
|
|
17
|
+
encrypted_password
|
|
18
|
+
reset_password_token
|
|
19
|
+
reset_password_sent_at
|
|
20
|
+
remember_created_at
|
|
21
|
+
].freeze
|
|
22
|
+
private_constant :PROTECTED_COLUMNS
|
|
23
|
+
|
|
24
|
+
# Initializes an instance
|
|
25
|
+
# @param model [Class] a class inheriting from ApplicationRecord
|
|
26
|
+
# @return [DirtySeed::DirtyModel]
|
|
27
|
+
def initialize(model)
|
|
28
|
+
@model = model
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
# Returns models where models are associated to the current model through a has_many or has_one associations
|
|
32
|
+
# @return [Array<Class>] ActiveRecord models
|
|
33
|
+
def associated_models
|
|
34
|
+
associations.map(&:associated_models).flatten
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns an dirty associations representing the self.model belongs_to associations
|
|
38
|
+
# @return [Array<DirtySeed::DirtyAssociation>]
|
|
39
|
+
def associations
|
|
40
|
+
included_reflections.map do |reflection|
|
|
41
|
+
DirtySeed::DirtyAssociation.new(self, reflection)
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Returns model attributes
|
|
46
|
+
# @return [Array<String>]
|
|
47
|
+
def attributes
|
|
48
|
+
included_columns.map do |column|
|
|
49
|
+
DirtySeed::DirtyAttribute.new(self, column)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Returns uniq errors
|
|
54
|
+
# @return [Array<Error>]
|
|
55
|
+
def errors
|
|
56
|
+
@errors.flatten.uniq
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Creates instances for each model
|
|
60
|
+
# @param count [Integer]
|
|
61
|
+
# @return [void]
|
|
62
|
+
def seed(count = 5)
|
|
63
|
+
reset_info
|
|
64
|
+
count.times do |sequence|
|
|
65
|
+
create_instance(sequence)
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
private
|
|
70
|
+
|
|
71
|
+
# Reset seed info
|
|
72
|
+
# @return [void]
|
|
73
|
+
def reset_info
|
|
74
|
+
@seeded = 0
|
|
75
|
+
@errors = []
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Creates an instance
|
|
79
|
+
# @param sequence [Integer]
|
|
80
|
+
# @return [void]
|
|
81
|
+
def create_instance(sequence)
|
|
82
|
+
instance = model.new
|
|
83
|
+
associations.each { |association| association.assign_value(instance) }
|
|
84
|
+
attributes.each { |attribute| attribute.assign_value(instance, sequence) }
|
|
85
|
+
if instance.save
|
|
86
|
+
@seeded += 1
|
|
87
|
+
else
|
|
88
|
+
@errors << instance.errors.full_messages
|
|
89
|
+
end
|
|
90
|
+
rescue ActiveRecord::ActiveRecordError => e
|
|
91
|
+
errors << e
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Returns columns that should be treated as regular attributes
|
|
95
|
+
# @return [Array<ActiveRecord::ConnectionAdapters::Column>]
|
|
96
|
+
def included_columns
|
|
97
|
+
excluded = PROTECTED_COLUMNS + reflection_related_attributes
|
|
98
|
+
model.columns.reject do |column|
|
|
99
|
+
column.name.in? excluded
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Returns attributes related to an association
|
|
104
|
+
# @example
|
|
105
|
+
# ["foo_id", "doable_id", "doable_type"]
|
|
106
|
+
# @return [Array<String>]
|
|
107
|
+
def reflection_related_attributes
|
|
108
|
+
all_reflection_related_attributes =
|
|
109
|
+
associations.map do |association|
|
|
110
|
+
[association.attribute, association.type_key].compact
|
|
111
|
+
end
|
|
112
|
+
all_reflection_related_attributes.flatten.map(&:to_s)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Returns reflections of the model
|
|
116
|
+
# @return [Array<ActiveRecord::Reflection::BelongsToReflection>]
|
|
117
|
+
def included_reflections
|
|
118
|
+
model.reflections.values.select do |reflection|
|
|
119
|
+
reflection.is_a? ActiveRecord::Reflection::BelongsToReflection
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
123
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DirtySeed
|
|
4
|
+
# Forwards missing method to another object if it matches
|
|
5
|
+
module MethodMissingHelper
|
|
6
|
+
# Defines missing_method and respond_to_missing? methods
|
|
7
|
+
# @param addressee_key [Object]
|
|
8
|
+
# @return [Object]
|
|
9
|
+
# @raise [NoMethodError]
|
|
10
|
+
def forward_missing_methods_to(addressee_key)
|
|
11
|
+
define_addressee(addressee_key)
|
|
12
|
+
define_method_missing
|
|
13
|
+
define_respond_to_missing?
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
# Defines addressee method
|
|
17
|
+
# @param addressee_key [Object]
|
|
18
|
+
# @return [void]
|
|
19
|
+
def define_addressee(addressee_key)
|
|
20
|
+
define_method :addressee do
|
|
21
|
+
public_send(addressee_key)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
# Defines missing_method method so it returns the adressee or calls super
|
|
26
|
+
# @example
|
|
27
|
+
# calling #name on a DirtyModel instance will call name on its @model object
|
|
28
|
+
# @return [void]
|
|
29
|
+
def define_method_missing
|
|
30
|
+
define_method :method_missing do |method_name, *args, &block|
|
|
31
|
+
return super(method_name, *args, &block) unless addressee.respond_to?(method_name)
|
|
32
|
+
|
|
33
|
+
addressee.public_send(method_name, *args, &block)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Defines respond_to_missing? method to matches the method_missing behavior
|
|
38
|
+
# @return [void]
|
|
39
|
+
def define_respond_to_missing?
|
|
40
|
+
define_method :respond_to_missing? do |method_name, _include_private = false|
|
|
41
|
+
# :nocov:
|
|
42
|
+
return super(method_name, _include_private = false) unless addressee.respond_to?(method_name)
|
|
43
|
+
|
|
44
|
+
true
|
|
45
|
+
# :nocov:
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module DirtySeed
|
|
4
|
+
# Sorts ActiveRecord models depending on their associations
|
|
5
|
+
class Sorter
|
|
6
|
+
attr_reader :models, :sorted, :checked, :current, :skip_optional
|
|
7
|
+
alias unsorted models
|
|
8
|
+
|
|
9
|
+
# Initializes an instance
|
|
10
|
+
# @param models [Array<Class>] models inheriting from ActiveRecord::Base
|
|
11
|
+
# @return [DirtySeed::Sorter]
|
|
12
|
+
def initialize(models = [])
|
|
13
|
+
@models = models
|
|
14
|
+
@sorted = []
|
|
15
|
+
@checked = []
|
|
16
|
+
@current = unsorted.first
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Sorts models depending on their associations
|
|
20
|
+
# @return [Array<Class>] classes inheriting from ActiveRecord::Base
|
|
21
|
+
def sort!
|
|
22
|
+
return sorted if unsorted.empty?
|
|
23
|
+
|
|
24
|
+
set_current
|
|
25
|
+
# active skip_optional option to prevent infinite loop
|
|
26
|
+
skip_optional! if break_loop?
|
|
27
|
+
insert_or_rotate
|
|
28
|
+
sort!
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
# Defines the current model to be sorted and add it to the checked ones
|
|
34
|
+
# @return [Class] class inheriting from ActiveRecord::Base
|
|
35
|
+
def set_current
|
|
36
|
+
@current = unsorted.first
|
|
37
|
+
checked << current
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns true if the current model has already been checked and skip optional option is not already activated
|
|
41
|
+
# @return [Boolean]
|
|
42
|
+
def break_loop?
|
|
43
|
+
current.in?(checked) && !skip_optional
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Activates skip_optional option to prevent infinite loop
|
|
47
|
+
# @return [void]
|
|
48
|
+
def skip_optional!
|
|
49
|
+
# if skip_optional is already true
|
|
50
|
+
# there is an infinite loop of belongs_to associations
|
|
51
|
+
raise DirtySeed::CyclicalAssociationsError if skip_optional
|
|
52
|
+
|
|
53
|
+
@skip_optional = true
|
|
54
|
+
@checked = []
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
# Chooses if current should be added to sorted ones or not
|
|
58
|
+
# @return [void]
|
|
59
|
+
def insert_or_rotate
|
|
60
|
+
# if the current is dependent form a non-sorted model
|
|
61
|
+
if dependent?
|
|
62
|
+
# rotate models array so current will be sorted at the end
|
|
63
|
+
unsorted.rotate!
|
|
64
|
+
else
|
|
65
|
+
# removed current from unsorted and add it to sorted
|
|
66
|
+
sorted << unsorted.shift
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Returns true if @current belongs_to a model that has not been sorted yet
|
|
71
|
+
# @return [Boolean]
|
|
72
|
+
def dependent?
|
|
73
|
+
return false if unsorted.one?
|
|
74
|
+
|
|
75
|
+
current.reflections.values.any? do |reflection|
|
|
76
|
+
belongs_to?(reflection) &&
|
|
77
|
+
unsorted.any? do |model|
|
|
78
|
+
mirror?(model, reflection)
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
# Returns true if relfection is a :belongs_to kind
|
|
84
|
+
# @param reflection [ActiveRecord::Reflection::AssociationReflection]
|
|
85
|
+
# @return [Boolean]
|
|
86
|
+
def belongs_to?(reflection)
|
|
87
|
+
return false if reflection.options[:optional] && skip_optional
|
|
88
|
+
|
|
89
|
+
reflection.is_a?(ActiveRecord::Reflection::BelongsToReflection)
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Returns true if model is or can be the <reflection> mirror
|
|
93
|
+
# @param model [ActiveRecord model]
|
|
94
|
+
# @param reflection [ActiveRecord::Reflection::AssociationReflection]
|
|
95
|
+
# @example
|
|
96
|
+
# Given `Foo.belongs_to(:bar)`
|
|
97
|
+
# And `Bar.has_many(:foos)`
|
|
98
|
+
# And <reflection> is the belongs_to reflection
|
|
99
|
+
# Then mirror?(Bar, reflection) returns true
|
|
100
|
+
# @example
|
|
101
|
+
# Given `Foo.belongs_to(:foable, polymorphic: true)`
|
|
102
|
+
# And `Bar.has_many(:foos, as: :foable)`
|
|
103
|
+
# And `Baz.has_many(:foos, as: :foable)`
|
|
104
|
+
# And <reflection> is the Foo "belongs_to" reflection
|
|
105
|
+
# Then mirror?(Bar, reflection) returns true
|
|
106
|
+
# And mirror?(Baz, reflection) returns true
|
|
107
|
+
# @return [Boolean]
|
|
108
|
+
def mirror?(model, reflection)
|
|
109
|
+
if reflection.options[:polymorphic]
|
|
110
|
+
model.reflections.values.any? do |model_reflection|
|
|
111
|
+
model_reflection.options[:as] == reflection.name
|
|
112
|
+
end
|
|
113
|
+
else
|
|
114
|
+
model == reflection.klass
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
end
|