activerecord_data_importer 0.1.0

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: 6ebdc71d64176a86427eb21488dc8a88f11f203a
4
+ data.tar.gz: c9529d154b5556a4cfbca919bc77df28f838bd49
5
+ SHA512:
6
+ metadata.gz: 5304fa3f990f2989e208446494478e924ac64514d69450c0394c718e41fa63ae3f2e986a2135ac160923c1671fa0dbad90b58d9dc299c540f2c9629959a81c95
7
+ data.tar.gz: e01776c4641c4b835ecdd8dbbce57a61715264a9417f4004a8cdaea5cd8981aab4bf99031ac863aba01575b95dfaade46be423750579f98798e699e648289fd1
data/.gitignore ADDED
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --format documentation
2
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ nguage: ruby
2
+ rvm:
3
+ - 2.2.0
4
+ - 2.2.1
5
+ install: bundle install
6
+ cache:
7
+ directories:
8
+ - vendor/bundle
9
+ script: bundle exec rspec spec
@@ -0,0 +1,13 @@
1
+ # Contributor Code of Conduct
2
+
3
+ As contributors and maintainers of this project, we pledge to respect all people who contribute through reporting issues, posting feature requests, updating documentation, submitting pull requests or patches, and other activities.
4
+
5
+ We are committed to making participation in this project a harassment-free experience for everyone, regardless of level of experience, gender, gender identity and expression, sexual orientation, disability, personal appearance, body size, race, age, or religion.
6
+
7
+ Examples of unacceptable behavior by participants include the use of sexual language or imagery, derogatory comments or personal attacks, trolling, public or private harassment, insults, or other unprofessional conduct.
8
+
9
+ Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct. Project maintainers who do not follow the Code of Conduct may be removed from the project team.
10
+
11
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by opening an issue or contacting one or more of the project maintainers.
12
+
13
+ This Code of Conduct is adapted from the [Contributor Covenant](http:contributor-covenant.org), version 1.0.0, available at [http://contributor-covenant.org/version/1/0/0/](http://contributor-covenant.org/version/1/0/0/)
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in activerecord_json_loader.gemspec
4
+ gemspec
5
+ gem 'mysql2', '~> 0.3.18'
6
+ gem 'activerecord', '~> 4.2.0'
7
+ gem 'activesupport', '~> 4.2.0'
8
+ gem 'rspec'
9
+ gem 'database_rewinder'
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 TODO: Write your name
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,202 @@
1
+ # ActiverecordDataImporter
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/activerecord_data_importer`. 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 'activerecord_data_importer'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install activerecord_data_importer
22
+
23
+ ## Usage
24
+
25
+ ### Basic
26
+ please include sentence on ActiveRecord's Model
27
+ ```
28
+ include ActiverecordDataImporter
29
+
30
+ ```
31
+
32
+ When you defined model like this
33
+ ```
34
+ class Item < ActiveRecord::Base
35
+ include ActiverecordDataImporter
36
+ end
37
+
38
+ schema
39
+ ///////////
40
+ create_table "Items", force: :cascade do |t|
41
+ t.string "name", null: false
42
+ t.datetime "created_at", null: false
43
+ t.datetime "updated_at", null: false
44
+ end
45
+ ///////////
46
+ ```
47
+
48
+ and exsit file json file like context,
49
+ ```
50
+ [
51
+ { "id": 1, "name": "hoge" },
52
+ { "id": 2, "name": "huga" }
53
+ ]
54
+ ```
55
+
56
+ you'll be able to import the following description(json file path is /foo/bar/items.json).
57
+ ```
58
+ Item.import_from_json "/foo/bar/items.json"
59
+ ```
60
+
61
+ If json single object following description, it can be used in the same way.
62
+ ```
63
+ { "id": 1, "name": "hoge" }
64
+ ```
65
+
66
+ ### Association support
67
+ If you've used association model
68
+ example
69
+ ```
70
+
71
+ class Item < ActiveRecord::Base
72
+ include ActiverecordDataImporter
73
+ has_many :item_effects
74
+ end
75
+
76
+ class ItemEffect < ActiveRecord::Base
77
+ include ActiverecordDataImporter
78
+ end
79
+
80
+ schema
81
+ ///////////
82
+ create_table "Items", force: :cascade do |t|
83
+ t.string "name", null: false
84
+ t.datetime "created_at", null: false
85
+ t.datetime "updated_at", null: false
86
+ end
87
+
88
+ create_table "Item_effects", force: :cascade do |t|
89
+ t.integer "value", null: false
90
+ t.integer "item_id", null: false
91
+ t.datetime "created_at", null: false
92
+ t.datetime "updated_at", null: false
93
+ end
94
+ ///////////
95
+ ```
96
+ you can import such json structure
97
+ ```
98
+ [
99
+ {
100
+ "id": 1,
101
+ "name": "hoge",
102
+ "item_effects":[
103
+ { "value": 1 },
104
+ { "value": 2 },
105
+ { "value": 3 }
106
+ ]
107
+ },
108
+ {
109
+ "id": 2,
110
+ "name": "huga",
111
+ "item_effects":[
112
+ { "value": 1 },
113
+ { "value": 2 },
114
+ { "value": 3 }
115
+ ]
116
+ },
117
+ {
118
+ "id": 3,
119
+ "name": "piyo",
120
+ "item_effects":[
121
+ { "value": 1 },
122
+ { "value": 2 },
123
+ { "value": 3 }
124
+ ]
125
+ }
126
+ ]
127
+ ```
128
+ You can also import in the same way if it multistage association in the correct json description.
129
+
130
+ ### Versioning number
131
+ If importing target schema has version column, It will be increment automatically each time a record is updated.
132
+ But, if same data importing (no change), version is not updated.
133
+ Also, having assosiation and updating child association, parent version is updated.
134
+ Furthermore, when version is updated, version number is that obtained by adding 1 to the highest number in all data before the update
135
+
136
+ ### csv supporting
137
+ csv file format is supported. if you want to use relational structure, please define same row and column name should be dotted
138
+ And if relational data is `has_many` structure, please put the serial number before the dot
139
+ ex:
140
+ csv file
141
+ ```
142
+ id,name,item_effects1.value,item_effects2.value,item_effects3.value
143
+ 1,hoge,1,2,3
144
+ 2,huga,1,2,3
145
+ 3,piyo,1,2,3
146
+ ```
147
+ table showing
148
+ | id | name | item_effects1.value | item_effects2.value | item_effects3.value |
149
+ | ------------- | ------------- | ------------- | ------------- | ------------- |
150
+ | 1 | hoge | 1 | 2 | 3 |
151
+ | 2 | huga | 1 | 2 | 3 |
152
+ | 3 | piyo | 1 | 2 | 3 |
153
+
154
+ ## Note that
155
+
156
+ * If id does not exist json's attributes, new record will be created. As long as it does not want this thing , always please do put the id.
157
+ * Case of has_many association existed, always sync import data. Example for previous model structure (item and item_effect), if the effect associated with the item Two importing the data associated with three one state , and is adjusted to two.
158
+ ```
159
+ when before imported data is
160
+ {
161
+ "id": 1,
162
+ "name": "hoge",
163
+ "item_effects":[
164
+ { "value": 1 },
165
+ { "value": 2 },
166
+ { "value": 3 }
167
+ ]
168
+ }
169
+ and after is...
170
+ {
171
+ "id": 1,
172
+ "name": "hoge",
173
+ "item_effects":[
174
+ { "value": 1 },
175
+ { "value": 2 }
176
+ ]
177
+ }
178
+
179
+ the result is that..
180
+ Item.find(1).item_effects
181
+ >> [#<ItemEffect id: 1, item_id: 1, value: 1, created_at: "2015-09-15 09:32:19", updated_at: "2015-09-15 09:32:19">, #<ItemEffect id: 2, item_id: 1, value: 2, created_at: "2015-09-15 09:32:19", updated_at: "2015-09-15 09:32:19">]
182
+ ```
183
+ * belong_to assosiasion is not supported
184
+ * default versioning is maybe undesirable for you. The reason is that is processing to get the latest version is published full scan query.
185
+ * If you think you undesirable this, this method should be overridden to get the latest version in a different way.
186
+ * ex)
187
+ * using cache store latest version.
188
+ * using model for version information.
189
+
190
+ ## Development
191
+
192
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `bin/console` for an interactive prompt that will allow you to experiment.
193
+
194
+ 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` to create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
195
+
196
+ ## Contributing
197
+
198
+ 1. Fork it ( https://github.com/[my-github-username]/activerecord_data_importer/fork )
199
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
200
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
201
+ 4. Push to the branch (`git push origin my-new-feature`)
202
+ 5. Create a new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+
@@ -0,0 +1,20 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'activerecord_data_importer/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "activerecord_data_importer"
8
+ spec.version = ActiverecordDataImporter::VERSION
9
+ spec.authors = ["ukayare"]
10
+ spec.email = ["ukayare@gmail.com"]
11
+
12
+ spec.summary = %q{Data in file (csv or json) load to ActiveRecord schema mapping object extension}
13
+ spec.description = %q{ActiverecordDataImporter is enable to load and import file (format is csv, json) for ActiveRecord's model. For example, when you must use and import master data(ex. game character, enemy, and item stc), it will help you. }
14
+ spec.homepage = "https://github.com/ukayare/activerecord_data_importer"
15
+
16
+ spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
17
+ spec.bindir = "exe"
18
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
19
+ spec.require_paths = ["lib"]
20
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "activerecord_data_importer"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,7 @@
1
+ #!/bin/bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+
5
+ bundle install
6
+
7
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,140 @@
1
+ require "active_support/concern"
2
+ require "activerecord_data_importer/version"
3
+ require "activerecord_data_importer/csv_converter"
4
+
5
+ module ActiverecordDataImporter
6
+ extend ActiveSupport::Concern
7
+
8
+ module ClassMethods
9
+ def import_from_csv(filename)
10
+ csv_data = ActiverecordDataImporter::CsvConverter.convert_csv_to_hash(filename)
11
+ self.import_data csv_data
12
+ end
13
+
14
+ def import_from_json(filename)
15
+ json_data = self.load_json filename
16
+ self.import_data json_data
17
+ end
18
+
19
+ def divide_attributes(row_data)
20
+ row_data.partition { |k, _v| self.attribute_names.include? k }.map(&:to_h)
21
+ end
22
+
23
+ def divide_relation_key(another_attributes)
24
+ relation_keys = self.reflect_on_all_associations.map(&:name).map(&:to_s)
25
+ another_attributes.select { |key, value| relation_keys.include? key }
26
+ end
27
+
28
+ def get_latest_version
29
+ # note
30
+ # This method is published full scan query.
31
+ # If you think you undesirable this, this method should be overridden to get the latest version in a different way.
32
+ # ex)
33
+ # * using cache store latest version.
34
+ # * using model for version information.
35
+ if self.attribute_names.include? "version"
36
+ self.maximum("version").to_i
37
+ else
38
+ 0
39
+ end
40
+ end
41
+
42
+ protected
43
+ def load_json(filename)
44
+ json_data = open(filename) { |io| JSON.load io }
45
+ end
46
+
47
+ def import_data(json_data)
48
+ latest_version = self.get_latest_version
49
+ case json_data
50
+ when Array
51
+ json_data.select do |row_data|
52
+ self.import_row_data row_data, latest_version
53
+ end.present?
54
+ when Hash
55
+ self.import_row_data json_data, latest_version
56
+ else
57
+ false
58
+ end
59
+ end
60
+
61
+ def import_row_data(row_data, latest_version)
62
+ record_instance = if row_data["id"]
63
+ self.where(id: row_data["id"]).first_or_initialize
64
+ else
65
+ self.new
66
+ end
67
+ record_instance.update_row_data row_data, latest_version
68
+ end
69
+ end
70
+
71
+ def update_row_data(row_data, latest_version)
72
+ origin_updated = false
73
+ relation_updated = false
74
+ self_attributes, another_attributes = self.class.divide_attributes row_data
75
+ origin_updated = self.update_origin self_attributes, latest_version
76
+ return origin_updated if another_attributes.blank?
77
+ relation_updated = self.update_relation another_attributes
78
+ if (!origin_updated && relation_updated)
79
+ self.update_with_version latest_version
80
+ true
81
+ else
82
+ false
83
+ end
84
+ end
85
+
86
+ def update_origin(self_attributes, latest_version)
87
+ self_attributes
88
+ self.attributes = self_attributes
89
+ return false unless self.changed?
90
+ self.update_with_version latest_version
91
+ true
92
+ end
93
+
94
+ def update_relation(another_attributes)
95
+ latest_version = self.class.get_latest_version
96
+ self.class.divide_relation_key(another_attributes).select do |key, value|
97
+ case value
98
+ when Hash
99
+ relation_instance = self.try(key) || self.try("build_#{key}")
100
+ relation_instance.update_row_data value, latest_version
101
+ when Array
102
+ relation_instances = self.try(key).to_a
103
+ updated_flag = false
104
+ value.each_with_index do |relation_attributes, i|
105
+ relation_instance = relation_instances[i] || self.try(key).build
106
+ relation_updated = relation_instance.update_row_data relation_attributes, latest_version
107
+ updated_flag ||= relation_updated
108
+ end
109
+ relation_deleted = self.delete_remain_relation(relation_instances[value.size..-1])
110
+ updated_flag || relation_deleted
111
+ else
112
+ false
113
+ end
114
+ end.present?
115
+ end
116
+
117
+
118
+ def update_with_version(latest_version)
119
+ if self.respond_to?(:version)
120
+ self.version = latest_version + 1
121
+ end
122
+ if self.class.respond_to? :with_writable
123
+ self.class.with_writable { self.save }
124
+ else
125
+ self.save
126
+ end
127
+ end
128
+
129
+ def delete_remain_relation(remain_instances)
130
+ return false if remain_instances.blank?
131
+ remain_instances.each do |remain_instance|
132
+ if self.class.respond_to? :with_writable
133
+ remain_instance.class.with_writable { remain_instance.destroy }
134
+ else
135
+ remain_instance.destroy
136
+ end
137
+ end
138
+ true
139
+ end
140
+ end
@@ -0,0 +1,39 @@
1
+ require "csv"
2
+ class ActiverecordDataImporter::CsvConverter
3
+ def self.convert_csv_to_hash(filename)
4
+ csvs = CSV.table(filename, header_converters: :downcase)
5
+ hash = csvs.map do |csv|
6
+ self.convert_attributes csv.to_h
7
+ end
8
+ hash
9
+ end
10
+
11
+ private
12
+
13
+ def self.convert_attributes(csv)
14
+ returned_csv = csv.to_h.dup
15
+ csv.to_h.select { |k, _v| k.include? "." }.group_by { |k, _v| k.split(".").first }.each do |k, v|
16
+ relation_key = k.gsub(/[0-9]+/, "")
17
+ attributes = v.map { |key, value|[key.gsub(/#{k}\./, ""), value] }.to_h
18
+ relation_attributes = attributes.select { |key, _v| key.include? "." }
19
+ if relation_attributes.present?
20
+ attributes.merge!(self.convert_attributes relation_attributes)
21
+ relation_attributes.each do |key, _value|
22
+ attributes.delete key
23
+ returned_csv.delete "#{k}.#{key}"
24
+ end
25
+ end
26
+ if relation_key == k
27
+ returned_csv.merge! relation_key => attributes
28
+ else
29
+ if returned_csv.key? relation_key
30
+ returned_csv[relation_key] << attributes
31
+ else
32
+ returned_csv[relation_key] = [attributes]
33
+ end
34
+ end
35
+ attributes.each { |key, value| returned_csv.delete "#{k}.#{key}" }
36
+ end
37
+ returned_csv
38
+ end
39
+ end
@@ -0,0 +1,3 @@
1
+ module ActiverecordDataImporter
2
+ VERSION = "0.1.0"
3
+ end
metadata ADDED
@@ -0,0 +1,59 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord_data_importer
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - ukayare
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2015-09-29 00:00:00.000000000 Z
12
+ dependencies: []
13
+ description: 'ActiverecordDataImporter is enable to load and import file (format is
14
+ csv, json) for ActiveRecord''s model. For example, when you must use and import
15
+ master data(ex. game character, enemy, and item stc), it will help you. '
16
+ email:
17
+ - ukayare@gmail.com
18
+ executables: []
19
+ extensions: []
20
+ extra_rdoc_files: []
21
+ files:
22
+ - ".gitignore"
23
+ - ".rspec"
24
+ - ".travis.yml"
25
+ - CODE_OF_CONDUCT.md
26
+ - Gemfile
27
+ - LICENSE.txt
28
+ - README.md
29
+ - Rakefile
30
+ - activerecord_data_importer.gemspec
31
+ - bin/console
32
+ - bin/setup
33
+ - lib/activerecord_data_importer.rb
34
+ - lib/activerecord_data_importer/csv_converter.rb
35
+ - lib/activerecord_data_importer/version.rb
36
+ homepage: https://github.com/ukayare/activerecord_data_importer
37
+ licenses: []
38
+ metadata: {}
39
+ post_install_message:
40
+ rdoc_options: []
41
+ require_paths:
42
+ - lib
43
+ required_ruby_version: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ required_rubygems_version: !ruby/object:Gem::Requirement
49
+ requirements:
50
+ - - ">="
51
+ - !ruby/object:Gem::Version
52
+ version: '0'
53
+ requirements: []
54
+ rubyforge_project:
55
+ rubygems_version: 2.4.5
56
+ signing_key:
57
+ specification_version: 4
58
+ summary: Data in file (csv or json) load to ActiveRecord schema mapping object extension
59
+ test_files: []