calagator 0.0.1.pre1

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.
Files changed (384) hide show
  1. checksums.yaml +7 -0
  2. data/LICENSE.txt +23 -0
  3. data/README.md +75 -0
  4. data/Rakefile +12 -0
  5. data/app/assets/images/confused-alligator-sm.png +0 -0
  6. data/app/assets/images/disk.png +0 -0
  7. data/app/assets/images/edit.png +0 -0
  8. data/app/assets/images/external_sites/epdx.png +0 -0
  9. data/app/assets/images/external_sites/external.gif +0 -0
  10. data/app/assets/images/external_sites/facebook.png +0 -0
  11. data/app/assets/images/external_sites/foursquare.png +0 -0
  12. data/app/assets/images/external_sites/gowalla.png +0 -0
  13. data/app/assets/images/external_sites/lanyrd.png +0 -0
  14. data/app/assets/images/external_sites/meetup.png +0 -0
  15. data/app/assets/images/external_sites/plancast.png +0 -0
  16. data/app/assets/images/external_sites/shizzow.png +0 -0
  17. data/app/assets/images/external_sites/upcoming.png +0 -0
  18. data/app/assets/images/external_sites/yelp.png +0 -0
  19. data/app/assets/images/feed.png +0 -0
  20. data/app/assets/images/heart.png +0 -0
  21. data/app/assets/images/icon_ical.gif +0 -0
  22. data/app/assets/images/information.png +0 -0
  23. data/app/assets/images/nav_marker.gif +0 -0
  24. data/app/assets/images/nav_marker.png +0 -0
  25. data/app/assets/images/plus.png +0 -0
  26. data/app/assets/images/redx.png +0 -0
  27. data/app/assets/images/site-icon.png +0 -0
  28. data/app/assets/images/spinner.gif +0 -0
  29. data/app/assets/images/star.png +0 -0
  30. data/app/assets/images/subnav_marker.gif +0 -0
  31. data/app/assets/images/subnav_marker.png +0 -0
  32. data/app/assets/images/tag_blue.png +0 -0
  33. data/app/assets/images/tag_icons/angular.png +0 -0
  34. data/app/assets/images/tag_icons/beer.png +0 -0
  35. data/app/assets/images/tag_icons/bitcoin.png +0 -0
  36. data/app/assets/images/tag_icons/free.png +0 -0
  37. data/app/assets/images/tag_icons/golang.png +0 -0
  38. data/app/assets/images/tag_icons/html.png +0 -0
  39. data/app/assets/images/tag_icons/javascript.png +0 -0
  40. data/app/assets/images/tag_icons/linux.png +0 -0
  41. data/app/assets/images/tag_icons/php.png +0 -0
  42. data/app/assets/images/tag_icons/pizza.png +0 -0
  43. data/app/assets/images/tag_icons/python.png +0 -0
  44. data/app/assets/images/tag_icons/rails.png +0 -0
  45. data/app/assets/images/tag_icons/ruby.png +0 -0
  46. data/app/assets/images/tag_icons/sass.png +0 -0
  47. data/app/assets/images/tag_icons/swift.png +0 -0
  48. data/app/assets/images/transmit_blue.png +0 -0
  49. data/app/assets/images/weekday_background.gif +0 -0
  50. data/app/assets/javascripts/calagator.js +18 -0
  51. data/app/assets/javascripts/calagator/forms.js +60 -0
  52. data/app/assets/stylesheets/calagator.scss +8 -0
  53. data/app/assets/stylesheets/calagator/common.scss +14 -0
  54. data/app/assets/stylesheets/calagator/datepicker.scss +177 -0
  55. data/app/assets/stylesheets/calagator/errors.css +110 -0
  56. data/app/assets/stylesheets/calagator/forms.scss +4 -0
  57. data/app/assets/stylesheets/calagator/reset.css +40 -0
  58. data/app/assets/stylesheets/calagator/theme.css +0 -0
  59. data/app/controllers/calagator/admin_controller.rb +27 -0
  60. data/app/controllers/calagator/application_controller.rb +82 -0
  61. data/app/controllers/calagator/events_controller.rb +171 -0
  62. data/app/controllers/calagator/site_controller.rb +35 -0
  63. data/app/controllers/calagator/sources_controller.rb +94 -0
  64. data/app/controllers/calagator/venues_controller.rb +133 -0
  65. data/app/controllers/calagator/versions_controller.rb +20 -0
  66. data/app/helpers/calagator/application_helper.rb +128 -0
  67. data/app/helpers/calagator/events_helper.rb +184 -0
  68. data/app/helpers/calagator/google_event_export_helper.rb +67 -0
  69. data/app/helpers/calagator/mapping_helper.rb +103 -0
  70. data/app/helpers/calagator/sources_helper.rb +10 -0
  71. data/app/helpers/calagator/tags_helper.rb +42 -0
  72. data/app/helpers/calagator/time_range_helper.rb +166 -0
  73. data/app/models/calagator/event.rb +246 -0
  74. data/app/models/calagator/event/cloner.rb +39 -0
  75. data/app/models/calagator/event/ical_renderer.rb +155 -0
  76. data/app/models/calagator/event/overview.rb +45 -0
  77. data/app/models/calagator/event/saver.rb +58 -0
  78. data/app/models/calagator/event/search.rb +72 -0
  79. data/app/models/calagator/event/search_engine.rb +28 -0
  80. data/app/models/calagator/event/search_engine/apache_sunspot.rb +106 -0
  81. data/app/models/calagator/event/search_engine/sql.rb +107 -0
  82. data/app/models/calagator/source.rb +115 -0
  83. data/app/models/calagator/source/importer.rb +46 -0
  84. data/app/models/calagator/source/parser.rb +128 -0
  85. data/app/models/calagator/source/parser/facebook.rb +67 -0
  86. data/app/models/calagator/source/parser/hcal.rb +108 -0
  87. data/app/models/calagator/source/parser/http_authentication_required_error.rb +6 -0
  88. data/app/models/calagator/source/parser/ical.rb +186 -0
  89. data/app/models/calagator/source/parser/meetup.rb +72 -0
  90. data/app/models/calagator/source/parser/not_found.rb +9 -0
  91. data/app/models/calagator/source/parser/plancast.rb +59 -0
  92. data/app/models/calagator/venue.rb +161 -0
  93. data/app/models/calagator/venue/geocoder.rb +51 -0
  94. data/app/models/calagator/venue/search.rb +63 -0
  95. data/app/models/calagator/venue/search_engine.rb +24 -0
  96. data/app/models/calagator/venue/search_engine/apache_sunspot.rb +85 -0
  97. data/app/models/calagator/venue/search_engine/sql.rb +68 -0
  98. data/app/models/event/search_engine/base.rb +0 -0
  99. data/app/observers/calagator/cache_observer.rb +37 -0
  100. data/app/views/calagator/admin/events.html.erb +28 -0
  101. data/app/views/calagator/admin/index.html.erb +8 -0
  102. data/app/views/calagator/events/_feed_item.html.erb +62 -0
  103. data/app/views/calagator/events/_form.html.erb +63 -0
  104. data/app/views/calagator/events/_gcal_reminder.html.erb +1 -0
  105. data/app/views/calagator/events/_hcal.html.erb +41 -0
  106. data/app/views/calagator/events/_item.html.erb +120 -0
  107. data/app/views/calagator/events/_list.html.erb +30 -0
  108. data/app/views/calagator/events/_list_item.html.erb +37 -0
  109. data/app/views/calagator/events/_search_section.html.erb +15 -0
  110. data/app/views/calagator/events/_subnav.html.erb +13 -0
  111. data/app/views/calagator/events/_table.html.erb +74 -0
  112. data/app/views/calagator/events/duplicates.html.erb +58 -0
  113. data/app/views/calagator/events/edit.html.erb +3 -0
  114. data/app/views/calagator/events/index.atom.builder +32 -0
  115. data/app/views/calagator/events/index.html.erb +51 -0
  116. data/app/views/calagator/events/index.kml.erb +20 -0
  117. data/app/views/calagator/events/new.html.erb +7 -0
  118. data/app/views/calagator/events/search.html.erb +25 -0
  119. data/app/views/calagator/events/show.html.erb +81 -0
  120. data/app/views/calagator/site/_appropriateness_message.html.erb +15 -0
  121. data/app/views/calagator/site/_description.html.erb +3 -0
  122. data/app/views/calagator/site/about.html.erb +7 -0
  123. data/app/views/calagator/site/defunct.html.erb +15 -0
  124. data/app/views/calagator/site/export.html.erb +4 -0
  125. data/app/views/calagator/site/index.html.erb +40 -0
  126. data/app/views/calagator/site/opensearch.xml.builder +8 -0
  127. data/app/views/calagator/sources/_subnav.html.erb +0 -0
  128. data/app/views/calagator/sources/edit.html.erb +12 -0
  129. data/app/views/calagator/sources/import.html.erb +10 -0
  130. data/app/views/calagator/sources/index.html.erb +22 -0
  131. data/app/views/calagator/sources/new.html.erb +32 -0
  132. data/app/views/calagator/sources/show.html.erb +16 -0
  133. data/app/views/calagator/venues/_form.html.erb +57 -0
  134. data/app/views/calagator/venues/_subnav.html.erb +10 -0
  135. data/app/views/calagator/venues/duplicates.html.erb +65 -0
  136. data/app/views/calagator/venues/edit.html.erb +3 -0
  137. data/app/views/calagator/venues/index.html.erb +93 -0
  138. data/app/views/calagator/venues/index.kml.erb +15 -0
  139. data/app/views/calagator/venues/map.html.erb +4 -0
  140. data/app/views/calagator/venues/new.html.erb +5 -0
  141. data/app/views/calagator/venues/show.html.erb +113 -0
  142. data/app/views/calagator/versions/_chooser.html.erb +28 -0
  143. data/app/views/calagator/versions/_edit_with_chooser.html.erb +16 -0
  144. data/app/views/layouts/calagator/application.html.erb +110 -0
  145. data/config/deploy/local.rb +37 -0
  146. data/config/deploy/lucca.rb +33 -0
  147. data/config/initializers/dates.rb +7 -0
  148. data/config/initializers/formtastic.rb +82 -0
  149. data/config/initializers/geokit.rb +74 -0
  150. data/config/initializers/ics_renderer.rb +6 -0
  151. data/config/initializers/load_tag_model_extensions.rb +3 -0
  152. data/config/initializers/mime_types.rb +9 -0
  153. data/config/initializers/observers.rb +1 -0
  154. data/config/initializers/search_engine.rb +4 -0
  155. data/config/initializers/set_default_url_host.rb +5 -0
  156. data/config/initializers/time_get_zone.rb +8 -0
  157. data/config/locales/en.yml +5 -0
  158. data/config/routes.rb +55 -0
  159. data/config/secrets.yml.blag +75 -0
  160. data/config/sunspot.yml +23 -0
  161. data/db/development.sqlite3 +0 -0
  162. data/db/development.sqlite3.bak +0 -0
  163. data/db/development.sqlite3.old +0 -0
  164. data/db/development~20111112@110950.sqlite3 +0 -0
  165. data/db/migrate/001_create_events.rb +17 -0
  166. data/db/migrate/002_create_venues.rb +17 -0
  167. data/db/migrate/003_create_sources.rb +16 -0
  168. data/db/migrate/004_add_detailed_fields_to_venue.rb +19 -0
  169. data/db/migrate/005_add_end_time_to_events.rb +9 -0
  170. data/db/migrate/006_add_source_id_to_events.rb +9 -0
  171. data/db/migrate/008_add_source_id_to_venues.rb +10 -0
  172. data/db/migrate/009_add_duplicate_of_column_to_venues.rb +9 -0
  173. data/db/migrate/010_add_duplicate_of_column_to_events.rb +9 -0
  174. data/db/migrate/011_change_lat_long_type.rb +12 -0
  175. data/db/migrate/012_add_source_reimport.rb +9 -0
  176. data/db/migrate/013_change_end_time_to_duration.rb +11 -0
  177. data/db/migrate/014_remove_format_type_from_source.rb +9 -0
  178. data/db/migrate/015_create_updates.rb +15 -0
  179. data/db/migrate/016_remove_next_update_from_source.rb +9 -0
  180. data/db/migrate/20080705163959_change_duration_to_end_time.rb +11 -0
  181. data/db/migrate/20080705164959_create_tags_and_taggings.rb +28 -0
  182. data/db/migrate/20081011181519_create_versioned_events.rb +25 -0
  183. data/db/migrate/20081011193124_create_versioned_venues.rb +32 -0
  184. data/db/migrate/20081115190515_add_rrule_to_events.rb +15 -0
  185. data/db/migrate/20090912082129_create_versions.rb +18 -0
  186. data/db/migrate/20110219205156_add_closed_flag_to_venues.rb +9 -0
  187. data/db/migrate/20110220001008_add_wifi_flag_to_venues.rb +9 -0
  188. data/db/migrate/20110220011427_add_access_notes_to_venues.rb +9 -0
  189. data/db/migrate/20110220031117_add_events_count_to_venues.rb +8 -0
  190. data/db/migrate/20110604174521_add_venue_details_to_events.rb +9 -0
  191. data/db/migrate/20110717231316_acts_as_taggable_on_migration.rb +50 -0
  192. data/db/migrate/20120709092821_cleanup.rb +14 -0
  193. data/db/migrate/20120831234448_specify_venues_latitude_and_longitude_precision.rb +11 -0
  194. data/db/migrate/20150206085809_remove_updates.rb +13 -0
  195. data/db/migrate/20150207231355_add_locked_status_to_events.rb +5 -0
  196. data/db/production.sqlite3 +0 -0
  197. data/db/schema.rb +102 -0
  198. data/db/seeds.rb +16 -0
  199. data/db/test.sqlite3 +0 -0
  200. data/db/test2.sqlite3 +0 -0
  201. data/lib/calagator.rb +30 -0
  202. data/lib/calagator/blacklist_validator.rb +69 -0
  203. data/lib/calagator/decode_html_entities_hack.rb +33 -0
  204. data/lib/calagator/duplicate_checking.rb +133 -0
  205. data/lib/calagator/duplicate_checking/controller_actions.rb +38 -0
  206. data/lib/calagator/duplicate_checking/duplicate_finder.rb +83 -0
  207. data/lib/calagator/duplicate_checking/duplicate_squasher.rb +74 -0
  208. data/lib/calagator/engine.rb +30 -0
  209. data/lib/calagator/strip_whitespace.rb +19 -0
  210. data/lib/calagator/tag_model_extensions.rb +104 -0
  211. data/lib/calagator/url_prefixer.rb +9 -0
  212. data/lib/calagator/version.rb +3 -0
  213. data/lib/generators/calagator/install_generator.rb +39 -0
  214. data/lib/generators/calagator/templates/config/calagator.rb +26 -0
  215. data/lib/generators/calagator/templates/config/secrets.yml.sample +83 -0
  216. data/lib/secrets_reader.rb +76 -0
  217. data/lib/tasks/spec_db.rake +53 -0
  218. data/lib/tasks/sunspot_reindex_calagator.rake +6 -0
  219. data/lib/tasks/sunspot_solr_restart_enhancements.rake +19 -0
  220. data/lib/tasks/update_counter_caches.rake +13 -0
  221. data/lib/templates/erb/scaffold/_form.html.erb +11 -0
  222. data/lib/theme_reader.rb +17 -0
  223. data/lib/wait_for_solr.rb +25 -0
  224. data/spec/controllers/calagator/application_controller_spec.rb +47 -0
  225. data/spec/controllers/calagator/events_controller_spec.rb +794 -0
  226. data/spec/controllers/calagator/site_controller_spec.rb +59 -0
  227. data/spec/controllers/calagator/sources_controller_spec.rb +439 -0
  228. data/spec/controllers/calagator/venues_controller_spec.rb +319 -0
  229. data/spec/controllers/calagator/versions_controller_spec.rb +82 -0
  230. data/spec/controllers/squash_many_duplicates_examples.rb +49 -0
  231. data/spec/dummy/Gemfile +39 -0
  232. data/spec/dummy/Gemfile.lock +195 -0
  233. data/spec/dummy/README.rdoc +261 -0
  234. data/spec/dummy/Rakefile +7 -0
  235. data/spec/dummy/app/assets/images/rails.png +0 -0
  236. data/spec/dummy/app/assets/javascripts/application.js +16 -0
  237. data/spec/dummy/app/assets/stylesheets/application.css +14 -0
  238. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  239. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  240. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  241. data/spec/dummy/config.ru +4 -0
  242. data/spec/dummy/config/application.rb +62 -0
  243. data/spec/dummy/config/boot.rb +6 -0
  244. data/spec/dummy/config/database.yml +25 -0
  245. data/spec/dummy/config/environment.rb +5 -0
  246. data/spec/dummy/config/environments/development.rb +37 -0
  247. data/spec/dummy/config/environments/production.rb +67 -0
  248. data/spec/dummy/config/environments/test.rb +37 -0
  249. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  250. data/spec/dummy/config/initializers/calagator.rb +26 -0
  251. data/spec/dummy/config/initializers/inflections.rb +15 -0
  252. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  253. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  254. data/spec/dummy/config/initializers/session_store.rb +8 -0
  255. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  256. data/spec/dummy/config/locales/en.yml +5 -0
  257. data/spec/dummy/config/routes.rb +59 -0
  258. data/spec/dummy/config/secrets.yml +83 -0
  259. data/spec/dummy/db/development.sqlite3 +0 -0
  260. data/spec/dummy/db/migrate/20150309023304_create_events.calagator.rb +18 -0
  261. data/spec/dummy/db/migrate/20150309023305_create_venues.calagator.rb +18 -0
  262. data/spec/dummy/db/migrate/20150309023306_create_sources.calagator.rb +17 -0
  263. data/spec/dummy/db/migrate/20150309023307_add_detailed_fields_to_venue.calagator.rb +20 -0
  264. data/spec/dummy/db/migrate/20150309023308_add_end_time_to_events.calagator.rb +10 -0
  265. data/spec/dummy/db/migrate/20150309023309_add_source_id_to_events.calagator.rb +10 -0
  266. data/spec/dummy/db/migrate/20150309023310_add_source_id_to_venues.calagator.rb +11 -0
  267. data/spec/dummy/db/migrate/20150309023311_add_duplicate_of_column_to_venues.calagator.rb +10 -0
  268. data/spec/dummy/db/migrate/20150309023312_add_duplicate_of_column_to_events.calagator.rb +10 -0
  269. data/spec/dummy/db/migrate/20150309023313_change_lat_long_type.calagator.rb +13 -0
  270. data/spec/dummy/db/migrate/20150309023314_add_source_reimport.calagator.rb +10 -0
  271. data/spec/dummy/db/migrate/20150309023315_change_end_time_to_duration.calagator.rb +12 -0
  272. data/spec/dummy/db/migrate/20150309023316_remove_format_type_from_source.calagator.rb +10 -0
  273. data/spec/dummy/db/migrate/20150309023317_create_updates.calagator.rb +16 -0
  274. data/spec/dummy/db/migrate/20150309023318_remove_next_update_from_source.calagator.rb +10 -0
  275. data/spec/dummy/db/migrate/20150309023319_change_duration_to_end_time.calagator.rb +12 -0
  276. data/spec/dummy/db/migrate/20150309023320_create_tags_and_taggings.calagator.rb +29 -0
  277. data/spec/dummy/db/migrate/20150309023321_create_versioned_events.calagator.rb +26 -0
  278. data/spec/dummy/db/migrate/20150309023322_create_versioned_venues.calagator.rb +33 -0
  279. data/spec/dummy/db/migrate/20150309023323_add_rrule_to_events.calagator.rb +16 -0
  280. data/spec/dummy/db/migrate/20150309023324_create_versions.calagator.rb +19 -0
  281. data/spec/dummy/db/migrate/20150309023325_add_closed_flag_to_venues.calagator.rb +10 -0
  282. data/spec/dummy/db/migrate/20150309023326_add_wifi_flag_to_venues.calagator.rb +10 -0
  283. data/spec/dummy/db/migrate/20150309023327_add_access_notes_to_venues.calagator.rb +10 -0
  284. data/spec/dummy/db/migrate/20150309023328_add_events_count_to_venues.calagator.rb +9 -0
  285. data/spec/dummy/db/migrate/20150309023329_add_venue_details_to_events.calagator.rb +10 -0
  286. data/spec/dummy/db/migrate/20150309023330_acts_as_taggable_on_migration.calagator.rb +51 -0
  287. data/spec/dummy/db/migrate/20150309023331_cleanup.calagator.rb +15 -0
  288. data/spec/dummy/db/migrate/20150309023332_specify_venues_latitude_and_longitude_precision.calagator.rb +12 -0
  289. data/spec/dummy/db/migrate/20150309023333_remove_updates.calagator.rb +14 -0
  290. data/spec/dummy/db/migrate/20150309023334_add_locked_status_to_events.calagator.rb +6 -0
  291. data/spec/dummy/db/schema.rb +95 -0
  292. data/spec/dummy/db/seeds.rb +7 -0
  293. data/spec/dummy/db/test.sqlite3 +0 -0
  294. data/spec/dummy/doc/README_FOR_APP +2 -0
  295. data/spec/dummy/log/development.log +273 -0
  296. data/spec/dummy/public/404.html +26 -0
  297. data/spec/dummy/public/422.html +26 -0
  298. data/spec/dummy/public/500.html +25 -0
  299. data/spec/dummy/public/favicon.ico +0 -0
  300. data/spec/dummy/public/robots.txt +5 -0
  301. data/spec/dummy/script/rails +6 -0
  302. data/spec/dummy/test/performance/browsing_test.rb +12 -0
  303. data/spec/dummy/test/test_helper.rb +13 -0
  304. data/spec/factories.rb +93 -0
  305. data/spec/features/add_event_spec.rb +99 -0
  306. data/spec/features/add_venue_spec.rb +34 -0
  307. data/spec/features/admin_auth_spec.rb +22 -0
  308. data/spec/features/admin_lock_event_spec.rb +41 -0
  309. data/spec/features/import_events_from_feed_spec.rb +43 -0
  310. data/spec/features/managing_event_spec.rb +111 -0
  311. data/spec/features/managing_venue_spec.rb +71 -0
  312. data/spec/features/search_event_spec.rb +27 -0
  313. data/spec/helpers/calagator/application_helper_spec.rb +82 -0
  314. data/spec/helpers/calagator/events_helper_spec.rb +172 -0
  315. data/spec/helpers/calagator/google_event_export_helper_spec.rb +70 -0
  316. data/spec/helpers/calagator/sources_helper_spec.rb +12 -0
  317. data/spec/helpers/calagator/tags_helper_spec.rb +85 -0
  318. data/spec/helpers/calagator/time_range_helper_spec.rb +59 -0
  319. data/spec/lib/calagator/blacklist_validator_spec.rb +65 -0
  320. data/spec/lib/calagator/decode_html_entities_hack_spec.rb +54 -0
  321. data/spec/lib/calagator/settings_spec.rb +20 -0
  322. data/spec/lib/calagator/url_prefixer_spec.rb +33 -0
  323. data/spec/lib/secrets_reader_spec.rb +65 -0
  324. data/spec/models/calagator/event/cloner_spec.rb +43 -0
  325. data/spec/models/calagator/event/overview_spec.rb +79 -0
  326. data/spec/models/calagator/event/search_spec.rb +103 -0
  327. data/spec/models/calagator/event_search_spec.rb +149 -0
  328. data/spec/models/calagator/event_spec.rb +859 -0
  329. data/spec/models/calagator/source/parser_facebook_spec.rb +73 -0
  330. data/spec/models/calagator/source/parser_hcal_spec.rb +69 -0
  331. data/spec/models/calagator/source/parser_ical_non_standard_spec.rb +91 -0
  332. data/spec/models/calagator/source/parser_ical_spec.rb +322 -0
  333. data/spec/models/calagator/source/parser_meetup_spec.rb +69 -0
  334. data/spec/models/calagator/source/parser_plancast_spec.rb +53 -0
  335. data/spec/models/calagator/source/parser_spec.rb +238 -0
  336. data/spec/models/calagator/source_spec.rb +135 -0
  337. data/spec/models/calagator/venue/search_spec.rb +92 -0
  338. data/spec/models/calagator/venue_search_spec.rb +124 -0
  339. data/spec/models/calagator/venue_spec.rb +346 -0
  340. data/spec/models/tag_spec.rb +35 -0
  341. data/spec/rails_helper.rb +39 -0
  342. data/spec/spec_helper.rb +140 -0
  343. data/spec/support/disable_geocoding.rb +1 -0
  344. data/spec/support/http_samples.rb +5 -0
  345. data/spec/support/samples/facebook.json +23 -0
  346. data/spec/support/samples/hcal_basic.xml +6 -0
  347. data/spec/support/samples/hcal_dup_event_dup_venue.xml +13 -0
  348. data/spec/support/samples/hcal_event_duplicates_fixture.xml +13 -0
  349. data/spec/support/samples/hcal_event_wo_lat_and_long.xml +14 -0
  350. data/spec/support/samples/hcal_multiple.xml +16 -0
  351. data/spec/support/samples/hcal_same_event_twice_with_different_venues.xml +12 -0
  352. data/spec/support/samples/hcal_single.xml +8 -0
  353. data/spec/support/samples/hcal_two_identical_events.xml +14 -0
  354. data/spec/support/samples/hcal_upcoming_v1.html +412 -0
  355. data/spec/support/samples/hcal_upcoming_v2.html +749 -0
  356. data/spec/support/samples/hcal_upcoming_v3.html +685 -0
  357. data/spec/support/samples/hcal_upcoming_v4.html +761 -0
  358. data/spec/support/samples/ical_apple.ics +22 -0
  359. data/spec/support/samples/ical_apple_v3.ics +37 -0
  360. data/spec/support/samples/ical_basic.ics +15 -0
  361. data/spec/support/samples/ical_basic_with_duration.ics +16 -0
  362. data/spec/support/samples/ical_event_with_squashed_venue.ics +12 -0
  363. data/spec/support/samples/ical_eventful_many.ics +504 -0
  364. data/spec/support/samples/ical_gmt.ics +12 -0
  365. data/spec/support/samples/ical_google.ics +786 -0
  366. data/spec/support/samples/ical_multiple_calendars.ics +111 -0
  367. data/spec/support/samples/ical_upcoming.ics +36 -0
  368. data/spec/support/samples/ical_upcoming_many.ics +682 -0
  369. data/spec/support/samples/ical_upcoming_v2.ics +43 -0
  370. data/spec/support/samples/ical_z.ics +10 -0
  371. data/spec/support/samples/meetup.ics +16 -0
  372. data/spec/support/samples/meetup.json +31 -0
  373. data/spec/support/samples/plancast.ics +59 -0
  374. data/spec/support/samples/plancast.json +51 -0
  375. data/spec/support/samples/plancast_with_missing_venue.json +39 -0
  376. data/spec/support/samples/upcoming_v1.xml +8 -0
  377. data/spec/support/samples/upcoming_v2_with_invalid_utc_dates.xml +8 -0
  378. data/spec/support/time_convenience_methods.rb +8 -0
  379. data/spec/support/time_zones.rb +12 -0
  380. data/spec/support/url_helpers.rb +5 -0
  381. data/spec/support/wait_for_ajax.rb +15 -0
  382. data/spec/support/webmock.rb +8 -0
  383. data/spec/travis_spec.rb +7 -0
  384. metadata +1194 -0
