mass_record 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/MIT-LICENSE +20 -0
- data/README.rdoc +3 -0
- data/Rakefile +34 -0
- data/app/assets/javascripts/mass_record/application.js +13 -0
- data/app/assets/stylesheets/mass_record/application.css +15 -0
- data/app/controllers/mass_record/application_controller.rb +4 -0
- data/app/helpers/mass_record/application_helper.rb +4 -0
- data/app/views/layouts/mass_record/application.html.erb +14 -0
- data/config/routes.rb +2 -0
- data/lib/mass_record/engine.rb +5 -0
- data/lib/mass_record/version.rb +3 -0
- data/lib/mass_record.rb +373 -0
- data/lib/tasks/mass_record_tasks.rake +4 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/config/application.rb +23 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +37 -0
- data/test/dummy/config/environments/production.rb +78 -0
- data/test/dummy/config/environments/test.rb +39 -0
- data/test/dummy/config/initializers/assets.rb +8 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/integration/navigation_test.rb +10 -0
- data/test/mass_record_test.rb +7 -0
- data/test/test_helper.rb +15 -0
- metadata +157 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 054417da2bfb6c27e10c6cab4a43749a4e7c5928
|
4
|
+
data.tar.gz: 2cc23822c8fadd28876fa92ddeedd0d55d020611
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 796999b036af5527ac05d769d037f46ec45d22cca212348fe012dd147e736f047792ef2f98858b8d94a565db16e665127e9f77583c969c6e7243af2f9478892d
|
7
|
+
data.tar.gz: 03a884f22b6df45f46fe11cc0a192b115a86a22302015abc434792ab89c64faa7037b17e1962755a2f3cc9a715ace191536340bcee17a2d6955c5b236bde2aff
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2014 YOURNAME
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.rdoc
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,34 @@
|
|
1
|
+
begin
|
2
|
+
require 'bundler/setup'
|
3
|
+
rescue LoadError
|
4
|
+
puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
|
5
|
+
end
|
6
|
+
|
7
|
+
require 'rdoc/task'
|
8
|
+
|
9
|
+
RDoc::Task.new(:rdoc) do |rdoc|
|
10
|
+
rdoc.rdoc_dir = 'rdoc'
|
11
|
+
rdoc.title = 'MassRecord'
|
12
|
+
rdoc.options << '--line-numbers'
|
13
|
+
rdoc.rdoc_files.include('README.rdoc')
|
14
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
15
|
+
end
|
16
|
+
|
17
|
+
APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
|
18
|
+
load 'rails/tasks/engine.rake'
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
Bundler::GemHelper.install_tasks
|
23
|
+
|
24
|
+
require 'rake/testtask'
|
25
|
+
|
26
|
+
Rake::TestTask.new(:test) do |t|
|
27
|
+
t.libs << 'lib'
|
28
|
+
t.libs << 'test'
|
29
|
+
t.pattern = 'test/**/*_test.rb'
|
30
|
+
t.verbose = false
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
task default: :test
|
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>MassRecord</title>
|
5
|
+
<%= stylesheet_link_tag "mass_record/application", media: "all" %>
|
6
|
+
<%= javascript_include_tag "mass_record/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
data/config/routes.rb
ADDED
data/lib/mass_record.rb
ADDED
@@ -0,0 +1,373 @@
|
|
1
|
+
require "mass_record/engine"
|
2
|
+
|
3
|
+
module MassRecord
|
4
|
+
mattr_accessor :path, :folder_path, :database_connection
|
5
|
+
self.path = {}
|
6
|
+
self.folder_path = "tmp/#{Rails.env}"
|
7
|
+
self.path[:queries] = "tmp/#{Rails.env}"
|
8
|
+
self.path[:queued_queries] = "tmp/#{Rails.env}"
|
9
|
+
self.path[:errored_queries] = "tmp/#{Rails.env}"
|
10
|
+
self.path[:completed_queries] = "tmp/#{Rails.env}"
|
11
|
+
|
12
|
+
module Actions
|
13
|
+
path = {}
|
14
|
+
folder_path = "tmp/#{Rails.env}"
|
15
|
+
path[:queries] = "#{folder_path}/queries"
|
16
|
+
path[:queued_queries] = "#{path[:queries]}/queued"
|
17
|
+
path[:errored_queries] = "#{path[:queries]}/errored"
|
18
|
+
path[:completed_queries] = "#{path[:queries]}/completed"
|
19
|
+
|
20
|
+
# accepts an array of objects with the option to specify what rails operation to perform
|
21
|
+
def queue_for_quick_query object_array,
|
22
|
+
operation: :save,
|
23
|
+
folder:{queued:path[:queued_queries]},
|
24
|
+
file_tag:Time.now.strftime("%Y%m%d%H%M%S").to_s,
|
25
|
+
key:{
|
26
|
+
table: "table",
|
27
|
+
operation: "operation",
|
28
|
+
object: "object"
|
29
|
+
}
|
30
|
+
|
31
|
+
object_array = [object_array] unless object_array.is_a? Array
|
32
|
+
queue = []
|
33
|
+
|
34
|
+
object_array.each do |object|
|
35
|
+
queue << {key[:table] => object.class.table_name, key[:operation] => operation, key[:object] => object} unless object.blank?
|
36
|
+
end
|
37
|
+
|
38
|
+
File.open(folder[:queued]+"/#{operation.to_s}_#{file_tag}.json",'w'){|f| f.write queue.to_json}
|
39
|
+
end
|
40
|
+
|
41
|
+
def execute_queued_queries key:{
|
42
|
+
operation:"operation",
|
43
|
+
table:"table",
|
44
|
+
object:"object"
|
45
|
+
},synonyms:{
|
46
|
+
insert: [:create, :new, :add, :insert, :post],
|
47
|
+
update: [:update,:edit,:modify,:put,:patch],
|
48
|
+
select: [:read,:select,:get]
|
49
|
+
},folder:{
|
50
|
+
queued:path[:queued_queries],
|
51
|
+
errored:path[:errored_queries],
|
52
|
+
completed:path[:completed_queries]
|
53
|
+
},file_tag:Time.now.strftime("%Y%m%d%H%M%S").to_s
|
54
|
+
|
55
|
+
files = Dir.foreach(folder[:queued]).collect{|x| x}.keep_if{|y|y=~/\.json$/i}
|
56
|
+
json_objects = []
|
57
|
+
|
58
|
+
# rename to avoid double processing
|
59
|
+
files.each{|x| File.rename "#{folder[:queued]}/#{x}","#{folder[:queued]}/#{x}.processing"}
|
60
|
+
|
61
|
+
# load all the json
|
62
|
+
files.each do |file|
|
63
|
+
File.open("#{folder[:queued]}/#{file}.processing",'r') do |f|
|
64
|
+
json = JSON.parse(f.read)
|
65
|
+
json_objects += json
|
66
|
+
end
|
67
|
+
end
|
68
|
+
|
69
|
+
# validate all objects
|
70
|
+
json_objects = mass_validate json_objects
|
71
|
+
|
72
|
+
# get all operations and tables in use
|
73
|
+
operations = json_objects.collect{|x| x[key[:operation]].to_sym}.to_set.to_a
|
74
|
+
|
75
|
+
# open database connection
|
76
|
+
database_connection = ActiveRecord::Base.connection
|
77
|
+
|
78
|
+
# construct mass queries
|
79
|
+
errors = {}
|
80
|
+
operations.each do |op|
|
81
|
+
if synonyms[:insert].include? op
|
82
|
+
errors[:insert] = mass_insert_by_table json_objects.select{|x| synonyms[:insert].include? x[key[:operation]].to_sym.downcase}, key:key
|
83
|
+
elsif synonyms[:update].include? op
|
84
|
+
errors[:update] = mass_update_by_table json_objects.select{|x| synonyms[:update].include? x[key[:operation]].to_sym.downcase}, key:key
|
85
|
+
elsif op == :save # needs to intelligently determine if the order already exists, insert if not, update if so
|
86
|
+
errors[:save] = mass_save_by_table json_objects.select{|x| :save == x[key[:operation]].to_sym.downcase}, key:key
|
87
|
+
elsif op == :delete
|
88
|
+
elsif synonyms[:select].include? op
|
89
|
+
else
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
# close database connection
|
94
|
+
database_connection.close
|
95
|
+
|
96
|
+
# move to appropriate folder and remove '.processing' from the filename
|
97
|
+
files = Dir.foreach(folder[:queued]).collect{|x| x}.keep_if{|y|y=~/\.json\.processing$/i}
|
98
|
+
errors_present = errors.any?{|op,h| h.any?{|table,a| a.count > 0 }}
|
99
|
+
files.each{|x| File.rename "#{folder[:queued]}/#{x}","#{errors_present ? folder[:errored] : folder[:completed]}/group_#{file_tag}_#{x.gsub /\.processing$/,''}"}
|
100
|
+
|
101
|
+
return errors
|
102
|
+
end
|
103
|
+
|
104
|
+
def mass_validate objects
|
105
|
+
# TODO: write logic, should return only valid objects
|
106
|
+
return objects
|
107
|
+
end
|
108
|
+
|
109
|
+
def mass_update_by_table json_objects, key:{}
|
110
|
+
begin
|
111
|
+
tables = json_objects.collect{|x| x[key[:table]]}.to_set.to_a
|
112
|
+
|
113
|
+
errors = {}
|
114
|
+
tables.each do |table|
|
115
|
+
hashes = json_objects.select{|o| o[key[:table]] == table}.collect{|x| x[key[:object]]}
|
116
|
+
errors[table.to_sym] = mass_update hashes, into:table
|
117
|
+
end
|
118
|
+
return errors
|
119
|
+
rescue Exception => e
|
120
|
+
return {method:e} unless defined? errors
|
121
|
+
errors[:method] = e if defined? errors
|
122
|
+
return errors
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
def mass_save_by_table json_objects, key:{}
|
127
|
+
begin
|
128
|
+
tables = json_objects.collect{|x| x[key[:table]]}.to_set.to_a
|
129
|
+
|
130
|
+
errors = {}
|
131
|
+
tables.each do |table|
|
132
|
+
hashes = json_objects.select{|o| o[key[:table]] == table}.collect{|x| x[key[:object]]}
|
133
|
+
model = table.classify.constantize
|
134
|
+
pk = model.primary_key
|
135
|
+
|
136
|
+
# organize hashes based on whether they exist (based on their primary key(s)) in the table or not
|
137
|
+
if pk.is_a? Array
|
138
|
+
ids = hashes.reject{|x| pk.any?{|k| x[k].blank?}}.collect{|x| x.select{|k,v| pk.include? k}} # only accept full sets of pk's
|
139
|
+
where_clauses = []
|
140
|
+
ids.each do |id|
|
141
|
+
equivalence_clauses = []
|
142
|
+
id.each do |k,v|
|
143
|
+
equivalence_clauses << "#{ActiveRecord::Base.connection.quote_column_name k} = #{ActiveRecord::Base.connection.quote(ActiveRecord::Base.connection.type_cast(v, model.column_types[k]))}"
|
144
|
+
end
|
145
|
+
where_clauses << "(#{equivalence_clauses.join ' and '})"
|
146
|
+
end
|
147
|
+
existing_id_sets = ActiveRecord::Base.connection.execute("SELECT #{pk.join ', '} FROM #{table} WHERE #{where_clauses.join ' OR '}").collect{|x| Hash[x.map.with_index{|x,i| [pk[i],x]}]}
|
148
|
+
insert_hashes = hashes.reject{|h| existing_id_sets.any?{|set| h == h.merge(set)}}
|
149
|
+
update_hashes = hashes.select{|h| existing_id_sets.any?{|set| h == h.merge(set)}}
|
150
|
+
else
|
151
|
+
ids = hashes.reject{|x| x[pk].blank?}.collect{|x| x[pk]} # should not include null values
|
152
|
+
existing_ids = ActiveRecord::Base.connection.execute("SELECT #{pk} FROM #{table} WHERE #{pk} in ('#{ids.join "','"}')").collect{|x| x.first.to_s}
|
153
|
+
insert_hashes = hashes.reject{|x| existing_ids.include? x[pk].to_s}
|
154
|
+
update_hashes = hashes.select{|x| existing_ids.include? x[pk].to_s}
|
155
|
+
end
|
156
|
+
|
157
|
+
# perform the appropriate operations
|
158
|
+
errors[table.to_sym] = []
|
159
|
+
errors[table.to_sym] += mass_update update_hashes, into:model unless update_hashes.blank?
|
160
|
+
errors[table.to_sym] += mass_insert insert_hashes, into:model unless insert_hashes.blank?
|
161
|
+
end
|
162
|
+
return errors
|
163
|
+
rescue Exception => e
|
164
|
+
return {method:e} unless defined? errors
|
165
|
+
errors[:method] = e if defined? errors
|
166
|
+
return errors
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def mass_update hashes, into:nil
|
171
|
+
begin
|
172
|
+
return false if hashes.blank? or into.blank?
|
173
|
+
|
174
|
+
model = into.is_a?(String) ? into.classify.constantize : into
|
175
|
+
id_column_name = model.primary_key
|
176
|
+
created_at = model.attribute_alias?("created_at") ? model.attribute_alias("created_at") : "created_at"
|
177
|
+
updated_at = model.attribute_alias?("updated_at") ? model.attribute_alias("updated_at") : "updated_at"
|
178
|
+
solitary_queries = []
|
179
|
+
t = model.arel_table
|
180
|
+
|
181
|
+
# organize by unique column sets
|
182
|
+
unique_column_sets = {}
|
183
|
+
|
184
|
+
hashes.each do |hash|
|
185
|
+
column_set = hash.keys.sort
|
186
|
+
unique_column_sets[column_set] = [] unless unique_column_sets.has_key? column_set and unique_column_sets[column_set].is_a? Array
|
187
|
+
unique_column_sets[column_set] << hash
|
188
|
+
end
|
189
|
+
|
190
|
+
# assemble list of queries (1 for each unique set of columns)
|
191
|
+
queries = []
|
192
|
+
|
193
|
+
unique_column_sets.each do |column_set, hash_group|
|
194
|
+
if id_column_name.is_a? Array
|
195
|
+
ids = hash_group.collect{|hash| Hash[id_column_name.map.with_index{|column_name,i| [column_name,hash[column_name]] }]}
|
196
|
+
update = "UPDATE #{model.table_name} SET "
|
197
|
+
where_clauses = []
|
198
|
+
id_column_name.each do |key|
|
199
|
+
value_set = ids.collect{|id_set| ActiveRecord::Base.connection.quote(ActiveRecord::Base.connection.type_cast(id_set[key], model.column_types[key]))}
|
200
|
+
where_clauses << "(#{model.table_name}.#{key} in (#{value_set.join ','}))"
|
201
|
+
end
|
202
|
+
where = "WHERE #{where_clauses.join ' and '}"
|
203
|
+
|
204
|
+
set_fragments = {}
|
205
|
+
|
206
|
+
hash_group.each do |hash|
|
207
|
+
hash = convert_to_db_format hash, model:model
|
208
|
+
if id_column_name.all?{|column_name| hash.has_key? column_name} # if the hash has all primary keys
|
209
|
+
hash.each do |k,v|
|
210
|
+
unless id_column_name.include? k # don't allow the update of primary key columns
|
211
|
+
set_fragments[k] = [] unless set_fragments.has_key? k and set_fragments[k].is_a? Array
|
212
|
+
case_fragments = []
|
213
|
+
id_column_name.each do |key|
|
214
|
+
case_fragments << "#{ActiveRecord::Base.connection.quote_column_name key} = #{ActiveRecord::Base.connection.quote hash[key]}"
|
215
|
+
end
|
216
|
+
set_fragments[k] << "WHEN (#{case_fragments.join ' and '}) THEN #{ActiveRecord::Base.connection.quote v}"
|
217
|
+
end
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
else
|
222
|
+
ids = hash_group.collect{|x| x[id_column_name]}
|
223
|
+
update = "UPDATE #{model.table_name} SET "
|
224
|
+
where = "WHERE #{model.table_name}.#{id_column_name} in ('#{ids.join("','")}')"
|
225
|
+
|
226
|
+
set_fragments = {}
|
227
|
+
|
228
|
+
hash_group.each do |hash|
|
229
|
+
hash = convert_to_db_format hash, model:model # TODO: adapt the method to work nicely with updates (ie- don't overwrite the created_at)
|
230
|
+
if hash.has_key? id_column_name
|
231
|
+
hash.each do |k,v|
|
232
|
+
if k != id_column_name
|
233
|
+
set_fragments[k] = [] unless set_fragments.has_key? k and set_fragments[k].is_a? Array
|
234
|
+
set_fragments[k] << "WHEN #{ActiveRecord::Base.connection.quote hash[id_column_name]} THEN #{ActiveRecord::Base.connection.quote v}"
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
240
|
+
|
241
|
+
set_columns = []
|
242
|
+
|
243
|
+
set_fragments.each do |column, values|
|
244
|
+
set_columns << "#{column} = CASE #{model.table_name}.#{id_column_name} #{values.join ' '} END" unless id_column_name.is_a? Array
|
245
|
+
set_columns << "#{column} = CASE #{values.join ' '} END" if id_column_name.is_a? Array
|
246
|
+
end
|
247
|
+
|
248
|
+
queries << "#{update} #{set_columns.join ', '} #{where}"
|
249
|
+
end
|
250
|
+
# [{"id"=>545, "header"=>"new system","details"=>"ya, it worked"},{"id"=>546, "header"=>"sweet system"},{"id"=>547, "header"=>"THAT system","details"=>"ya, it worked"}]
|
251
|
+
errors = []
|
252
|
+
# execute the queries
|
253
|
+
queries.each do |sql|
|
254
|
+
begin
|
255
|
+
query sql
|
256
|
+
rescue Exception => e
|
257
|
+
puts e.message
|
258
|
+
errors << e
|
259
|
+
end
|
260
|
+
end
|
261
|
+
return errors
|
262
|
+
rescue Exception => e
|
263
|
+
return (defined? errors) ? (errors << e) : [e]
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
def mass_insert_by_table json_objects, key:{}
|
268
|
+
begin
|
269
|
+
tables = json_objects.collect{|x| x[key[:table]]}.to_set.to_a
|
270
|
+
|
271
|
+
errors = {}
|
272
|
+
tables.each do |table|
|
273
|
+
hashes = json_objects.select{|o| o[key[:table]] == table}.collect{|x| x[key[:object]]}
|
274
|
+
errors[table.to_sym] = mass_insert hashes, into:table
|
275
|
+
end
|
276
|
+
return errors
|
277
|
+
rescue Exception => e
|
278
|
+
return {method:e} unless defined? errors
|
279
|
+
errors[:method] = e if defined? errors
|
280
|
+
return errors
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
def mass_insert hashes, into:nil
|
285
|
+
begin
|
286
|
+
return false if hashes.blank? or into.blank?
|
287
|
+
|
288
|
+
# create an array of single insert queries
|
289
|
+
model = into.is_a?(String) ? into.classify.constantize : into
|
290
|
+
id_column_name = model.primary_key
|
291
|
+
created_at = model.attribute_alias?("created_at") ? model.attribute_alias("created_at") : "created_at"
|
292
|
+
updated_at = model.attribute_alias?("updated_at") ? model.attribute_alias("updated_at") : "updated_at"
|
293
|
+
solitary_queries = []
|
294
|
+
t = model.arel_table
|
295
|
+
|
296
|
+
hashes.each do |h|
|
297
|
+
im = Arel::InsertManager.new(ActiveRecord::Base)
|
298
|
+
unless id_column_name.is_a? Array # don't modify the id fields if there are concatenated primary keys
|
299
|
+
h.delete id_column_name if model.columns.select{|x| x.name == id_column_name}.first.extra == 'auto_increment' or h[id_column_name].blank?
|
300
|
+
end
|
301
|
+
h = convert_to_db_format h, model:model, created_at:created_at, updated_at:updated_at
|
302
|
+
pairs = h.collect do |k,v|
|
303
|
+
[t[k.to_sym],v]
|
304
|
+
end
|
305
|
+
im.insert pairs
|
306
|
+
solitary_queries << im.to_sql
|
307
|
+
end
|
308
|
+
|
309
|
+
# group the queries by unique column lists
|
310
|
+
concentrated_queries = {}
|
311
|
+
|
312
|
+
solitary_queries.each do |q|
|
313
|
+
k = q.gsub /\s*VALUES.*$/,''
|
314
|
+
concentrated_queries[k] = [] unless concentrated_queries.has_key? k and concentrated_queries[k].is_a? Array
|
315
|
+
concentrated_queries[k] << q.gsub(/^.*VALUES\s*/,'')
|
316
|
+
end
|
317
|
+
|
318
|
+
errors = []
|
319
|
+
# reparse the queries and execute them
|
320
|
+
concentrated_queries.each do |k,v|
|
321
|
+
begin
|
322
|
+
query "#{k} VALUES #{v.join(", ")}"
|
323
|
+
rescue Exception => e
|
324
|
+
puts e.message
|
325
|
+
errors << e
|
326
|
+
end
|
327
|
+
end
|
328
|
+
return errors
|
329
|
+
rescue Exception => e
|
330
|
+
return (defined? errors) ? (errors << e) : [e]
|
331
|
+
end
|
332
|
+
end
|
333
|
+
|
334
|
+
|
335
|
+
def convert_to_db_format json_object, model:nil, created_at:'created_at', updated_at:'updated_at'
|
336
|
+
throw "No Model provided, cannot format the data for the specified columns" if model.blank?
|
337
|
+
|
338
|
+
json_date_regex = /^"?\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}[\+\-]\d{2}:\d{2}"?$/i
|
339
|
+
time = Time.now.to_s(:db)
|
340
|
+
crud_times = [created_at,updated_at]
|
341
|
+
json_object.each do |k,v|
|
342
|
+
v = Time.parse v if v.is_a? String and v =~ json_date_regex # fix funky to_json format if present
|
343
|
+
v = time if crud_times.include? k and (v.blank? or k == updated_at) and model.column_names.include? k # add crud time if it is blank and it is a column in the model or if it is update_at just add the time
|
344
|
+
|
345
|
+
# convert to correct database type
|
346
|
+
begin
|
347
|
+
v = ActiveRecord::Base.connection.type_cast v, model.column_types[k]
|
348
|
+
rescue Exception => e # If it is a text field, automatically yamlize it if there is a non text type passed in (just like normal active record saves)
|
349
|
+
v = ActiveRecord::Base.connection.type_cast v.to_yaml, model.column_types[k] if e.is_a? TypeError and model.column_types[k].type == :text
|
350
|
+
end
|
351
|
+
json_object[k] = v
|
352
|
+
end
|
353
|
+
|
354
|
+
return json_object
|
355
|
+
end
|
356
|
+
|
357
|
+
def query sql
|
358
|
+
if database_connection.blank?
|
359
|
+
res = ActiveRecord::Base.connection.execute sql
|
360
|
+
ActiveRecord::Base.connection.close
|
361
|
+
else
|
362
|
+
res = database_connection.execute sql
|
363
|
+
end
|
364
|
+
|
365
|
+
return res
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
class << self
|
370
|
+
include Actions
|
371
|
+
end
|
372
|
+
|
373
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
== README
|
2
|
+
|
3
|
+
This README would normally document whatever steps are necessary to get the
|
4
|
+
application up and running.
|
5
|
+
|
6
|
+
Things you may want to cover:
|
7
|
+
|
8
|
+
* Ruby version
|
9
|
+
|
10
|
+
* System dependencies
|
11
|
+
|
12
|
+
* Configuration
|
13
|
+
|
14
|
+
* Database creation
|
15
|
+
|
16
|
+
* Database initialization
|
17
|
+
|
18
|
+
* How to run the test suite
|
19
|
+
|
20
|
+
* Services (job queues, cache servers, search engines, etc.)
|
21
|
+
|
22
|
+
* Deployment instructions
|
23
|
+
|
24
|
+
* ...
|
25
|
+
|
26
|
+
|
27
|
+
Please feel free to use a different markup language if you do not plan to run
|
28
|
+
<tt>rake doc:app</tt>.
|
data/test/dummy/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or vendor/assets/stylesheets of plugins, if any, can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>Dummy</title>
|
5
|
+
<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
|
6
|
+
<%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
data/test/dummy/bin/rake
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
require File.expand_path('../boot', __FILE__)
|
2
|
+
|
3
|
+
require 'rails/all'
|
4
|
+
|
5
|
+
Bundler.require(*Rails.groups)
|
6
|
+
require "mass_record"
|
7
|
+
|
8
|
+
module Dummy
|
9
|
+
class Application < Rails::Application
|
10
|
+
# Settings in config/environments/* take precedence over those specified here.
|
11
|
+
# Application configuration should go into files in config/initializers
|
12
|
+
# -- all .rb files in that directory are automatically loaded.
|
13
|
+
|
14
|
+
# Set Time.zone default to the specified zone and make Active Record auto-convert to this zone.
|
15
|
+
# Run "rake -D time" for a list of tasks for finding time zone names. Default is UTC.
|
16
|
+
# config.time_zone = 'Central Time (US & Canada)'
|
17
|
+
|
18
|
+
# The default locale is :en and all translations from config/locales/*.rb,yml are auto loaded.
|
19
|
+
# config.i18n.load_path += Dir[Rails.root.join('my', 'locales', '*.{rb,yml}').to_s]
|
20
|
+
# config.i18n.default_locale = :de
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|