mobilize-base 1.35 → 1.36

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.
data/README.md CHANGED
@@ -1,675 +1,4 @@
1
1
  Mobilize
2
2
  ========
3
3
 
4
- Mobilize is an end-to-end data transfer workflow manager with:
5
- * a Google Spreadsheets UI through [google-drive-ruby][google_drive_ruby];
6
- * a queue manager through [Resque][resque];
7
- * a persistent caching / database layer through [Mongoid][mongoid];
8
- * gems for data transfers to/from Hive, mySQL, and HTTP endpoints
9
- (coming soon).
10
-
11
- Mobilize-Base includes all the core scheduling and processing
12
- functionality, allowing you to:
13
- * put workers on the Mobilize Resque queue.
14
- * create [Users](#section_Start_Users_User) and their associated Google Spreadsheet [Runners](#section_Start_Users_Runner);
15
- * poll for [Jobs](#section_Job) on Runners (currently gsheet to gsheet only) and add them to Resque;
16
- * monitor the status of Jobs on a rolling log.
17
-
18
- Table Of Contents
19
- -----------------
20
- * [Overview](#section_Overview)
21
- * [Install](#section_Install)
22
- * [Redis](#section_Install_Redis)
23
- * [MongoDB](#section_Install_MongoDB)
24
- * [Mobilize-Base](#section_Install_Mobilize-Base)
25
- * [Default Folders and Files](#section_Install_Folders_and_Files)
26
- * [Configure](#section_Configure)
27
- * [Google Drive](#section_Configure_Google_Drive)
28
- * [Google Sheets](#section_Configure_Google_Sheets)
29
- * [Jobtracker](#section_Configure_Jobtracker)
30
- * [Resque](#section_Configure_Resque)
31
- * [Resque-Web](#section_Configure_Resque-Web)
32
- * [Gridfs](#section_Configure_Gridfs)
33
- * [Mongoid](#section_Configure_Mongoid)
34
- * [Start](#section_Start)
35
- * [Start Resque-Web](#section_Start_Start_Resque-Web)
36
- * [Set Environment](#section_Start_Set_Environment)
37
- * [Create User](#section_Start_Create_User)
38
- * [Start Workers](#section_Start_Start_Workers)
39
- * [View Logs](#section_Start_View_Logs)
40
- * [Start Jobtracker](#section_Start_Start_Jobtracker)
41
- * [Create Job](#section_Start_Create_Job)
42
- * [Run Test](#section_Start_Run_Test)
43
- * [Add Gbooks and Gsheets](#section_Start_Add_Gbooks_And_Gsheets)
44
- * [Meta](#section_Meta)
45
- * [Author](#section_Author)
46
- * [Special Thanks](#section_Special_Thanks)
47
-
48
-
49
- <a name='section_Overview'></a>
50
- Overview
51
- -----------
52
-
53
- * Mobilize is a script deployment and data visualization framework with
54
- a Google Spreadsheets UI.
55
- * Mobilize uses Resque for parallelization and queueuing, MongoDB for caching,
56
- and Google Drive for hosting, user input and display.
57
- * The [mobilize-ssh][mobilize-ssh] gem allows you to run scripts and
58
- copy files between different machines, and have output directed to a
59
- spreadsheet for viewing and processing.
60
- * The platform is easily extensible: add your own rake tasks and
61
- handlers by following a few simple conventions, and you can have your own
62
- Mobilize gem up and running in no time.
63
-
64
- <a name='section_Install'></a>
65
- Install
66
- ------------
67
-
68
- Mobilize requires Ruby 1.9.3, and has been tested on OSX and Ubuntu.
69
-
70
- [RVM][rvm] is great for managing your rubies.
71
-
72
- <a name='section_Install_Redis'></a>
73
- ### Redis
74
-
75
- Redis is a pre-requisite for running Resque.
76
-
77
- Please refer to the [Resque Redis Section][redis] for complete
78
- instructions.
79
-
80
- <a name='section_Install_MongoDB'></a>
81
- ### MongoDB
82
-
83
- MongoDB is used to persist caches between reads and writes, keep track
84
- of Users and Jobs, and store Datasets that map to endpoints.
85
-
86
- Please refer to the [MongoDB Quickstart Page][mongodb_quickstart] to get started.
87
-
88
- The settings for database and port are set in config/mongoid.yml
89
- and are best left as default. Please refer to [Configure
90
- Mongoid](#section_Configure_Mongoid) for details.
91
-
92
- <a name='section_Install_Mobilize-Base'></a>
93
- ### Mobilize-Base
94
-
95
- Mobilize-Base contains all of the gems it needs to run.
96
-
97
- add this to your Gemfile:
98
-
99
- ``` ruby
100
- gem "mobilize-base"
101
- ```
102
-
103
- or do
104
-
105
- $ gem install mobilize-base
106
-
107
- for a ruby-wide install.
108
-
109
- <a name='section_Install_Folders_and_Files'></a>
110
- ### Folders and Files
111
-
112
- Mobilize requires a config folder and a log folder.
113
-
114
- If you're on Rails, it will use the built-in config and log folders.
115
-
116
- Otherwise, it will use log and config folders in the project folder (the
117
- same one that contains your Rakefile)
118
-
119
- ### Rakefile
120
-
121
- Inside the Rakefile in your project's root folder, make sure you have:
122
-
123
- ``` ruby
124
- require 'mobilize-base/tasks'
125
- ```
126
-
127
- This defines rake tasks essential to run the environment.
128
-
129
- ### Config and Log Folders
130
-
131
- run
132
-
133
- $ rake mobilize_base:setup
134
-
135
- Mobilize will create config/mobilize/ and log/ folders at the project root
136
- level. (same as the Rakefile).
137
-
138
- (You can override these by passing
139
- MOBILIZE_CONFIG_DIR and/or MOBILIZE_LOG_DIR arguments to the command.
140
- All directories must end with a '/'.)
141
-
142
- The script will also create samples for all required config files, which are detailed below.
143
-
144
- Resque will create a mobilize-resque-`<environment>`.log in the log folder,
145
- and loop over 10 files, 10MB each.
146
-
147
- <a name='section_Configure'></a>
148
- Configure
149
- ------------
150
-
151
- All Mobilize configurations live in files in `config/mobilize/*.yml` by
152
- default. Samples can
153
- be found below or on github in the [lib/samples][git_samples] folder.
154
-
155
- <a name='section_Configure_Google_Drive'></a>
156
- ### Configure Google Drive
157
-
158
- gdrive.yml needs:
159
- * a domain, which can be gmail.com but may be different depending on
160
- your organization. All gdrive accounts should have
161
- the same domain, and all Users should have emails in this domain.
162
- * an owner name and password. You can set up separate owners
163
- for different environments as in the below file, which will keep your
164
- mission critical workers from getting rate-limit errors.
165
- * one admin_group_name, which the owner and all admins should be added to -- this
166
- group will need read permissions to read from and edit permissions to write
167
- to files.
168
- * one or more admins with email attributes -- these will be for people
169
- who should be given write permissions to all Mobilize books in the
170
- environment for maintenance purposes.
171
- * one worker_group_name, which the owner and all workers should be added to -- this
172
- group will need read permissions to read from and edit permissions to write
173
- to files.
174
- * one or more workers with name and pw attributes -- they will be used
175
- to queue up google reads and writes. This can be the same as the owner
176
- account for testing purposes or low-volume environments.
177
-
178
- __Mobilize only allows one Resque
179
- worker at a time to use a Google drive worker account for
180
- reading/writing, which is called a gdrive_slot.__
181
-
182
- Sample gdrive.yml:
183
-
184
- ``` yml
185
- ---
186
- development:
187
- domain: host.com
188
- owner:
189
- name: owner_development
190
- pw: google_drive_password
191
- admin_group_name: admins_development
192
- admins:
193
- - name: admin
194
- worker_group_name: workers_development
195
- workers:
196
- - name: worker_development001
197
- pw: worker001_google_drive_password
198
- - name: worker_development002,
199
- pw: worker002_google_drive_password
200
- test:
201
- domain: host.com
202
- owner:
203
- name: owner_test
204
- pw: google_drive_password
205
- admin_group_name: admins_test
206
- admins:
207
- - name: admin
208
- worker_group_name: workers_test
209
- workers:
210
- - name: worker_test001
211
- pw: worker001_google_drive_password
212
- - name: worker_test002
213
- pw: worker002_google_drive_password
214
- production:
215
- domain: host.com
216
- owner:
217
- name: owner_production
218
- pw: google_drive_password
219
- admin_group_name: admins_production
220
- admins:
221
- - name: admin
222
- worker_group_name: workers_production
223
- workers:
224
- - name: worker_production001
225
- pw: worker001_google_drive_password
226
- - name: worker_production002
227
- pw: worker002_google_drive_password
228
- ```
229
-
230
- <a name='section_Configure_Google_Sheets'></a>
231
- ### Configure Google Sheets
232
-
233
- gsheet.yml needs:
234
- * max_cells, which is the number of cells a sheet is allowed to have
235
- written to it at one time. Default is 50k cells, which is about how
236
- much you can write before things start breaking.
237
- * Because Google Docs ties date formatting to the Locale for the
238
- spreadsheet, there are 2 date format parameters:
239
- * read_date_format, which is the format that should be read FROM google
240
- sheets for date columns.
241
- * sheet_date_format, which is the format that the google sheet is in.
242
- * A date column is defined as one where the column header = "date" or "Date", or ends with "_date" or "Date".
243
- * The defaults are set to US locale for sheet_date_format, because in 'Murica (US) we
244
- use %m/%d/%Y for some reason, and to %Y-%m-%d format for
245
- reading, which is more standard and sorts well as a string. If your
246
- locale is NOT 'Murica you will want to change these.
247
-
248
- Sample gsheet.yml
249
-
250
- ``` yml
251
- ---
252
- development:
253
- max_cells: 400000
254
- read_date_format: "%Y-%m-%d"
255
- sheet_date_format: "%m/%d/%Y"
256
- test:
257
- max_cells: 400000
258
- read_date_format: "%Y-%m-%d"
259
- sheet_date_format: "%m/%d/%Y"
260
- staging:
261
- max_cells: 400000
262
- read_date_format: "%Y-%m-%d"
263
- sheet_date_format: "%m/%d/%Y"
264
- production:
265
- max_cells: 400000
266
- read_date_format: "%Y-%m-%d"
267
- sheet_date_format: "%m/%d/%Y"
268
- ```
269
-
270
- <a name='section_Configure_Jobtracker'></a>
271
- ### Configure Jobtracker
272
-
273
- The Jobtracker sits on your Resque and does 2 things:
274
- * check for Users that are due for polling;
275
- * send out notifications when:
276
- * there are failed jobs on Resque;
277
- * there are jobs on Resque that have run beyond the max run time.
278
-
279
- Emails are sent using ActionMailer, through the owner Google Drive
280
- account.
281
- * errors are sent to the owner of the job/stage as well as the admin group.
282
- * errors not specific to a job/stage are sent to the admin group only, as
283
- given in gdrive.yml
284
-
285
- To this end, it needs these parameters, for which there is a sample
286
- below and in the [lib/samples][git_samples] folder:
287
-
288
- ``` yml
289
- ---
290
- development:
291
- cycle_freq: 10 #time between Jobtracker sweeps
292
- notification_freq: 3600 #1 hour between failure/timeout notifications
293
- runner_read_freq: 300 #5 min between runner reads
294
- max_run_time: 14400 # if a job runs for 4h+, notification will be sent
295
- extensions: [] #additional Mobilize modules to load workers with
296
- test:
297
- cycle_freq: 10 #time between Jobtracker sweeps
298
- notification_freq: 3600 #1 hour between failure/timeout notifications
299
- runner_read_freq: 300 #5 min between runner reads
300
- max_run_time: 14400 # if a job runs for 4h+, notification will be sent
301
- extensions: [] #additional Mobilize modules to load workers with
302
- production:
303
- cycle_freq: 10 #time between Jobtracker sweeps
304
- notification_freq: 3600 #1 hour between failure/timeout notifications
305
- runner_read_freq: 300 #5 min between runner reads
306
- max_run_time: 14400 # if a job runs for 4h+, notification will be sent
307
- extensions: [] #additional Mobilize modules to load workers with
308
- ```
309
-
310
- <a name='section_Configure_Resque'></a>
311
- ### Configure Resque
312
-
313
- Resque keeps track of Jobs, Workers and logging.
314
-
315
- It needs the below parameters, which can be found in the [lib/samples][git_samples] folder.
316
-
317
- * queue_name - the name of the Resque queue where you would like the Jobtracker and Resque Workers to
318
- run. Default is mobilize.
319
- * max_workers - the total number of simultaneous workers you would like
320
- on your queue. Default is 4 for development and test, 36 in
321
- production, but feel free to adjust depending on your hardware.
322
- * redis_port - you should probably leave this alone, it specifies the
323
- default port for dev and prod and a separate one for testing.
324
- * web_port - this specifies the port under which resque-web operates
325
-
326
- ``` yml
327
- ---
328
- development:
329
- queue_name: mobilize
330
- max_workers: 4
331
- redis_port: 6379
332
- web_port: 8282
333
- test:
334
- queue_name: mobilize
335
- max_workers: 4
336
- redis_port: 9736
337
- web_port: 8282
338
- production:
339
- queue_name: mobilize
340
- max_workers: 36
341
- redis_port: 6379
342
- web_port: 8282
343
- ```
344
-
345
- <a name='section_Configure_Resque-Web'></a>
346
- ### Configure Resque-Web
347
-
348
- Please change your default username and password in the resque_web.rb
349
- file in your config folder, reproduced below:
350
-
351
- ``` ruby
352
- #comment out the below if you want no authentication on your web portal (not recommended)
353
- Resque::Server.use(Rack::Auth::Basic) do |user, password|
354
- [user, password] == ['admin', 'changeyourpassword']
355
- end
356
- ```
357
-
358
- This file is passed as a config file argument to
359
- mobilize_base:resque_web task, as detailed in [Start Resque-Web](#section_Start_Start_Resque-Web).
360
-
361
- <a name='section_Configure_Gridfs'></a>
362
- ### Configure Gridfs
363
-
364
- Mobilize stores cached data in MongoDB Gridfs.
365
- It needs the below parameters, which can be found in the [lib/samples][git_samples] folder.
366
-
367
- * max_compressed_write_size - the amount of compressed data Gridfs will
368
- allow. If you try to write more than this, an exception will be thrown.
369
-
370
- ``` yml
371
- ---
372
- development:
373
- max_compressed_write_size: 1000000000 #~1GB
374
- test:
375
- max_compressed_write_size: 1000000000 #~1GB
376
- production:
377
- max_compressed_write_size: 1000000000 #~1GB
378
- ```
379
-
380
- <a name='section_Configure_Mongoid'></a>
381
- ### Configure Mongoid
382
-
383
- Mongoid is the abstraction layer on top of MongoDB so we can interact
384
- with it in an ActiveRecord-like fashion.
385
-
386
- It needs the below parameters, which can be found in the [lib/samples][git_samples] folder.
387
-
388
- You shouldn't need to change anything in this file.
389
-
390
- ``` yml
391
- ---
392
- development:
393
- sessions:
394
- default:
395
- database: mobilize-development
396
- persist_in_safe_mode: true
397
- hosts:
398
- - 127.0.0.1:27017
399
- test:
400
- sessions:
401
- default:
402
- database: mobilize-test
403
- persist_in_safe_mode: true
404
- hosts:
405
- - 127.0.0.1:27017
406
- production:
407
- sessions:
408
- default:
409
- database: mobilize-production
410
- persist_in_safe_mode: true
411
- hosts:
412
- - 127.0.0.1:27017
413
- ```
414
-
415
- <a name='section_Start'></a>
416
- Start
417
- -----
418
-
419
- A Mobilize instance can be considered "started" or "running" when you have:
420
-
421
- 1. Resque workers running on the Mobilize queue;
422
- 2. A Jobtracker running on one of the Resque workers;
423
- 3. One or more Users created in your MongoDB;
424
- 4. One or more Jobs created in a User's Runner;
425
-
426
- <a name='section_Start_Start_resque-web'></a>
427
- ### Start resque-web
428
-
429
- Mobilize ships with its own rake task to start resque web -- you can do
430
- the following:
431
-
432
-
433
- $ MOBILIZE_ENV=<environment> rake mobilize_base:resque_web
434
-
435
- This will start a resque_web instance with the port specified in your
436
- resque.yml and the config/auth scheme specified in your resque_web.rb.
437
-
438
- More detail on the
439
- [Resque-Web Standalone section][resque-web].
440
-
441
- <a name='section_Start_Set_Environment'></a>
442
- ### Set Environment
443
-
444
- Mobilize takes the environment from your Rails.env if you're running
445
- Rails, or assumes "development." You can specify "development", "test",
446
- or "production," as per the yml files.
447
-
448
- Otherwise, it takes it from MOBILIZE_ENV parameter, as in:
449
-
450
- ``` ruby
451
- > ENV['MOBILIZE_ENV'] = 'production'
452
- > require 'mobilize-base'
453
- ```
454
- This affects all parameters as set in the yml files, including the
455
- database.
456
-
457
- <a name='section_Start_Create_User'></a>
458
- ### Create User
459
-
460
- Users are people who use the Mobilize service to move data from one
461
- endpoint to another. They each have a Runner, which is a google sheet
462
- that contains one or more Jobs.
463
-
464
- To create a requestor, use the User.find_or_create_by_name
465
- command (replace the user with your own name, or any name
466
- in your domain).
467
-
468
- ``` ruby
469
- irb> User.find_or_create_by_name("user_name")
470
- ```
471
-
472
- <a name='section_Start_Start_Workers'></a>
473
- ### Start Workers
474
-
475
- Workers are rake tasks that load the Mobilize environment and allow the
476
- processing of the Jobtracker, Users and Jobs.
477
-
478
- These will start as many workers as are defined in your resque.yml.
479
-
480
- To start workers, do:
481
-
482
- ``` ruby
483
- > Jobtracker.prep_workers
484
- ```
485
-
486
- if you have workers already running and would like to kill and refresh
487
- them, do:
488
-
489
- ``` ruby
490
- > Jobtracker.restart_workers!
491
- ```
492
-
493
- Note that restart will kill any workers on the Mobilize queue.
494
-
495
- <a name='section_Start_View_Logs'></a>
496
- ### View Logs
497
-
498
- at this point, you'll want to start viewing the logs for the Resque
499
- workers -- they will be stored under your log folder, by default log/. You can do:
500
-
501
- $ tail -f log/mobilize-`<environment>`.log
502
-
503
- to view them.
504
-
505
- <a name='section_Start_Start_Jobtracker'></a>
506
- ### Start Jobtracker
507
-
508
- Once the Resque workers are running, and you have at least one User
509
- set up, it's time to start the Jobtracker:
510
-
511
- ``` ruby
512
- > Jobtracker.start
513
- ```
514
-
515
- The Jobtracker will automatically enqueue any Users that have not
516
- been processed in the requestor_refresh period defined in the
517
- jobtracker.yml, and create their Runners if they do not exist. You can
518
- see this process on your Resque UI and in the log file.
519
-
520
- <a name='section_Start_Create_Job'></a>
521
- ### Create Job
522
-
523
- Now it's time to go onto the Runner and add a Job to be processed.
524
-
525
- To do this, you should log into your Google Drive with either the
526
- owner's account, an admin account, or the Runner User's account. These
527
- will be the accounts with edit permissions to a given Runner.
528
-
529
- Navigate to the Jobs tab on the Runner `(denoted by Runner(<requestor
530
- name>))` and enter values under each header:
531
-
532
- * name This is the name of the job you would like to add. Names must be unique across all your jobs, otherwise you will get an error
533
-
534
- * active set this to blank or FALSE if you want to turn off a job
535
-
536
- * trigger This uses human readable syntax to schedule jobs. It accepts the following:
537
- * every `<integer>` hour -- fire the job at increments of `<integer>` hours, minimum of 1 hour
538
- * every `<integer>` day -- fire the job at increments of `<integer>` days, minimum of 1
539
- * every `<integer>` day after <HH:MM> -- fire the job at increments of <integer> days, after HH:MM UTC time
540
- * every `<integer>` day_of_week after <HH:MM> -- fire the job on specified day of week, after HH:MM UTC time; 1=Sunday
541
- * every `<integer>` day_of_month after <HH:MM> -- fire the job on specified day of month, after HH:MM UTC time
542
- * once -- fire the job once if active is set to TRUE, set active to FALSE right after
543
- * after `<jobname>` -- fire the job after the job named `<jobname>`
544
-
545
- * status Mobilize writes this field with the last status returned by the job
546
-
547
- * stage1..stage5 List of stages to be performed by the job.
548
- * Stages have this syntax: `<handler>.<call> <params>`.
549
- * handler specifies the file that should receive the stage
550
- * the call specifies the method within the file. The method should
551
- be called `"<handler>.<call>_by_stage_path"`
552
- * the params the method accepts, which are custom to each
553
- stage. These should be of the for `<key1>: <value1>, <key2>: <value2>`, where
554
- `<key>` is an unquoted string and `<value>` is a quoted string, an
555
- integer, an array (delimited by square braces), or a hash (delimited by
556
- curly braces).
557
- * For mobilize-base, the following stage is available:
558
- * gsheet.write `source: <input_path>`, which reads the sheet.
559
- * The input_path should be of the form:
560
- * `<gbook_name>/<gsheet_name>` or just `<gsheet_name>` if the target is in
561
- the Runner itself.
562
- * `gfile://<gfile_name>` if the target is a file.
563
- * The file must be owned by the Gdrive owner.
564
- * The test uses "gfile://test_base_1.tsv".
565
- * The stage_name should be of the form `<stage_column>`. The test uses "stage1" for the first test
566
- and "base1.out" for the second test. The first
567
- takes the output from the first stage and the second reads it straight
568
- from the referenced sheet.
569
- * All stages accept retry parameters:
570
- * retries: an integer specifying the number of times that the system will try it again before giving up.
571
- * delay: an integer specifying the number of seconds between retries.
572
- * always_on: if false, turns the job off on stage failures.
573
- Otherwise the job will retry from the beginning with the same frequency as the Runner refresh rate.
574
- * notify: by default, the stage owner will be notified on failure.
575
- * if false, will not notify the stage owner in the event of a failure.
576
- * If it's an email address, will email the specified person.
577
- * If a stage fails after all retries, it will output its standard error to a tab in the Runner with the name of the job, the name of the stage, and a ".err" extension
578
- * The tab will be headed "response" and will contain the exception and backtrace for the error.
579
- * The test uses "Requestor_mobilize(test)/base1.out" and
580
- "Runner_mobilize(test)/base2.out" for target sheets.
581
-
582
- <a name='section_Start_Run_Test'></a>
583
- ### Run Test
584
-
585
- To run tests, you will need to
586
-
587
- 1) clone the repository
588
-
589
- From the project folder, run
590
-
591
- 2) rake mobilize_base:setup
592
-
593
- and populate the "test" environment in the config files with the
594
- necessary details.
595
-
596
- 3) $ rake test
597
-
598
- This will create a test Runner with a sample job. These will run off a
599
- test redis instance which will be killed once the tests finish.
600
-
601
- <a name='section_Start_'></a>
602
- ### Run Test
603
-
604
- To run tests, you will need to
605
-
606
- 1) clone the repository
607
-
608
- From the project folder, run
609
-
610
- 2) rake mobilize_base:setup
611
-
612
- and populate the "test" environment in the config files with the
613
- necessary details.
614
-
615
- 3) $ rake test
616
-
617
- This will create a test Runner with a sample job. These will run off a
618
- test redis instance. This instance will be kept alive so you can test
619
- additional Mobilize modules. (see [mobilize-ssh][mobilize-ssh] for more)
620
-
621
- <a name='section_Start_Add_Gbooks_And_Gsheets'></a>
622
- ### Add Gbooks and Gsheets
623
-
624
- A User's Runner should be kept clean, preferably with only the jobs
625
- sheet. The test keeps everything in the
626
- Runner, but in reality you will want to create lots of different books
627
- to share with different people in your organization.
628
-
629
- To add a new Gbook, create one as you normally would, then make sure the
630
- Owner is the same user as specified in your gdrive.yml/owner/name value.
631
- Mobilize will handle the rest, extending permissions to workers and
632
- admins.
633
-
634
- Also make sure any Gsheets you specify for __read__ operations exist
635
- prior to calling the job, or there will be an error. __Write__
636
- operations will create the book and sheet if it does not already exist,
637
- already under ownership of the owner account.
638
-
639
- <a name='section_Meta'></a>
640
- Meta
641
- ----
642
-
643
- * Code: `git clone git://github.com/ngmoco/mobilize-base.git`
644
- * Home: <https://github.com/ngmoco/mobilize-base>
645
- * Bugs: <https://github.com/ngmoco/mobilize-base/issues>
646
- * Gems: <http://rubygems.org/gems/mobilize-base>
647
-
648
- <a name='section_Author'></a>
649
- Author
650
- ------
651
-
652
- Cassio Paes-Leme :: cpaesleme@ngmoco.com :: @cpaesleme
653
-
654
- <a name='section_Special_Thanks'></a>
655
- Special Thanks
656
- --------------
657
-
658
- * Al Thompson and Sagar Mehta for awesome design advice and discussions
659
- * Elliott Clark for enlightening me to the wonders of Resque
660
- * Bob Colner for pointing me to google-drive-ruby when I tried to
661
- reinvent the wheel
662
- * ngmoco:) and DeNA Global for supporting and adopting the Mobilize
663
- platform
664
- * gimite, defunkt, 10gen, and the countless other github heroes and
665
- crewmembers.
666
-
667
- [google_drive_ruby]: https://github.com/gimite/google-drive-ruby
668
- [resque]: https://github.com/defunkt/resque
669
- [mongoid]: http://mongoid.org/en/mongoid/index.html
670
- [resque_redis]: https://github.com/defunkt/resque#section_Installing_Redis
671
- [mongodb_quickstart]: http://www.mongodb.org/display/DOCS/Quickstart
672
- [git_samples]: https://github.com/ngmoco/mobilize-base/tree/master/lib/samples
673
- [rvm]: https://rvm.io/
674
- [resque-web]: https://github.com/defunkt/resque#standalone
675
- [mobilize-ssh]: https://github.com/ngmoco/mobilize-ssh
4
+ Please refer to the mobilize-server wiki: https://github.com/DeNA/mobilize-server/wiki
@@ -12,11 +12,16 @@ class Array
12
12
  return self.inject{|sum,x| sum + x }
13
13
  end
14
14
  def hash_array_to_tsv
15
- if self.first.nil? or self.first.class!=Hash
15
+ ha = self
16
+ if ha.first.nil? or ha.first.class!=Hash
16
17
  return ""
17
18
  end
18
- header = self.first.keys.join("\t")
19
- rows = self.map{|r| r.values.join("\t")}
19
+ max_row_length = ha.map{|h| h.keys.length}.max
20
+ header_keys = ha.select{|h| h.keys.length==max_row_length}.first.keys
21
+ header = header_keys.join("\t")
22
+ rows = ha.map do |r|
23
+ header_keys.map{|k| r[k]}.join("\t")
24
+ end
20
25
  ([header] + rows).join("\n")
21
26
  end
22
27
  end
@@ -14,7 +14,7 @@ module GoogleDrive
14
14
  def push(entry)
15
15
  #do not send email notifications
16
16
  entry = AclEntry.new(entry) if entry.is_a?(Hash)
17
- url_suffix = "?send-notification-emails=false"
17
+ url_suffix = ((@acls_feed_url.index("?") ? "&" : "?") + "send-notification-emails=false")
18
18
  header = {"GData-Version" => "3.0", "Content-Type" => "application/atom+xml"}
19
19
  doc = @session.request(:post, "#{@acls_feed_url}#{url_suffix}", :data => entry.to_xml(), :header => header, :auth => :writely)
20
20
  entry.params = entry_to_params(doc.root)