@@ -0,0 +1,16 @@
1
+ # ruby encoding: utf-8
2
+
3
+ # This file should contain all the record creation needed to seed the database with its default values.
4
+ # The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
5
+ #
6
+ # Examples:
7
+ #
8
+ # cities = City.create([{ :name => 'Chicago' }, { :name => 'Copenhagen' }])
9
+ # Mayor.create(:name => 'Daley', :city => cities.first)
10
+
11
+ require 'faker'
12
+
13
+ FactoryGirl.create_list(:seed_venue, 25, :with_events)
14
+ FactoryGirl.create_list(:seed_venue, 25)
15
+ FactoryGirl.create_list(:seed_event, 25, :with_venue)
16
+ FactoryGirl.create_list(:seed_event, 25)
Binary file
Binary file
@@ -0,0 +1,30 @@
1
+ require "calagator/engine"
2
+
3
+ require "formtastic"
4
+ require "rails_autolink"
5
+ require "nokogiri"
6
+ require "columnize"
7
+ require "geokit"
8
+ require "htmlentities"
9
+ require "paper_trail"
10
+ require "ri_cal"
11
+ require "will_paginate"
12
+ require "will_paginate/array"
13
+ require "rest-client"
14
+ require "loofah"
15
+ require "loofah-activerecord"
16
+ require "bluecloth"
17
+ require "formtastic"
18
+ require "acts-as-taggable-on"
19
+ require "jquery-rails"
20
+ require "jquery-ui-rails"
21
+ require "progress_bar"
22
+ require "font-awesome-rails"
23
+ require "paper_trail_manager"
24
+ require "utf8-cleaner"
25
+ require "sunspot_rails"
26
+ require "sunspot_solr"
27
+ require "lucene_query"
28
+
29
+ module Calagator
30
+ end
@@ -0,0 +1,69 @@
1
+ # = BlacklistValidator
2
+ #
3
+ # A naively simple mixin that blacklists content in ActiveModel objects.
4
+ #
5
+ # == Usage
6
+ #
7
+ # Let's say that your applications lets people post messages, but don't want
8
+ # them using the word "viagra" in posts as a naively simple way of preventing
9
+ # spam.
10
+ #
11
+ # You'd first create a config/blacklist.txt file with a line like:
12
+ # \bviagrab\b
13
+ #
14
+ # And then you'd include the blacklisting feature into your Message class like:
15
+ #
16
+ # class Message < ActiveRecord::Base
17
+ # validates :title, :content, blacklist: true
18
+ # end
19
+ #
20
+ # Now including the word "viagra" in your record's values will fail:
21
+ #
22
+ # message = Message.new(title: "foo viagra bar")
23
+ # message.valid? # => false
24
+ #
25
+ # Available validator options:
26
+ # * patterns: Array of regular expressions that will be matched
27
+ # against the given attribute contents and any matches will cause the
28
+ # record to be marked invalid.
29
+ # * blacklist: Reads an array of blacklisted regular expressions from
30
+ # a filename.
31
+ # * message: Error message to use on invalid records.
32
+ #
33
+ # If no :patterns or :blacklist is given, patterns are read from:
34
+ # * config/blacklist.txt
35
+ # * config/blacklist-local.txt
36
+
37
+ class BlacklistValidator < ActiveModel::EachValidator
38
+ BLACKLIST_DEFAULT_MESSAGE = "contains blacklisted content"
39
+
40
+ def validate_each(record, attribute, value)
41
+ if value.present? && patterns.any? { |pattern| value.match(pattern) }
42
+ record.errors.add attribute, message
43
+ end
44
+ end
45
+
46
+ private
47
+
48
+ def message
49
+ options.fetch(:message, BLACKLIST_DEFAULT_MESSAGE)
50
+ end
51
+
52
+ def patterns
53
+ @patterns ||= options.fetch(:patterns) do
54
+ [
55
+ Calagator.blacklist_patterns,
56
+ get_blacklist_patterns_from(options.fetch(:blacklist, "blacklist.txt")),
57
+ ].flatten.compact
58
+ end
59
+ end
60
+
61
+ def get_blacklist_patterns_from(filename)
62
+ filename = Rails.root.join('config',filename) unless filename.match(/[\/\\]/)
63
+ return unless File.exists?(filename)
64
+
65
+ File.readlines(filename).map do |line|
66
+ Regexp.new(line.strip, Regexp::IGNORECASE)
67
+ end
68
+ end
69
+ end
@@ -0,0 +1,33 @@
1
+ # = DecodeHtmlEntitiesHack
2
+ #
3
+ # Loofah encodes HTML entities in every string that gets passed through it.
4
+ # This includes things like the friendly ampersand which we don't want to be HTML
5
+ # encoded in our database, so we need to decode the things that it changes.
6
+ #
7
+ # This should be included in models that use xss_foliate somewhere _after_ the xss_folidate call.
8
+ #
9
+ # This is, as the name of the module suggests, a giant hack. At some point, it should
10
+ # be removed after the issue is resolved in the underlying library:
11
+ # https://github.com/flavorjones/loofah/issues/20#issuecomment-1751538
12
+ #
13
+ # Warning: this effectively renders loofah's "escape" scrubbing mode useless by
14
+ # undoing everything it does. Don't use that mode.
15
+ #
16
+ module Calagator
17
+
18
+ module DecodeHtmlEntitiesHack
19
+ def self.included(base)
20
+ base.set_callback(:validate, :before, :decode_html_entities)
21
+ end
22
+
23
+ def decode_html_entities
24
+ self.attributes.each do |field, value|
25
+ decoded_content = HTMLEntities.new.decode(value)
26
+ if decoded_content.present? && !(decoded_content == value)
27
+ self.send("#{field}=", decoded_content)
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ end
@@ -0,0 +1,133 @@
1
+ # = DuplicateChecking
2
+ #
3
+ # This mixin provides a way for ActiveRecord classes to find and squash duplicates.
4
+ #
5
+ # Example:
6
+ #
7
+ # # Define your class
8
+ # class Thing < ActiveRecord::Base
9
+ # # Load the mixin into your class
10
+ # include DuplicateChecking
11
+ #
12
+ # # Declare attributes that should be ignored during duplicate checks
13
+ # duplicate_checking_ignores_attributes :random_value
14
+ #
15
+ # # Declare associations that should be ignored during duplicate squashing
16
+ # duplicate_squashing_ignores_associations :tags
17
+ # end
18
+ #
19
+ # # Set duplicates on objects
20
+ # foo1 = Thing.create!!:name => "foo")
21
+ # foo2 = Thing.create!(:name => "foo", :duplicate_of => foo1)
22
+ # bar = Thing.create!(:name => "bar")
23
+ #
24
+ # # Check whether record is set as duplicate
25
+ # foo1.duplicate? # => false
26
+ # foo2.duplicate? # => true
27
+ # bar.duplicate? # => false
28
+ #
29
+ # # Find duplicate of a record
30
+ # foo3.find_exact_duplicates # => [foo1, foo2]
31
+ # bar.find_exact_duplicates # => nil
32
+ require "calagator/duplicate_checking/duplicate_finder"
33
+ require "calagator/duplicate_checking/duplicate_squasher"
34
+
35
+ module Calagator
36
+
37
+ module DuplicateChecking
38
+ DUPLICATE_MARK_COLUMN = :duplicate_of_id
39
+ DEFAULT_SQUASH_METHOD = :mark
40
+ DUPLICATE_CHECKING_IGNORES_ATTRIBUTES =
41
+ Set.new((%w(created_at updated_at id) + [DUPLICATE_MARK_COLUMN]).map(&:to_sym))
42
+
43
+ def self.included(base)
44
+ base.extend ClassMethods
45
+ end
46
+
47
+ def duplicate?
48
+ duplicate_of.present?
49
+ end
50
+ alias_method :marked_as_duplicate?, :duplicate?
51
+ alias_method :slave?, :duplicate?
52
+
53
+ def master?
54
+ !slave?
55
+ end
56
+
57
+ # Return the ultimate master for a record, which may be the record itself.
58
+ def progenitor
59
+ parent = self
60
+ seen = Set.new
61
+
62
+ loop do
63
+ return parent if parent.master?
64
+ raise DuplicateCheckingError, "Loop detected in duplicates chain at #{parent.class}##{parent.id}" if seen.include?(parent)
65
+ seen << parent
66
+ parent = parent.duplicate_of
67
+ end
68
+ end
69
+
70
+ # Return either an Array of exact duplicates for this record, or nil if no exact duplicates were found.
71
+ #
72
+ # Note that this method requires that all associations are set before this method is called.
73
+ def find_exact_duplicates
74
+ matchable_attributes = attributes.reject { |key, value|
75
+ self.class.duplicate_checking_ignores_attributes.include?(key.to_sym)
76
+ }
77
+ duplicates = self.class.where(matchable_attributes).reject{|t| t.id == id}
78
+ duplicates.blank? ? nil : duplicates
79
+ end
80
+
81
+ module ClassMethods
82
+ def self.extended(klass)
83
+ klass.instance_eval do
84
+ cattr_accessor(:_duplicate_checking_ignores_attributes) { Set.new }
85
+ cattr_accessor(:_duplicate_squashing_ignores_associations) { Set.new }
86
+
87
+ belongs_to :duplicate_of, :class_name => name, :foreign_key => DUPLICATE_MARK_COLUMN
88
+ has_many :duplicates, :class_name => name, :foreign_key => DUPLICATE_MARK_COLUMN
89
+
90
+ scope :marked_duplicates, -> { where("#{table_name}.#{DUPLICATE_MARK_COLUMN} IS NOT NULL") }
91
+ scope :non_duplicates, -> { where("#{table_name}.#{DUPLICATE_MARK_COLUMN} IS NULL") }
92
+ end
93
+ end
94
+
95
+ # Return set of attributes that should be ignored for duplicate checking
96
+ def duplicate_checking_ignores_attributes(*args)
97
+ _duplicate_checking_ignores_attributes.merge(args.map(&:to_sym)) unless args.empty?
98
+ DUPLICATE_CHECKING_IGNORES_ATTRIBUTES + _duplicate_checking_ignores_attributes
99
+ end
100
+
101
+ # Return set of associations that will be ignored during duplicate squashing
102
+ def duplicate_squashing_ignores_associations(*args)
103
+ _duplicate_squashing_ignores_associations.merge(args.map(&:to_sym)) unless args.empty?
104
+ _duplicate_squashing_ignores_associations
105
+ end
106
+
107
+ # Return events with duplicate values for a given set of fields.
108
+ #
109
+ # Options:
110
+ # * :grouped => Return Hash of events grouped by commonality, rather than returning an Array. Defaults to false.
111
+ # * :where => String that specifies additional arguments to add to the WHERE clause.
112
+ # * :select => String that specified additional arguments to add to the SELECT clause.
113
+ # * :from => String that specifies additional arguments to add to the FROM clause
114
+ # * :joins => String that specifies additional argument to add to a JOINS clause.
115
+ def find_duplicates_by(fields, options={})
116
+ DuplicateFinder.new(self, fields, options).find
117
+ end
118
+
119
+ # Squash duplicates. Options accept ActiveRecord instances or IDs.
120
+ #
121
+ # Options:
122
+ # :duplicates => ActiveRecord instance(s) to mark as duplicates
123
+ # :master => ActiveRecord instance to use as master
124
+ def squash(master, duplicates)
125
+ DuplicateSquasher.new(master, duplicates, name.downcase).squash
126
+ end
127
+ end
128
+ end
129
+
130
+ class DuplicateCheckingError < Exception
131
+ end
132
+
133
+ end
@@ -0,0 +1,38 @@
1
+ module Calagator
2
+
3
+ module DuplicateChecking
4
+ module ControllerActions
5
+ # GET /#{model_class}/duplicates
6
+ def duplicates
7
+ @type = params[:type]
8
+ @grouped_venues = @grouped_events = model_class.find_duplicates_by_type(@type)
9
+ rescue ArgumentError => e
10
+ @grouped_venues = @grouped_events = {}
11
+ flash[:failure] = e.to_s
12
+ end
13
+
14
+ # POST /#{model_class}/squash_multiple_duplicates
15
+ def squash_many_duplicates
16
+ master = model_class.find_by_id(params[:master_id])
17
+ duplicate_ids = params.keys.grep(/^duplicate_id_\d+$/){|t| params[t].to_i}
18
+ duplicates = model_class.where(id: duplicate_ids)
19
+
20
+ squasher = model_class.squash(master, duplicates)
21
+ if squasher.success
22
+ flash[:success] = squasher.success
23
+ else
24
+ flash[:failure] = squasher.failure
25
+ end
26
+ redirect_to action: "duplicates", type: params[:type]
27
+ end
28
+
29
+ private
30
+
31
+ def model_class
32
+ # Derive model class from controller name
33
+ "Calagator::#{controller_name.singularize.titleize}".constantize
34
+ end
35
+ end
36
+ end
37
+
38
+ end
@@ -0,0 +1,83 @@
1
+ module Calagator
2
+
3
+ module DuplicateChecking
4
+ class DuplicateFinder < Struct.new(:model, :fields, :options)
5
+ def find
6
+ scope = model.select("a.*")
7
+ scope = scope.from("#{model.table_name} a, #{model.table_name} b")
8
+ scope = scope.where(options[:where]) if options[:where]
9
+ scope = scope.where("a.id <> b.id")
10
+ scope = scope.where("a.duplicate_of_id" => nil)
11
+ scope = scope.where(query)
12
+ scope.distinct
13
+
14
+ records = scope.all
15
+ records = group_by_fields(records) if grouped
16
+ records
17
+ end
18
+
19
+ def fields
20
+ super || :all
21
+ end
22
+
23
+ private
24
+
25
+ def query
26
+ case fields
27
+ when :all then query_from_all
28
+ when :any then query_from_any
29
+ else query_from_fields
30
+ end
31
+ end
32
+
33
+ def grouped
34
+ options[:grouped] || false
35
+ end
36
+
37
+ def group_by_fields records
38
+ # Group by the field values we're matching on; skip any values for which we only have one record
39
+ records = records.group_by do |record|
40
+ fields.map do |field|
41
+ record.read_attribute(field)
42
+ end
43
+ end
44
+ records.reject { |value, group| group.size <= 1 }
45
+ end
46
+
47
+ def query_from_all
48
+ attributes.map do |attr|
49
+ "((a.#{attr} = b.#{attr}) OR (a.#{attr} IS NULL AND b.#{attr} IS NULL))"
50
+ end.join(" AND ")
51
+ end
52
+
53
+ def query_from_any
54
+ attributes.map do |attr|
55
+ query = "(a.#{attr} = b.#{attr} AND ("
56
+ column = model.columns.find {|column| column.name.to_sym == attr}
57
+ case column.type
58
+ when :integer, :decimal
59
+ query << "a.#{attr} != 0 AND "
60
+ when :string, :text
61
+ query << "a.#{attr} != '' AND "
62
+ end
63
+ query << "a.#{attr} IS NOT NULL))"
64
+ end.join(" OR ")
65
+ end
66
+
67
+ def query_from_fields
68
+ raise ArgumentError, "Unknown fields: #{fields.inspect}" if (Array(fields) - attributes).any?
69
+ Array(fields).map do |attr|
70
+ "a.#{attr} = b.#{attr}"
71
+ end.join(" AND ")
72
+ end
73
+
74
+ def attributes
75
+ # TODO make find_duplicates_by(:all) pay attention to ignore fields
76
+ model.new.attribute_names.map(&:to_sym).reject do |attr|
77
+ [:id, :created_at, :updated_at, :duplicate_of_id, :version].include?(attr)
78
+ end
79
+ end
80
+ end
81
+ end
82
+
83
+ end
@@ -0,0 +1,74 @@
1
+ module Calagator
2
+
3
+ module DuplicateChecking
4
+ class DuplicateSquasher < Struct.new(:master, :duplicates, :model_name, :failure, :success)
5
+ def duplicates
6
+ Array(super)
7
+ end
8
+
9
+ def valid?
10
+ name = model_name.split("::").last
11
+ self.failure = "A master #{name} must be selected." if master.blank?
12
+ self.failure = "At least one duplicate #{name} must be selected." if duplicates.empty?
13
+ self.failure = "The master #{name} could not be squashed into itself." if duplicates.include?(master)
14
+ failure.blank?
15
+ end
16
+
17
+ def squash
18
+ if valid?
19
+ duplicates.each do |duplicate|
20
+ SingleSquasher.new(master, duplicate, model_name).squash
21
+ end
22
+ name = model_name.split("::").last
23
+ self.success = "Squashed duplicate #{name.pluralize} #{duplicates.map(&:title)} into master #{master.id}."
24
+ end
25
+ self
26
+ end
27
+
28
+ class SingleSquasher < Struct.new(:master, :duplicate, :model_name)
29
+ def squash
30
+ # Transfer any venues that use this now duplicate venue as a master
31
+ if duplicate.duplicates.any?
32
+ DuplicateSquasher.new(master, duplicate.duplicates, model_name).squash
33
+ end
34
+
35
+ squash_associations
36
+
37
+ duplicate.update_attribute(:duplicate_of, master)
38
+ duplicate
39
+ end
40
+
41
+ private
42
+
43
+ def squash_associations
44
+ # Transfer any has_many associations of this model to the master
45
+ master.class.reflect_on_all_associations(:has_many).each do |association|
46
+ next if association.name == :duplicates
47
+ next if master.class.duplicate_squashing_ignores_associations.include?(association.name)
48
+
49
+ # Handle tags - can't simply reassign, need to be unique, and they may have some of the same tags
50
+ if association.name == :tag_taggings
51
+ squash_tags
52
+ else
53
+ squash_association association
54
+ end
55
+ end
56
+ end
57
+
58
+ # custom behavior for tags, concatentate the two objects tag strings together
59
+ def squash_tags
60
+ master.tag_list = master.tag_list + duplicate.tag_list
61
+ master.save_tags
62
+ end
63
+
64
+ def squash_association(association)
65
+ foreign_objects = duplicate.send(association.name)
66
+ foreign_objects.each do |object|
67
+ object.update_attribute(association.foreign_key, master.id)
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
73
+
74
+ end