model_to_googlesheet 1.0.0
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/Gemfile +1 -0
- data/Gemfile.lock +195 -0
- data/README.md +67 -0
- data/Rakefile +12 -0
- data/lib/model_to_googlesheet/export.rb +90 -0
- data/lib/model_to_googlesheet/google_drive/authentication_helper.rb +18 -0
- data/lib/model_to_googlesheet/google_drive/session.rb +31 -0
- data/lib/model_to_googlesheet/google_drive/spreadsheet.rb +18 -0
- data/lib/model_to_googlesheet/google_drive/worksheet.rb +22 -0
- data/lib/model_to_googlesheet/railtie.rb +24 -0
- data/lib/model_to_googlesheet/tasks/get_refresh_token.rake +22 -0
- data/lib/model_to_googlesheet/version.rb +3 -0
- data/lib/model_to_googlesheet.rb +72 -0
- data/model_to_googlesheet.gemspec +39 -0
- data/spec/export_spec.rb +99 -0
- data/spec/helpers/active_record_helper.rb +23 -0
- data/spec/session_spec.rb +49 -0
- data/spec/spec_helper.rb +61 -0
- data/spec/support/create_session.rb +22 -0
- data/spec/support/set_spreadsheets.rb +12 -0
- metadata +166 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: c153b7c2f6b311f25757ffcc28274b96fa8a5ff1
|
4
|
+
data.tar.gz: 54c810e9e834270f737a7581bff1773e7bbcee2b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 90bab47ac2e13357273686ee38cf27b49e96ef99cebb990bf146baef739dc71498adb8bf2308a5af8ef033286228b5302e926fe8e359c756b4bbe6e7d95b5327
|
7
|
+
data.tar.gz: 9263823d000b920b56250085a960e4105190fa36daf0b931d51315e621c780d3ad8ca05829256a8ae8435525fd96d23a2c7915f5a9bcaf78f22e2fde705862eb
|
data/Gemfile
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
gemspec
|
data/Gemfile.lock
ADDED
@@ -0,0 +1,195 @@
|
|
1
|
+
PATH
|
2
|
+
remote: .
|
3
|
+
specs:
|
4
|
+
model_to_googlesheet (1.0.0)
|
5
|
+
activerecord
|
6
|
+
google_drive (~> 1.0)
|
7
|
+
rails (~> 4.2)
|
8
|
+
|
9
|
+
GEM
|
10
|
+
specs:
|
11
|
+
actionmailer (4.2.5)
|
12
|
+
actionpack (= 4.2.5)
|
13
|
+
actionview (= 4.2.5)
|
14
|
+
activejob (= 4.2.5)
|
15
|
+
mail (~> 2.5, >= 2.5.4)
|
16
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
17
|
+
actionpack (4.2.5)
|
18
|
+
actionview (= 4.2.5)
|
19
|
+
activesupport (= 4.2.5)
|
20
|
+
rack (~> 1.6)
|
21
|
+
rack-test (~> 0.6.2)
|
22
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
23
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
24
|
+
actionview (4.2.5)
|
25
|
+
activesupport (= 4.2.5)
|
26
|
+
builder (~> 3.1)
|
27
|
+
erubis (~> 2.7.0)
|
28
|
+
rails-dom-testing (~> 1.0, >= 1.0.5)
|
29
|
+
rails-html-sanitizer (~> 1.0, >= 1.0.2)
|
30
|
+
activejob (4.2.5)
|
31
|
+
activesupport (= 4.2.5)
|
32
|
+
globalid (>= 0.3.0)
|
33
|
+
activemodel (4.2.5)
|
34
|
+
activesupport (= 4.2.5)
|
35
|
+
builder (~> 3.1)
|
36
|
+
activerecord (4.2.5)
|
37
|
+
activemodel (= 4.2.5)
|
38
|
+
activesupport (= 4.2.5)
|
39
|
+
arel (~> 6.0)
|
40
|
+
activesupport (4.2.5)
|
41
|
+
i18n (~> 0.7)
|
42
|
+
json (~> 1.7, >= 1.7.7)
|
43
|
+
minitest (~> 5.1)
|
44
|
+
thread_safe (~> 0.3, >= 0.3.4)
|
45
|
+
tzinfo (~> 1.1)
|
46
|
+
addressable (2.4.0)
|
47
|
+
arel (6.0.3)
|
48
|
+
autoparse (0.3.3)
|
49
|
+
addressable (>= 2.3.1)
|
50
|
+
extlib (>= 0.9.15)
|
51
|
+
multi_json (>= 1.0.0)
|
52
|
+
builder (3.2.2)
|
53
|
+
coderay (1.1.0)
|
54
|
+
concurrent-ruby (1.0.0)
|
55
|
+
diff-lcs (1.2.5)
|
56
|
+
erubis (2.7.0)
|
57
|
+
extlib (0.9.16)
|
58
|
+
faraday (0.9.2)
|
59
|
+
multipart-post (>= 1.2, < 3)
|
60
|
+
globalid (0.3.6)
|
61
|
+
activesupport (>= 4.1.0)
|
62
|
+
google-api-client (0.8.6)
|
63
|
+
activesupport (>= 3.2)
|
64
|
+
addressable (~> 2.3)
|
65
|
+
autoparse (~> 0.3)
|
66
|
+
extlib (~> 0.9)
|
67
|
+
faraday (~> 0.9)
|
68
|
+
googleauth (~> 0.3)
|
69
|
+
launchy (~> 2.4)
|
70
|
+
multi_json (~> 1.10)
|
71
|
+
retriable (~> 1.4)
|
72
|
+
signet (~> 0.6)
|
73
|
+
google_drive (1.0.4)
|
74
|
+
google-api-client (>= 0.7.0, < 0.9)
|
75
|
+
nokogiri (>= 1.4.4, != 1.5.2, != 1.5.1)
|
76
|
+
oauth (>= 0.3.6)
|
77
|
+
oauth2 (>= 0.5.0)
|
78
|
+
googleauth (0.4.2)
|
79
|
+
faraday (~> 0.9)
|
80
|
+
jwt (~> 1.4)
|
81
|
+
logging (~> 2.0)
|
82
|
+
memoist (~> 0.12)
|
83
|
+
multi_json (~> 1.11)
|
84
|
+
signet (~> 0.6)
|
85
|
+
i18n (0.7.0)
|
86
|
+
interception (0.5)
|
87
|
+
json (1.8.3)
|
88
|
+
jwt (1.5.2)
|
89
|
+
launchy (2.4.3)
|
90
|
+
addressable (~> 2.3)
|
91
|
+
little-plugger (1.1.4)
|
92
|
+
logging (2.0.0)
|
93
|
+
little-plugger (~> 1.1)
|
94
|
+
multi_json (~> 1.10)
|
95
|
+
loofah (2.0.3)
|
96
|
+
nokogiri (>= 1.5.9)
|
97
|
+
mail (2.6.3)
|
98
|
+
mime-types (>= 1.16, < 3)
|
99
|
+
memoist (0.13.0)
|
100
|
+
method_source (0.8.2)
|
101
|
+
mime-types (2.99)
|
102
|
+
mini_portile2 (2.0.0)
|
103
|
+
minitest (5.8.3)
|
104
|
+
multi_json (1.11.2)
|
105
|
+
multi_xml (0.5.5)
|
106
|
+
multipart-post (2.0.0)
|
107
|
+
nokogiri (1.6.7)
|
108
|
+
mini_portile2 (~> 2.0.0.rc2)
|
109
|
+
oauth (0.4.7)
|
110
|
+
oauth2 (1.0.0)
|
111
|
+
faraday (>= 0.8, < 0.10)
|
112
|
+
jwt (~> 1.0)
|
113
|
+
multi_json (~> 1.3)
|
114
|
+
multi_xml (~> 0.5)
|
115
|
+
rack (~> 1.2)
|
116
|
+
pry (0.10.3)
|
117
|
+
coderay (~> 1.1.0)
|
118
|
+
method_source (~> 0.8.1)
|
119
|
+
slop (~> 3.4)
|
120
|
+
pry-rescue (1.4.2)
|
121
|
+
interception (>= 0.5)
|
122
|
+
pry
|
123
|
+
rack (1.6.4)
|
124
|
+
rack-test (0.6.3)
|
125
|
+
rack (>= 1.0)
|
126
|
+
rails (4.2.5)
|
127
|
+
actionmailer (= 4.2.5)
|
128
|
+
actionpack (= 4.2.5)
|
129
|
+
actionview (= 4.2.5)
|
130
|
+
activejob (= 4.2.5)
|
131
|
+
activemodel (= 4.2.5)
|
132
|
+
activerecord (= 4.2.5)
|
133
|
+
activesupport (= 4.2.5)
|
134
|
+
bundler (>= 1.3.0, < 2.0)
|
135
|
+
railties (= 4.2.5)
|
136
|
+
sprockets-rails
|
137
|
+
rails-deprecated_sanitizer (1.0.3)
|
138
|
+
activesupport (>= 4.2.0.alpha)
|
139
|
+
rails-dom-testing (1.0.7)
|
140
|
+
activesupport (>= 4.2.0.beta, < 5.0)
|
141
|
+
nokogiri (~> 1.6.0)
|
142
|
+
rails-deprecated_sanitizer (>= 1.0.1)
|
143
|
+
rails-html-sanitizer (1.0.2)
|
144
|
+
loofah (~> 2.0)
|
145
|
+
railties (4.2.5)
|
146
|
+
actionpack (= 4.2.5)
|
147
|
+
activesupport (= 4.2.5)
|
148
|
+
rake (>= 0.8.7)
|
149
|
+
thor (>= 0.18.1, < 2.0)
|
150
|
+
rake (10.4.2)
|
151
|
+
retriable (1.4.1)
|
152
|
+
rspec (3.4.0)
|
153
|
+
rspec-core (~> 3.4.0)
|
154
|
+
rspec-expectations (~> 3.4.0)
|
155
|
+
rspec-mocks (~> 3.4.0)
|
156
|
+
rspec-core (3.4.1)
|
157
|
+
rspec-support (~> 3.4.0)
|
158
|
+
rspec-expectations (3.4.0)
|
159
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
160
|
+
rspec-support (~> 3.4.0)
|
161
|
+
rspec-mocks (3.4.0)
|
162
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
163
|
+
rspec-support (~> 3.4.0)
|
164
|
+
rspec-support (3.4.1)
|
165
|
+
signet (0.7.0)
|
166
|
+
addressable (~> 2.3)
|
167
|
+
faraday (~> 0.9)
|
168
|
+
jwt (~> 1.5)
|
169
|
+
multi_json (~> 1.10)
|
170
|
+
slop (3.6.0)
|
171
|
+
sprockets (3.5.2)
|
172
|
+
concurrent-ruby (~> 1.0)
|
173
|
+
rack (> 1, < 3)
|
174
|
+
sprockets-rails (2.3.3)
|
175
|
+
actionpack (>= 3.0)
|
176
|
+
activesupport (>= 3.0)
|
177
|
+
sprockets (>= 2.8, < 4.0)
|
178
|
+
sqlite3 (1.3.11)
|
179
|
+
thor (0.19.1)
|
180
|
+
thread_safe (0.3.5)
|
181
|
+
tzinfo (1.2.2)
|
182
|
+
thread_safe (~> 0.1)
|
183
|
+
|
184
|
+
PLATFORMS
|
185
|
+
ruby
|
186
|
+
|
187
|
+
DEPENDENCIES
|
188
|
+
model_to_googlesheet!
|
189
|
+
pry
|
190
|
+
pry-rescue
|
191
|
+
rspec (~> 3.4)
|
192
|
+
sqlite3
|
193
|
+
|
194
|
+
BUNDLED WITH
|
195
|
+
1.10.6
|
data/README.md
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
###ModelToGooglesheet
|
2
|
+
|
3
|
+
###How to install?
|
4
|
+
|
5
|
+
gem 'model_to_googlesheet'
|
6
|
+
|
7
|
+
###How to obtain client_id, **client_secret** and **refresh_token**?
|
8
|
+
https://auth0.com/docs/connections/social/google
|
9
|
+
|
10
|
+
After creation, go to https://console.developers.google.com/apis/library?project=db-to-gs and enable Drive api.
|
11
|
+
|
12
|
+
Choose 'other' as an application type.
|
13
|
+
|
14
|
+
Once you get **client_id** and **session_id**, you can get a **refresh_token** with:
|
15
|
+
|
16
|
+
`rake model_to_googlesheet:get_refresh_token client_id='274709489501-ekvsdc8cpuh9nrps73h55m29i1kbgtgk.apps.googleusercontent.com' client_secret='hbSi0Q7VWArzLQZ2maJoagdx'`
|
17
|
+
|
18
|
+
###Configuration
|
19
|
+
Once you get your **client_id**, **client_secret** and **refresh_token**, you can set them either globally, permodel, or permethod.
|
20
|
+
Available options are include:
|
21
|
+
|
22
|
+
client_id
|
23
|
+
client_secret
|
24
|
+
refresh_token
|
25
|
+
spreadsheet - title of the spreadheet you'd like to export your data in.
|
26
|
+
worksheet - title of the worksheet you'd like to export your data in. if either worksheet or spreadsheet with such titles don't exit, they will be created.
|
27
|
+
convert_with - (optional) either symbol of method name in your model or Proc that will return hash (columns and values to include in a worksheet created).
|
28
|
+
|
29
|
+
|
30
|
+
##You can put your configuration in */config/initializers*:
|
31
|
+
|
32
|
+
ModelToGooglesheet.configure do |config|
|
33
|
+
config.client_id = client_id
|
34
|
+
config.client_secret = client_secret
|
35
|
+
config.refresh_token = refresh_token
|
36
|
+
end
|
37
|
+
|
38
|
+
##You can override that configuration, or add new one options in your model:
|
39
|
+
|
40
|
+
exportable_to_googlesheet refresh_token: refresh_token,
|
41
|
+
spreadsheet: 'My App', worksheet: 'Users'
|
42
|
+
|
43
|
+
##And finally you can override it all, or add nothing at all permethod:
|
44
|
+
|
45
|
+
User.last.export_to_googlesheet spreadsheet: 'Another one',
|
46
|
+
convert_with: :exportize
|
47
|
+
|
48
|
+
provided you have a method
|
49
|
+
|
50
|
+
def exportize
|
51
|
+
{
|
52
|
+
name: name.upcase,
|
53
|
+
age: age
|
54
|
+
}
|
55
|
+
end
|
56
|
+
|
57
|
+
in your model. You can also avoid creating new method with proc or lambda:
|
58
|
+
|
59
|
+
convert_with: -> (record) { { name: record.name.upcase, age: record.age } }
|
60
|
+
|
61
|
+
|
62
|
+
If you export a collection of users, gem will recreate your worksheet and export it all to a clean one. If you export one user, gem will add it to the worksheet if it was already created and create a new one (with a spreadsheet if needed) if it couldn't find one, adding record to a newly created one.
|
63
|
+
|
64
|
+
|
65
|
+
|
66
|
+
|
67
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# list Provides access to cells using column names, assuming the first row ALREADY contains column names.
|
2
|
+
# keys() --- Column names i.e. the contents of the first row. Duplicates are removed.
|
3
|
+
# keys=(ary) --- Updates column names i.e. the contents of the first row.
|
4
|
+
|
5
|
+
|
6
|
+
# ws.list
|
7
|
+
# ws.list.keys --- waits, but caches
|
8
|
+
# ws.list.push
|
9
|
+
|
10
|
+
module ModelToGooglesheet
|
11
|
+
module Export
|
12
|
+
|
13
|
+
def self.included(base)
|
14
|
+
base.extend ClassMethods
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
def export_to_googlesheet permethod_options={}
|
19
|
+
options = Configuration.merge_configs permethod_options, model_to_googlesheet_configuration
|
20
|
+
session = GoogleDrive::Session.new_for_gs({
|
21
|
+
client_id: options[:client_id],
|
22
|
+
client_secret: options[:client_secret],
|
23
|
+
refresh_token: options[:refresh_token]
|
24
|
+
})
|
25
|
+
|
26
|
+
ws = session.get_or_create_ss(options[:spreadsheet]).get_or_create_ws(options[:worksheet])
|
27
|
+
|
28
|
+
record_hash = self.get_exportable_hash options[:convert_with]
|
29
|
+
ws.export_hash record_hash
|
30
|
+
|
31
|
+
ws.save
|
32
|
+
end
|
33
|
+
|
34
|
+
def get_exportable_hash convert_with
|
35
|
+
case convert_with
|
36
|
+
when nil
|
37
|
+
attributes
|
38
|
+
when Symbol
|
39
|
+
self.send convert_with
|
40
|
+
when Proc
|
41
|
+
convert_with.call self
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
|
46
|
+
|
47
|
+
|
48
|
+
module ClassMethods
|
49
|
+
BATCH_SIZE = 500 #google drive has trouble with saving about 2000 of rows right away, so we're saving them in batches. we may raise batch size up to about 1000.
|
50
|
+
|
51
|
+
def export_to_googlesheet permethod_options={}
|
52
|
+
options = Configuration.merge_configs permethod_options, model_to_googlesheet_configuration
|
53
|
+
|
54
|
+
session = GoogleDrive::Session.new_for_gs({
|
55
|
+
client_id: options[:client_id],
|
56
|
+
client_secret: options[:client_secret],
|
57
|
+
refresh_token: options[:refresh_token]
|
58
|
+
})
|
59
|
+
|
60
|
+
ss = session.get_or_create_ss options[:spreadsheet]
|
61
|
+
ws = ss.create_or_recreate_ws options[:worksheet]
|
62
|
+
|
63
|
+
amount_of_batches_to_skip = self.count/BATCH_SIZE
|
64
|
+
(0..amount_of_batches_to_skip).each do |skip_n_batches|
|
65
|
+
records = limit(BATCH_SIZE).offset(skip_n_batches*BATCH_SIZE)
|
66
|
+
|
67
|
+
records.each do |record|
|
68
|
+
record_hash = record.get_exportable_hash options[:convert_with]
|
69
|
+
ws.export_hash record_hash
|
70
|
+
end
|
71
|
+
|
72
|
+
ws.save
|
73
|
+
end
|
74
|
+
|
75
|
+
|
76
|
+
end
|
77
|
+
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
|
82
|
+
|
83
|
+
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module GoogleDrive
|
2
|
+
module AuthenticationHelper
|
3
|
+
|
4
|
+
# private
|
5
|
+
def self.set_auth client_id, client_secret
|
6
|
+
auth = Google::APIClient.new({application_name: 'Db To Googlesheet'}).authorization
|
7
|
+
auth.client_id = client_id
|
8
|
+
auth.client_secret = client_secret
|
9
|
+
|
10
|
+
auth.scope =
|
11
|
+
"https://www.googleapis.com/auth/drive " +
|
12
|
+
"https://spreadsheets.google.com/feeds/"
|
13
|
+
auth.redirect_uri = "urn:ietf:wg:oauth:2.0:oob"
|
14
|
+
|
15
|
+
auth
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
require_relative 'authentication_helper'
|
2
|
+
module GoogleDrive
|
3
|
+
class Session
|
4
|
+
|
5
|
+
def self.new_for_gs client_id:, client_secret:, refresh_token:
|
6
|
+
auth = GoogleDrive::AuthenticationHelper.set_auth client_id, client_secret
|
7
|
+
|
8
|
+
auth.refresh_token = refresh_token
|
9
|
+
auth.fetch_access_token!
|
10
|
+
@session = login_with_oauth(auth.access_token)
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_or_create_ss title
|
14
|
+
ss = exact_ss title
|
15
|
+
ss ||= create_spreadsheet(title)
|
16
|
+
end
|
17
|
+
|
18
|
+
#spreadsheets(title:) returns trashed files too.
|
19
|
+
#gs doesn't allow to create worksheets with the same title in one spreadsheet
|
20
|
+
#and it doesn't trash ws-s
|
21
|
+
def exact_ss title
|
22
|
+
exact_sss(title).first
|
23
|
+
end
|
24
|
+
def exact_sss title
|
25
|
+
spreadsheets(title: title, 'title-exact': true).select { |ss| !ss.labels.trashed }
|
26
|
+
end
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
module GoogleDrive
|
2
|
+
class Spreadsheet
|
3
|
+
|
4
|
+
def get_or_create_ws title
|
5
|
+
ws = worksheet_by_title title
|
6
|
+
ws ||= add_worksheet(title)
|
7
|
+
end
|
8
|
+
|
9
|
+
# to delete everything in the worksheet, deleting row by row is too slow
|
10
|
+
def create_or_recreate_ws title
|
11
|
+
if ws = worksheet_by_title(title)
|
12
|
+
ws.delete
|
13
|
+
end
|
14
|
+
add_worksheet title
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module GoogleDrive
|
2
|
+
class Worksheet
|
3
|
+
|
4
|
+
def export_hash hash
|
5
|
+
begin
|
6
|
+
self.list.push hash #will raise error if unfamiliar key. faster than making a request everytime asking for present keys.
|
7
|
+
rescue GoogleDrive::Error => error #GoogleDrive::Error: Column doesn't exist: "hi"
|
8
|
+
if error.message.include? "Column doesn't exist:"
|
9
|
+
old_keys = self.list.keys # then update all keys, because it'll take same amount of time as updating specific keys
|
10
|
+
new_keys = (old_keys + hash.keys).uniq
|
11
|
+
self.list.keys = new_keys #we are updating the first row.
|
12
|
+
retry
|
13
|
+
end
|
14
|
+
raise
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
module ModelToGooglesheet
|
3
|
+
class Railtie < Rails::Railtie
|
4
|
+
|
5
|
+
rake_tasks do
|
6
|
+
load 'model_to_googlesheet/tasks/get_refresh_token.rake'
|
7
|
+
end
|
8
|
+
|
9
|
+
initializer 'model_to_googlesheet.extend_activerecord' do
|
10
|
+
ActiveRecord::Base.extend ModelToGooglesheet::ClassMethods
|
11
|
+
end
|
12
|
+
|
13
|
+
config.before_configuration do
|
14
|
+
ModelToGooglesheet.configuration = ModelToGooglesheet::Configuration.new
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'google/api_client'
|
2
|
+
require 'google_drive'
|
3
|
+
require 'model_to_googlesheet/google_drive/authentication_helper'
|
4
|
+
|
5
|
+
namespace :model_to_googlesheet do
|
6
|
+
|
7
|
+
# add credentials --- CHOOSE other client
|
8
|
+
# use this task manually unless you already know your refresh token
|
9
|
+
desc "What's my refresh token?\nrake model_to_googlesheet:get_refresh_token client_id='274709489501-ekvsdc8cpuh9nrps73h55m29i1kbgtgk.apps.googleusercontent.com' client_secret='hbSi0Q7VWArzLQZ2maJoagdx'"
|
10
|
+
task :get_refresh_token do
|
11
|
+
auth = GoogleDrive::AuthenticationHelper.set_auth ENV['client_id'], ENV['client_secret']
|
12
|
+
puts "1. Open this page:\n #{auth.authorization_uri}\n\n"
|
13
|
+
print "2. Enter the authorization code shown in the page: "
|
14
|
+
|
15
|
+
auth.code = $stdin.gets.chomp
|
16
|
+
auth.fetch_access_token!
|
17
|
+
puts "\nYour refresh token is: #{auth.refresh_token}"
|
18
|
+
end
|
19
|
+
|
20
|
+
end
|
21
|
+
|
22
|
+
|
@@ -0,0 +1,72 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
require 'rails/railtie'
|
3
|
+
require 'google/api_client'
|
4
|
+
require 'google_drive'
|
5
|
+
|
6
|
+
|
7
|
+
require 'model_to_googlesheet/google_drive/session'
|
8
|
+
require 'model_to_googlesheet/google_drive/spreadsheet'
|
9
|
+
require 'model_to_googlesheet/google_drive/worksheet'
|
10
|
+
|
11
|
+
require 'model_to_googlesheet/export'
|
12
|
+
require 'model_to_googlesheet/railtie'
|
13
|
+
|
14
|
+
module ModelToGooglesheet
|
15
|
+
|
16
|
+
module ClassMethods
|
17
|
+
def exportable_to_googlesheet options={}
|
18
|
+
mattr_accessor :model_to_googlesheet_configuration
|
19
|
+
self.model_to_googlesheet_configuration = ModelToGooglesheet.configuration #here we are initializing permodel configuration with perapp configuration
|
20
|
+
|
21
|
+
options.each do |k, v|
|
22
|
+
self.model_to_googlesheet_configuration.send("#{k}=", v)
|
23
|
+
end #unfamiliar option will raise an error
|
24
|
+
|
25
|
+
include ModelToGooglesheet::Export
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
class << self
|
30
|
+
attr_accessor :configuration
|
31
|
+
end
|
32
|
+
# for config/initializers
|
33
|
+
def self.configure
|
34
|
+
yield configuration
|
35
|
+
end
|
36
|
+
|
37
|
+
|
38
|
+
class Configuration
|
39
|
+
OPTIONS = [
|
40
|
+
:client_id, :client_secret, :refresh_token,
|
41
|
+
:worksheet, :spreadsheet,
|
42
|
+
:convert_with
|
43
|
+
]
|
44
|
+
|
45
|
+
attr_accessor *OPTIONS
|
46
|
+
|
47
|
+
# defaults
|
48
|
+
def initialize
|
49
|
+
@client_id = nil
|
50
|
+
@client_secret = nil
|
51
|
+
@refresh_token = nil
|
52
|
+
@worksheet = nil
|
53
|
+
@spreadsheet = nil
|
54
|
+
@convert_with = nil #optional
|
55
|
+
end
|
56
|
+
|
57
|
+
def self.merge_configs permethod_options, permodel_configuration
|
58
|
+
options = ActiveSupport::HashWithIndifferentAccess.new permethod_options
|
59
|
+
ModelToGooglesheet::Configuration::OPTIONS.each do |option_name|
|
60
|
+
options[option_name] ||= permodel_configuration.send(option_name)
|
61
|
+
end
|
62
|
+
options
|
63
|
+
end
|
64
|
+
|
65
|
+
end
|
66
|
+
|
67
|
+
end
|
68
|
+
|
69
|
+
|
70
|
+
|
71
|
+
|
72
|
+
|
@@ -0,0 +1,39 @@
|
|
1
|
+
$:.push File.expand_path('../lib', __FILE__)
|
2
|
+
|
3
|
+
# Maintain your gem's version:
|
4
|
+
require 'model_to_googlesheet/version'
|
5
|
+
|
6
|
+
# Describe your gem and declare its dependencies:
|
7
|
+
Gem::Specification.new do |s|
|
8
|
+
s.name = 'model_to_googlesheet'
|
9
|
+
s.version = ModelToGooglesheet::VERSION
|
10
|
+
s.authors = ['Helga Karunus']
|
11
|
+
s.email = ['lakesare@gmail.com']
|
12
|
+
s.homepage = 'https://github.com/lakesare/model_to_googlesheet'
|
13
|
+
s.summary = 'Export your Rails model to Googlesheets'
|
14
|
+
s.description = "A Railtie that allows you to configute model export to your google worksheet with just a few configurations, and will manage export of either a record or a set of records (with customized fields if you feel like it), export in batches (google spreadsheet gets buggy with large sets of data otherwise), creation of spreadsheet or worksheet if either was nonexistent"
|
15
|
+
s.license = 'MIT'
|
16
|
+
|
17
|
+
s.files = `git ls-files`.split("\n") - ['.gitignore']
|
18
|
+
s.require_paths = ['lib']
|
19
|
+
|
20
|
+
s.add_dependency 'activerecord'
|
21
|
+
s.add_dependency 'rails', '~>4.2'
|
22
|
+
s.add_dependency 'google_drive', '~>1.0'
|
23
|
+
|
24
|
+
s.add_development_dependency 'sqlite3'
|
25
|
+
s.add_development_dependency 'rspec', '~>3.4'
|
26
|
+
s.add_development_dependency 'pry'
|
27
|
+
s.add_development_dependency 'pry-rescue'
|
28
|
+
|
29
|
+
|
30
|
+
s.required_ruby_version = '>= 1.8.6'
|
31
|
+
end
|
32
|
+
|
33
|
+
|
34
|
+
|
35
|
+
|
36
|
+
|
37
|
+
# 'title-exact': true везде понаставлять
|
38
|
+
|
39
|
+
|
data/spec/export_spec.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
# WARNING: these tests are not isolated, you need to run them all together. because making them isolated would make them run forever.
|
2
|
+
|
3
|
+
require 'spec_helper'
|
4
|
+
require 'helpers/active_record_helper'
|
5
|
+
|
6
|
+
RSpec.configure do |c|
|
7
|
+
c.include ActiveRecordHelper
|
8
|
+
end
|
9
|
+
|
10
|
+
describe ModelToGooglesheet::Export do
|
11
|
+
|
12
|
+
before(:context){
|
13
|
+
ModelToGooglesheet::Railtie.run_initializers
|
14
|
+
ModelToGooglesheet.configuration = ModelToGooglesheet::Configuration.new
|
15
|
+
}
|
16
|
+
|
17
|
+
include_context :create_session
|
18
|
+
include_context :set_spreadsheets
|
19
|
+
|
20
|
+
before(:context) do
|
21
|
+
connect_to_db
|
22
|
+
end
|
23
|
+
|
24
|
+
#we sacrifice some modularity for speed
|
25
|
+
it 'gets every level configs and creates keys in empty worksheet' do
|
26
|
+
setup_table(:users)
|
27
|
+
|
28
|
+
ModelToGooglesheet.configure do |config|
|
29
|
+
config.client_id = client_id
|
30
|
+
config.client_secret = client_secret
|
31
|
+
config.refresh_token = refresh_token + 'wrong one'
|
32
|
+
end
|
33
|
+
|
34
|
+
#because User doesn't know about our local vars (like refesh_token)
|
35
|
+
User = Class.new ActiveRecord::Base
|
36
|
+
User.exportable_to_googlesheet refresh_token: refresh_token,
|
37
|
+
spreadsheet: was_0_title, worksheet: 'wrong one'
|
38
|
+
|
39
|
+
User.create name: 'Cesilia', age: 38
|
40
|
+
User.create name: 'Tom', age: 17
|
41
|
+
|
42
|
+
#appends first record to the list
|
43
|
+
User.first.export_to_googlesheet worksheet: 'wow'
|
44
|
+
|
45
|
+
ws = session.exact_ss(was_0_title).worksheet_by_title('wow')
|
46
|
+
|
47
|
+
expect(ws.list.keys).to eq(['id', 'name', 'age'])
|
48
|
+
end
|
49
|
+
|
50
|
+
it ':convert_with Symbol option' do
|
51
|
+
User.class_eval do
|
52
|
+
def exportize
|
53
|
+
{
|
54
|
+
name: name.upcase,
|
55
|
+
comment: 'riddle'
|
56
|
+
}
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
User.last.export_to_googlesheet spreadsheet: was_1_title, worksheet: 'wow',
|
61
|
+
convert_with: :exportize
|
62
|
+
|
63
|
+
ws = session.exact_ss(was_1_title).worksheet_by_title('wow')
|
64
|
+
|
65
|
+
expect(ws.list.keys).to eq(['name', 'comment'])
|
66
|
+
expect(ws.list[0].to_a).to eq([["name", "TOM"], ["comment", "riddle"]])
|
67
|
+
end
|
68
|
+
|
69
|
+
it ':convert_with Proc option' do
|
70
|
+
User.last.export_to_googlesheet spreadsheet: was_0_title, worksheet: 'wow',
|
71
|
+
convert_with: -> (record) { { age: record.age } }
|
72
|
+
|
73
|
+
ws = session.exact_ss(was_0_title).worksheet_by_title('wow')
|
74
|
+
expect(ws.list[0].to_a).to eq([["age", "17"]])
|
75
|
+
end
|
76
|
+
|
77
|
+
it 'User.export' do
|
78
|
+
User.export_to_googlesheet spreadsheet: was_1_title, worksheet: 'wow'
|
79
|
+
#make it return ws so that tests are faster?
|
80
|
+
|
81
|
+
ws = session.exact_ss(was_1_title).worksheet_by_title('wow')
|
82
|
+
expect(ws.list[0].to_a).to eq([["id", "1"], ["name", "Cesilia"], ["age", "38"]])
|
83
|
+
end
|
84
|
+
|
85
|
+
|
86
|
+
|
87
|
+
|
88
|
+
|
89
|
+
|
90
|
+
|
91
|
+
end
|
92
|
+
|
93
|
+
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
|
98
|
+
|
99
|
+
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module ActiveRecordHelper
|
2
|
+
def setup_table name
|
3
|
+
connection = ActiveRecord::Base.connection
|
4
|
+
if connection.table_exists? name
|
5
|
+
connection.drop_table name
|
6
|
+
end
|
7
|
+
connection.create_table name do |t|
|
8
|
+
if block_given?
|
9
|
+
yield t
|
10
|
+
else
|
11
|
+
t.string :name
|
12
|
+
t.integer :age
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def connect_to_db
|
18
|
+
ActiveRecord::Base.establish_connection(
|
19
|
+
:adapter => 'sqlite3',
|
20
|
+
:database => 'spec/test.sqlite3.db'
|
21
|
+
)
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe GoogleDrive::Session do
|
4
|
+
|
5
|
+
include_context :create_session
|
6
|
+
include_context :set_spreadsheets
|
7
|
+
|
8
|
+
it 'fetches existent spreadsheet' do
|
9
|
+
session.get_or_create_ss was_1_title
|
10
|
+
i = session.exact_sss(was_1_title).size
|
11
|
+
|
12
|
+
expect(i).to eq(1)
|
13
|
+
end
|
14
|
+
|
15
|
+
it "creates spreadsheet if can't fetch it" do
|
16
|
+
session.get_or_create_ss was_0_title
|
17
|
+
i = session.exact_sss(was_0_title).size
|
18
|
+
|
19
|
+
expect(i).to eq(1)
|
20
|
+
end
|
21
|
+
|
22
|
+
it "creates worksheet and spreadsheet if can't fetch both" do
|
23
|
+
spreadsheet = session.get_or_create_ss was_0_title
|
24
|
+
worksheet = spreadsheet.get_or_create_ws 'worksheety'
|
25
|
+
|
26
|
+
expect(spreadsheet.title).to eq(was_0_title)
|
27
|
+
expect(worksheet.title).to eq('worksheety')
|
28
|
+
end
|
29
|
+
|
30
|
+
it "creates worksheet if only spreadsheet exists" do
|
31
|
+
spreadsheet = session.get_or_create_ss was_1_title
|
32
|
+
worksheet = spreadsheet.get_or_create_ws 'worksheety'
|
33
|
+
|
34
|
+
expect(worksheet.title).to eq('worksheety')
|
35
|
+
end
|
36
|
+
|
37
|
+
it 'recreates worksheet' do
|
38
|
+
spreadsheet = session.get_or_create_ss was_1_title
|
39
|
+
worksheet = spreadsheet.get_or_create_ws 'worksheety'
|
40
|
+
|
41
|
+
worksheet[1, 1] = 'filled in'
|
42
|
+
worksheet.save
|
43
|
+
|
44
|
+
new_worksheet = spreadsheet.create_or_recreate_ws 'filled in'
|
45
|
+
expect(new_worksheet[1, 1]).to eq('')
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,61 @@
|
|
1
|
+
$LOAD_PATH.unshift File.expand_path('../../lib', __FILE__)
|
2
|
+
require 'model_to_googlesheet'
|
3
|
+
require 'pry'
|
4
|
+
require 'pry-rescue'
|
5
|
+
|
6
|
+
Dir['./spec/support/*.rb'].each { |file| require file }
|
7
|
+
|
8
|
+
|
9
|
+
|
10
|
+
|
11
|
+
|
12
|
+
|
13
|
+
|
14
|
+
|
15
|
+
# exportable_to_googlesheet
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
# User.export_to_googlesheet 'OurUsersInfo' #appends all the users to sheet
|
20
|
+
|
21
|
+
# User.find(3).export_to_googlesheet 'OurUsersInfo' #appends one user to sheet
|
22
|
+
|
23
|
+
|
24
|
+
|
25
|
+
# User.find(3).export_to_googlesheet 'OurUsersInfo' #appends one user to sheet
|
26
|
+
|
27
|
+
|
28
|
+
|
29
|
+
|
30
|
+
# # and the in configs allow to set any amount of defaults. no defaults except for fields: defaults to all fields with their column names
|
31
|
+
|
32
|
+
# exportable_to_googlesheet name: 'OurUsersInfo', fields: :fields_like_a, client_id:, client_secret:, refresh_token:
|
33
|
+
|
34
|
+
# User.export_to_googlesheet #but can override!
|
35
|
+
|
36
|
+
# def export_to_googlesheet
|
37
|
+
# ...if not hash, to hash acc to :fields||default...
|
38
|
+
# (if hash and :fields on method itself and not by default, WARNING)
|
39
|
+
# end
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
|
44
|
+
|
45
|
+
# before(:context) do
|
46
|
+
# ActiveRecord::Base.establish_connection(
|
47
|
+
# :adapter => 'sqlite3',
|
48
|
+
# :database => 'spec/test.sqlite3.db'
|
49
|
+
# )
|
50
|
+
|
51
|
+
# ActiveRecord::Base.connection.create_table :users do |t|
|
52
|
+
# t.string :name
|
53
|
+
# t.integer :age
|
54
|
+
# end
|
55
|
+
|
56
|
+
# class User < ActiveRecord::Base
|
57
|
+
# exportable_to_googlesheet
|
58
|
+
# end
|
59
|
+
|
60
|
+
# end
|
61
|
+
|
@@ -0,0 +1,22 @@
|
|
1
|
+
|
2
|
+
shared_context :create_session do
|
3
|
+
let(:client_id) { '274709489501-ekvsdc8cpuh9nrps73h55m29i1kbgtgk.apps.googleusercontent.com' }
|
4
|
+
let(:client_secret) { 'hbSi0Q7VWArzLQZ2maJoagdx' }
|
5
|
+
let(:refresh_token) { '1/iNO0L49Mnn1EOmhnc5Rn_AEF-6SYlGbAt9cmITdhD4hIgOrJDtdun6zK6XiATCKT' }
|
6
|
+
let(:session) {
|
7
|
+
GoogleDrive::Session.new_for_gs({
|
8
|
+
client_id: client_id,
|
9
|
+
client_secret: client_secret,
|
10
|
+
refresh_token: refresh_token
|
11
|
+
})
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
|
16
|
+
|
17
|
+
|
18
|
+
|
19
|
+
|
20
|
+
|
21
|
+
|
22
|
+
|
@@ -0,0 +1,12 @@
|
|
1
|
+
|
2
|
+
shared_context :set_spreadsheets do
|
3
|
+
let(:was_1_title) { 'existent_spreadsheet_for_tests' }
|
4
|
+
let(:was_0_title) { 'nonexistent_spreadsheet_for_tests' }
|
5
|
+
|
6
|
+
before(:example) do
|
7
|
+
session.exact_sss(was_1_title).each(&:delete)
|
8
|
+
session.create_spreadsheet was_1_title
|
9
|
+
|
10
|
+
session.exact_sss(was_0_title).each(&:delete)
|
11
|
+
end
|
12
|
+
end
|
metadata
ADDED
@@ -0,0 +1,166 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: model_to_googlesheet
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Helga Karunus
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-12-14 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: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rails
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.2'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.2'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: google_drive
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '1.0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '1.0'
|
55
|
+
- !ruby/object:Gem::Dependency
|
56
|
+
name: sqlite3
|
57
|
+
requirement: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: '0'
|
62
|
+
type: :development
|
63
|
+
prerelease: false
|
64
|
+
version_requirements: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: '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.4'
|
76
|
+
type: :development
|
77
|
+
prerelease: false
|
78
|
+
version_requirements: !ruby/object:Gem::Requirement
|
79
|
+
requirements:
|
80
|
+
- - "~>"
|
81
|
+
- !ruby/object:Gem::Version
|
82
|
+
version: '3.4'
|
83
|
+
- !ruby/object:Gem::Dependency
|
84
|
+
name: pry
|
85
|
+
requirement: !ruby/object:Gem::Requirement
|
86
|
+
requirements:
|
87
|
+
- - ">="
|
88
|
+
- !ruby/object:Gem::Version
|
89
|
+
version: '0'
|
90
|
+
type: :development
|
91
|
+
prerelease: false
|
92
|
+
version_requirements: !ruby/object:Gem::Requirement
|
93
|
+
requirements:
|
94
|
+
- - ">="
|
95
|
+
- !ruby/object:Gem::Version
|
96
|
+
version: '0'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: pry-rescue
|
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
|
+
description: A Railtie that allows you to configute model export to your google worksheet
|
112
|
+
with just a few configurations, and will manage export of either a record or a set
|
113
|
+
of records (with customized fields if you feel like it), export in batches (google
|
114
|
+
spreadsheet gets buggy with large sets of data otherwise), creation of spreadsheet
|
115
|
+
or worksheet if either was nonexistent
|
116
|
+
email:
|
117
|
+
- lakesare@gmail.com
|
118
|
+
executables: []
|
119
|
+
extensions: []
|
120
|
+
extra_rdoc_files: []
|
121
|
+
files:
|
122
|
+
- Gemfile
|
123
|
+
- Gemfile.lock
|
124
|
+
- README.md
|
125
|
+
- Rakefile
|
126
|
+
- lib/model_to_googlesheet.rb
|
127
|
+
- lib/model_to_googlesheet/export.rb
|
128
|
+
- lib/model_to_googlesheet/google_drive/authentication_helper.rb
|
129
|
+
- lib/model_to_googlesheet/google_drive/session.rb
|
130
|
+
- lib/model_to_googlesheet/google_drive/spreadsheet.rb
|
131
|
+
- lib/model_to_googlesheet/google_drive/worksheet.rb
|
132
|
+
- lib/model_to_googlesheet/railtie.rb
|
133
|
+
- lib/model_to_googlesheet/tasks/get_refresh_token.rake
|
134
|
+
- lib/model_to_googlesheet/version.rb
|
135
|
+
- model_to_googlesheet.gemspec
|
136
|
+
- spec/export_spec.rb
|
137
|
+
- spec/helpers/active_record_helper.rb
|
138
|
+
- spec/session_spec.rb
|
139
|
+
- spec/spec_helper.rb
|
140
|
+
- spec/support/create_session.rb
|
141
|
+
- spec/support/set_spreadsheets.rb
|
142
|
+
homepage: https://github.com/lakesare/model_to_googlesheet
|
143
|
+
licenses:
|
144
|
+
- MIT
|
145
|
+
metadata: {}
|
146
|
+
post_install_message:
|
147
|
+
rdoc_options: []
|
148
|
+
require_paths:
|
149
|
+
- lib
|
150
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
151
|
+
requirements:
|
152
|
+
- - ">="
|
153
|
+
- !ruby/object:Gem::Version
|
154
|
+
version: 1.8.6
|
155
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
156
|
+
requirements:
|
157
|
+
- - ">="
|
158
|
+
- !ruby/object:Gem::Version
|
159
|
+
version: '0'
|
160
|
+
requirements: []
|
161
|
+
rubyforge_project:
|
162
|
+
rubygems_version: 2.4.6
|
163
|
+
signing_key:
|
164
|
+
specification_version: 4
|
165
|
+
summary: Export your Rails model to Googlesheets
|
166
|
+
test_files: []
|