live_fixtures 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 8395ce611ed8a861cdc45201f736748f1553170b
4
+ data.tar.gz: 2f2366f259279f43a1df43df05b6d42ab16dfca4
5
+ SHA512:
6
+ metadata.gz: d8967a938a1ce69b465e64c2d82949624274cc69fe82f4fa46a3d4b11d055ce7ddb7edec0227b8c15ec60dd1cd9968e9d3a979de7a74d14feb47d091a8ee75c2
7
+ data.tar.gz: 74867a18f1f3996c48ced1cfa65498b7b30fa08a4511650766706291d6d4bf960491b0b073b9fbac747e93dc85a30036822fc95947df8cb41b6dd8faf75170be
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Change Log
2
+ All notable changes to this project will be documented in this file.
3
+ This project adheres to [Semantic Versioning](http://semver.org/).
4
+
5
+ ## [0.1.1] - 2016-06-30
6
+ ### Fixed
7
+ - [live fixtures works better with slow TTYs & zeus](https://github.com/NoRedInk/live_fixtures/pull/4)
8
+
9
+ ## 0.1.0 - 2016-06-13
10
+ ### Added
11
+ - initial release
12
+
13
+ [0.1.1]: https://github.com/NoRedInk/live_fixtures/compare/v0.1.0...v0.1.1
data/MIT-LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2016 raorao
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,41 @@
1
+ # LiveFixtures
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/live_fixtures`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'live_fixtures'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install live_fixtures
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/live_fixtures. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
36
+
37
+
38
+ ## License
39
+
40
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
41
+
@@ -0,0 +1,56 @@
1
+ module LiveFixtures::Export::Fixture
2
+ module_function
3
+ def to_yaml(model, references = [], more_attributes = {})
4
+ table_name = model.class.table_name
5
+
6
+ more_attributes.merge! attributes_from_references(model, references)
7
+
8
+ <<-YML
9
+ #{table_name}_#{model.id || SecureRandom.uuid.underscore}:
10
+ #{yml_attributes(model, more_attributes)}
11
+
12
+ YML
13
+ end
14
+
15
+ private_class_method def attributes_from_references(model, references)
16
+ {}.tap do |options|
17
+ Array(references).each do |assoc_name|
18
+
19
+ if model.respond_to? assoc_name # need to check #respond_to? because some assoc only exist in certain subclasses
20
+ assoc_model = model.send assoc_name
21
+ end
22
+
23
+ if assoc_model.present?
24
+ options["#{assoc_name}_id"] = LiveFixtures::Export::Reference.new(assoc_name, assoc_model)
25
+ end
26
+ end
27
+ end
28
+ end
29
+
30
+ private_class_method def yml_attributes(model, more_attributes)
31
+ model.attributes.merge(more_attributes).map do |name, value|
32
+ next if %w{id}.include? name
33
+ next if value.nil?
34
+
35
+ yml_value ||= case value
36
+ when Time, DateTime
37
+ value.utc.to_s(:db)
38
+ when Date
39
+ value.to_s(:db)
40
+ when Hash
41
+ value.to_yaml.inspect
42
+ when String
43
+ value.inspect
44
+ when LiveFixtures::Export::Template
45
+ value.code
46
+ when LiveFixtures::Export::Reference
47
+ name, value = value.name, value.value
48
+ "#{value.class.table_name}_#{value.id}"
49
+ else
50
+ value.to_s
51
+ end
52
+
53
+ "#{name}: " + yml_value
54
+ end.compact.join("\n ")
55
+ end
56
+ end
@@ -0,0 +1,57 @@
1
+ # This module is meant to be `include`ed into your export class.
2
+ #
3
+ # 1. Call #set_export_dir to set the dir where files should be created.
4
+ # If the dir does not already exist, it will be created for you.
5
+ #
6
+ # 2. Then call #export_fixtures for each db table, which will produce
7
+ # one yml file for each db table. Do *not* call export_fixtures multiple
8
+ # times for the same db table - that will overwrite the file each time!
9
+
10
+ module LiveFixtures::Export
11
+ Template = Struct.new(:code)
12
+ Reference = Struct.new(:name, :value)
13
+
14
+ private
15
+
16
+ def set_export_dir(dir)
17
+ @dir = dir
18
+ FileUtils.mkdir_p(@dir) unless File.directory?(@dir)
19
+ end
20
+
21
+ ##
22
+ # Export models to a yml file named after the corresponding table.
23
+ #
24
+ # Takes an optional block that will be invoked for each model.
25
+ # The block should return a hash of attributes to be merged and
26
+ # saved with the model's attributes.
27
+ def export_fixtures(models, with_references = [])
28
+ return unless models.present?
29
+
30
+ table_name = models.first.class.table_name
31
+ File.open(File.join(@dir, table_name + '.yml'), 'w') do |file|
32
+
33
+ ProgressBarIterator.new(models).each do |model|
34
+ more_attributes = block_given? ? yield(model) : {}
35
+ file.write Fixture.to_yaml(model, with_references, more_attributes)
36
+ end
37
+
38
+ end
39
+ end
40
+
41
+ class ProgressBarIterator
42
+ def initialize(models)
43
+ @models = models
44
+ @bar = LiveFixtures.get_progress_bar(
45
+ total:models.size,
46
+ title: models.first.class.name
47
+ )
48
+ end
49
+
50
+ def each
51
+ @models.each do |model|
52
+ yield model
53
+ @bar.increment
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,103 @@
1
+ require 'active_record/fixtures'
2
+
3
+ #rubocop:disable Style/PerlBackrefs
4
+
5
+ class LiveFixtures::Import
6
+ class Fixtures
7
+ delegate :model_class, :table_name, :fixtures, to: :ar_fixtures
8
+ attr_reader :ar_fixtures
9
+
10
+ def initialize(connection, table_name, class_name, filepath, label_to_id)
11
+ @ar_fixtures = ActiveRecord::Fixtures.new connection,
12
+ table_name,
13
+ class_name,
14
+ filepath
15
+ @label_to_id = label_to_id
16
+ end
17
+
18
+ # https://github.com/rails/rails/blob/3-2-stable/activerecord/lib/active_record/fixtures.rb#L569
19
+ # Rewritten to take advantage of @label_to_id instead of AR::Fixtures#identify,
20
+ # and to make an iterator.
21
+ #
22
+ # Iterator which yields [table_name, label, row] for each fixture
23
+ # (and for any implicit join table records)
24
+ def each_table_row_with_label
25
+ join_table_rows = Hash.new { |h,table| h[table] = [] }
26
+
27
+ fixtures.map do |label, fixture|
28
+ row = fixture.to_hash
29
+
30
+ reflection_class = reflection_class_for row
31
+
32
+ reflection_class.reflect_on_all_associations.each do |association|
33
+ next unless row[association.name.to_s]
34
+
35
+ case association.macro
36
+ when :belongs_to
37
+ maybe_convert_association_to_foreign_key row, association
38
+
39
+ when :has_and_belongs_to_many
40
+ join_table_name = association.options[:join_table]
41
+
42
+ targets = row.delete(association.name.to_s)
43
+ targets = targets.split(/\s*,\s*/) unless targets.is_a?(Array)
44
+
45
+ join_table_rows[join_table_name] << { targets: targets,
46
+ association: association,
47
+ label: label }
48
+ end
49
+ end
50
+
51
+ yield [table_name, label, row]
52
+ end
53
+
54
+ join_table_rows.each do |table_name, rows|
55
+ rows.each do |targets:, association:, label:|
56
+ targets.each do |target|
57
+ assoc_fk = @label_to_id[target] || target
58
+ row = { association.foreign_key => @label_to_id[label],
59
+ association.association_foreign_key => assoc_fk }
60
+ yield [table_name, NO_LABEL, row]
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ def model_connection
67
+ model_class.connection if model_class.respond_to? :connection
68
+ end
69
+
70
+ private
71
+
72
+ def inheritance_column_name
73
+ @inheritance_column_name ||= model_class && model_class.inheritance_column
74
+ end
75
+
76
+ # If STI is used, find the correct subclass for association reflection
77
+ def reflection_class_for(row)
78
+ return model_class unless row.include?(inheritance_column_name)
79
+
80
+ row[inheritance_column_name].constantize
81
+ rescue
82
+ model_class
83
+ end
84
+
85
+ def maybe_convert_association_to_foreign_key(row, association)
86
+ fk_name = (association.options[:foreign_key] || "#{association.name}_id").to_s
87
+
88
+ # Do not replace association name with association foreign key if they are named the same
89
+ return if association.name.to_s == fk_name
90
+
91
+ value = row.delete(association.name.to_s)
92
+
93
+ # support polymorphic belongs_to as "label (Type)"
94
+ if association.options[:polymorphic] && value.sub!(/\s*\(([^\)]*)\)\s*$/, "")
95
+ row[association.foreign_type] = $1
96
+ end
97
+
98
+ row[fk_name] = @label_to_id[value]
99
+ end
100
+
101
+ private :ar_fixtures
102
+ end
103
+ end
@@ -0,0 +1,123 @@
1
+ # ActiveRecord::Fixtures are a powerful way of populating data in a db;
2
+ # however, its strategy for handling primary keys and associations is
3
+ # UNACCEPTABLE for use with a production db. LiveFixtures works around this.
4
+ #
5
+ #
6
+ ########### Here's how ActiveRecord::Fixtures work ###########################
7
+ #
8
+ # Each record is assigned a label in its yml file. Primary key values are
9
+ # assigned using a guid algorithm that maps a label to a consistent integer
10
+ # between 1 and 2^30-1. Primary keys can then be assigned before saving any
11
+ # records to the db.
12
+ #
13
+ # Why would they do this? Because, this enables us to use labels in the
14
+ # Fixture yml files to refer to associations. For example:
15
+ #
16
+ # <users.yml>
17
+ # bob:
18
+ # username: thebob
19
+ #
20
+ # <posts.yml>
21
+ # hello:
22
+ # message: Hello everyone!
23
+ # user: bob
24
+ #
25
+ # The ActiveRecord::Fixture system first converts every instance of `bob` and
26
+ # `hello` into an integer using ActiveRecord::Fixture#identify, and then can
27
+ # save the records IN ANY ORDER and know that all foreign keys will be valid.
28
+ #
29
+ # There is a big problem with this. In a test db, each table is empty and so the
30
+ # odds of inserting a few dozen records causing a primary key collision is
31
+ # very small. However, for a production table with a hundred million rows, this
32
+ # is no longer the case! Collisions abound and db insertion fails.
33
+ #
34
+ # Also, autoincrement primary keys will continue from the LARGEST existing
35
+ # primary key value. If we insert a record at 1,000,000,000 - we've reduced the
36
+ # total number of records we can store in that table in half. Fine for a test db
37
+ # but not ideal for production.
38
+ #
39
+ #
40
+ ########### LiveFixtures work differently ####################################
41
+ #
42
+ # Since we want to be able to take advantage of normal autoincrement behavior,
43
+ # we cannot know the primary keys of each record before saving it to the db.
44
+ # Instead, we save each record, and then maintain a mapping (`@label_to_id`)
45
+ # from that record's label (`bob`), to its primary key (`213`). Later, when
46
+ # another record (`hello`) references `bob`, we can use this mapping to look up
47
+ # the primary key for `bob` before saving `hello`.
48
+ #
49
+ # This means that the order we insert records into the db matters: `bob` must
50
+ # be inserted before `hello`! This order is defined in INSERT_ORDER, and
51
+ # reflected in the order of the `@table_names` array.
52
+
53
+ class LiveFixtures::Import
54
+ NO_LABEL = nil
55
+
56
+ def initialize(root_path, insert_order)
57
+ @root_path = root_path
58
+ @table_names = Dir.glob(File.join(@root_path, '{*,**}/*.yml')).map do |filepath|
59
+ File.basename filepath, ".yml"
60
+ end
61
+ @table_names = insert_order.select {|table_name| @table_names.include? table_name}
62
+ if @table_names.size < insert_order.size
63
+ raise ArgumentError, "table(s) mentioned in `insert_order` which has no yml file to import: #{insert_order - @table_names}"
64
+ end
65
+ @label_to_id = {}
66
+ end
67
+
68
+ # https://github.com/rails/rails/blob/3-2-stable/activerecord/lib/active_record/fixtures.rb#L462
69
+ # The very similar method: ActiveRecord::Fixtures.create_fixtures has the
70
+ # unfortunate side effect of truncating each table!!
71
+ #
72
+ # Therefore, we have reproduced the relevant sections here, without DELETEs,
73
+ # with calling `LF::Import::Fixtures#each_table_row_with_label` instead of
74
+ # `AR::Fixtures#table_rows`, and using those labels to populate `@label_to_id`.
75
+ def import_all(class_names = {})
76
+ @table_names.each { |n|
77
+ class_names[n.tr('/', '_').to_sym] ||= n.classify if n.include?('/')
78
+ }
79
+
80
+ connection = ActiveRecord::Base.connection
81
+
82
+ files_to_read = @table_names
83
+
84
+ unless files_to_read.empty?
85
+ connection.transaction(requires_new: true) do
86
+ files_to_read.each do |path|
87
+ table_name = path.tr '/', '_'
88
+ class_name = class_names[table_name.to_sym] || table_name.classify
89
+
90
+ ff = Fixtures.new(connection,
91
+ table_name,
92
+ class_name,
93
+ ::File.join(@root_path, path),
94
+ @label_to_id)
95
+
96
+ conn = ff.model_connection || connection
97
+ ProgressBarIterator.new(ff).each do |table_name, label, row|
98
+ conn.insert_fixture(row, table_name)
99
+ @label_to_id[label] = conn.last_inserted_id(table_name) unless label == NO_LABEL
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+
106
+ class ProgressBarIterator
107
+ def initialize(ff)
108
+ @ff = ff
109
+ @bar = LiveFixtures.get_progress_bar(
110
+ total:ff.fixtures.size,
111
+ title: ff.model_class.name
112
+ )
113
+ end
114
+
115
+ def each
116
+ @ff.each_table_row_with_label do |*args|
117
+ yield(*args)
118
+ @bar.increment unless @bar.finished?
119
+ end
120
+ @bar.finish
121
+ end
122
+ end
123
+ end
@@ -0,0 +1,3 @@
1
+ module LiveFixtures
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,18 @@
1
+ require "live_fixtures/version"
2
+ require "live_fixtures/import"
3
+ require "live_fixtures/import/fixtures"
4
+ require "live_fixtures/export"
5
+ require "live_fixtures/export/fixture"
6
+ require "ruby-progressbar"
7
+
8
+ module LiveFixtures
9
+ module_function
10
+ def get_progress_bar total:, title:
11
+ ProgressBar.create(
12
+ total: total,
13
+ title: title,
14
+ format:'%t: |%B| %P% %E',
15
+ throttle_rate: 0.1
16
+ )
17
+ end
18
+ end
metadata ADDED
@@ -0,0 +1,165 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: live_fixtures
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.1
5
+ platform: ruby
6
+ authors:
7
+ - jleven
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-05-08 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '3.2'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '3.2'
27
+ - !ruby/object:Gem::Dependency
28
+ name: ruby-progressbar
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.11'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.11'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '3.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '3.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: temping
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: byebug
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: sqlite3
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ description:
126
+ email:
127
+ - josh@noredink.com
128
+ executables: []
129
+ extensions: []
130
+ extra_rdoc_files: []
131
+ files:
132
+ - CHANGELOG.md
133
+ - MIT-LICENSE
134
+ - README.md
135
+ - lib/live_fixtures.rb
136
+ - lib/live_fixtures/export.rb
137
+ - lib/live_fixtures/export/fixture.rb
138
+ - lib/live_fixtures/import.rb
139
+ - lib/live_fixtures/import/fixtures.rb
140
+ - lib/live_fixtures/version.rb
141
+ homepage:
142
+ licenses:
143
+ - MIT
144
+ metadata: {}
145
+ post_install_message:
146
+ rdoc_options: []
147
+ require_paths:
148
+ - lib
149
+ required_ruby_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ required_rubygems_version: !ruby/object:Gem::Requirement
155
+ requirements:
156
+ - - ">="
157
+ - !ruby/object:Gem::Version
158
+ version: '0'
159
+ requirements: []
160
+ rubyforge_project:
161
+ rubygems_version: 2.6.12
162
+ signing_key:
163
+ specification_version: 4
164
+ summary: Tools for exporting and importing between databases managed by ActiveRecord.
165
+ test_files: []