seapig-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (104) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +36 -0
  4. data/Rakefile +37 -0
  5. data/app/assets/javascripts/seapig/seapig.js.coffee +81 -0
  6. data/bin/seapig-notifier +66 -0
  7. data/config/routes.rb +2 -0
  8. data/lib/seapig-rails.rb +5 -0
  9. data/lib/seapig/acts_as_seapig_dependency.rb +34 -0
  10. data/lib/seapig/engine.rb +4 -0
  11. data/lib/seapig/version.rb +3 -0
  12. data/lib/tasks/seapig_tasks.rake +4 -0
  13. data/test/dummy/README.rdoc +28 -0
  14. data/test/dummy/Rakefile +6 -0
  15. data/test/dummy/app/assets/javascripts/application.js +15 -0
  16. data/test/dummy/app/assets/javascripts/json-patch.js +392 -0
  17. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  18. data/test/dummy/app/controllers/application_controller.rb +9 -0
  19. data/test/dummy/app/helpers/application_helper.rb +2 -0
  20. data/test/dummy/app/views/application/index.html.slim +10 -0
  21. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  22. data/test/dummy/bin/bundle +3 -0
  23. data/test/dummy/bin/rails +4 -0
  24. data/test/dummy/bin/rake +4 -0
  25. data/test/dummy/bin/setup +29 -0
  26. data/test/dummy/config.ru +4 -0
  27. data/test/dummy/config/application.rb +26 -0
  28. data/test/dummy/config/boot.rb +5 -0
  29. data/test/dummy/config/database.yml +85 -0
  30. data/test/dummy/config/environment.rb +5 -0
  31. data/test/dummy/config/environments/development.rb +41 -0
  32. data/test/dummy/config/environments/production.rb +79 -0
  33. data/test/dummy/config/environments/test.rb +42 -0
  34. data/test/dummy/config/initializers/assets.rb +11 -0
  35. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  36. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  37. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  38. data/test/dummy/config/initializers/inflections.rb +16 -0
  39. data/test/dummy/config/initializers/mime_types.rb +4 -0
  40. data/test/dummy/config/initializers/session_store.rb +3 -0
  41. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  42. data/test/dummy/config/locales/en.yml +23 -0
  43. data/test/dummy/config/routes.rb +56 -0
  44. data/test/dummy/config/secrets.yml +22 -0
  45. data/test/dummy/lib/seapigs/random.rb +14 -0
  46. data/test/dummy/log/development.log +1665 -0
  47. data/test/dummy/public/404.html +67 -0
  48. data/test/dummy/public/422.html +67 -0
  49. data/test/dummy/public/500.html +66 -0
  50. data/test/dummy/public/favicon.ico +0 -0
  51. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/-o-ZYYvENmbyGnbDFt6qAW7obI2QzsO9rM9EA7XlUHg.cache +0 -0
  52. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/1PVg-zJVu7--eXgp92_OEGf9YMjumXrwhnEbNQdV4h8.cache +1 -0
  53. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/2R9FCEwuVplMI7I5GoTBtw2Er_ap6H5s2otDK-m5XCk.cache +0 -0
  54. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/3eEw6ayC9zVKEVm7sgtqdiFFsi-0w4VyeWxR-hk72xg.cache +0 -0
  55. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/5Lly_CA8DZvPhQV2jDQx-Y6P_y3Ygra9t5jfSlGhHDA.cache +0 -0
  56. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/66l7LMqenzmlIoboeeCMFEZ6J_6zFdXmqNs-gu79P94.cache +1 -0
  57. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/6vXjezhMg67rV3BDCPOxLeOVbgCHn0aDwBXCA-N7_To.cache +2 -0
  58. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/7SWrjVR_xhTD9BCS4ifXzZIDn6dp3guSJjF8v5pYDRc.cache +1 -0
  59. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/98GLSy3KoIvHccw_zbxLg3FpmPEUKmGUr5oL7YZmvOU.cache +127 -0
  60. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/9Kel6H2jL5qJlsGHIcYRbxGaV-rMj_teA3CD1eaUVmk.cache +2 -0
  61. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/9T1WawqT-S8RLLgLI4J-o5mcGyy-wwU2mYMBwTuTl4o.cache +0 -0
  62. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/BXgyeZH3I-u535wy4F0jP3wmfV8m8WIWtXqHJKdBC2Q.cache +0 -0
  63. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/BsW5E247kS632hUZ5-NHcjkfprM3AwsB8dksndomUAY.cache +1 -0
  64. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/CW1ly2CbYhIQ3PoOCrGTUOxS8a03kI-4spp4REHx6mc.cache +0 -0
  65. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/Duqe6Cf2ThStcjIWL29RZnPilo1v6Vi7p86VOEBKqCs.cache +0 -0
  66. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/ETKdv5blZ86XWjAQcmxAQq2wK6RT9QvGqd7uV7DK1Iw.cache +2 -0
  67. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/HCc1doOYzZaBpAX7ozNMpo1ErZFli_aaKFQ73oRipH0.cache +2 -0
  68. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/KS-8mb2c29Zj9YWoyTAojF-sqg-aKMYQr6FkxGuoBWQ.cache +0 -0
  69. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/Lgka3bEq-I8Y6xz-LVIYNVeNIWkmW1NKDnOHOkYcxtE.cache +1 -0
  70. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/OI6uxGcnsKavdWTtwDAasU3wPx8QXhzBgV0X2n1KjMQ.cache +0 -0
  71. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/PgVoTat4a0VNolKPJS5RtDX7XcbAGbsqhquIWgWBBWE.cache +0 -0
  72. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/Tq5PhgpHymowY-e-a3airP7Q2OwxNuNC0792hdlAJRc.cache +1 -0
  73. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/VegRto_fFjOSBWQRgNRnnOiV3yByrpUI9b5IEhRvrDI.cache +1 -0
  74. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/VqL7bJxBiq13xYePLO71Spa6RfD5dFCeRRLGYb5jEqw.cache +0 -0
  75. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/X5QxTUYFP4VOH_7p2Qh34msJtFA6fOnJ0mM7Zip7dRU.cache +1 -0
  76. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/_2P0GKBCbJ61e8RdTBx4rJr8TsUYKFJYd7N0ZhA7o6k.cache +0 -0
  77. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/_mxi_K1gl7n32DYq8bFfbWrIRQrU3QQiuukc63_UBb4.cache +1 -0
  78. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/a1-5JrPXbtVr0ytoeAR0GzDsG_rlYUHm_sKEC1VHrrM.cache +1 -0
  79. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/aShHx921rUO4rZzH4135LWkt4TSWfqhpQMN8JsV2OqE.cache +0 -0
  80. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/b9Ac87wpHRTGZL-mBacNdb343KQ1426WdjSu03OVlz8.cache +0 -0
  81. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/beCZt_nFEyk5iX6v4bgC3qrdFMgSM0IgrwxaBTFEFA0.cache +1 -0
  82. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/bkclf1HVUxdntd8cKLvWGX5Pq-E12kwCwakqLo8RoN4.cache +1 -0
  83. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/csrXH9BNqM8JCRjroMFCt2hluEvIvM0neY_ZQySl58A.cache +1 -0
  84. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/fsxRcZUqoELh6-D0RWowwDKHYmUkGzh5HMFuwMMWk1g.cache +1 -0
  85. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/h2XCtwcdt21JcAtjhfoOuIjifaMeMaYwPcFGnmNc2ng.cache +1 -0
  86. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/hZi1k6tpxxCGYxRe7zY74ItcOI8gZrREOpGuA8JSpGg.cache +3 -0
  87. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/j3q-1jQzykD1sMeX9INl7jygCKtC0XJ8JkWkFQkL6qg.cache +2 -0
  88. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/j4VcbkSejSElTiJk3ehlEcJE9HRTG7T14-wVhUDocaA.cache +1 -0
  89. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/jU_8gMY9eZiN785s1lakKFjnK5ox1EA-Na1fKxuYcjs.cache +1 -0
  90. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/pEhaat2KBd5SrT7szC_8R1_6hK17FTpvoRFkmCRSD3M.cache +2 -0
  91. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/q3UrpsuPTq0hMrkTWoYoYEZL4u05PdVc4L5NXKuFDgQ.cache +0 -0
  92. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/sX4-Kn9knBu7plHg7gUT-ryVktGvmZfacNm1cUysjWs.cache +1 -0
  93. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/umxcUEQeoCOqF0ZwXlNqJEOyT_vhMK8iGO4--jduIDU.cache +1 -0
  94. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/urzLHu3R3qKeFqygSDL0NVDcyw8BFEA6i9jFeweVZQ4.cache +1 -0
  95. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/vCljKmhe1Sy9j9TDIB9DrQRa8dYlkPg8nyvsZfqfCmw.cache +0 -0
  96. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/vn6FA8BrDkisPSH4VFlc7tgmkzpx1DsOtDp5C3cdaRU.cache +1 -0
  97. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/wtuO4JuGsyO8emVQL7t1bm1fvl6YzGfBHc3tJJ49uvs.cache +2 -0
  98. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/z9KVC85iQuroh2hLYTGZhgZHDlh7183qTlfed3Uhtnw.cache +0 -0
  99. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/znvRewDLwMSRAUjlQozzMBQ_IGWvw9fVVOh9aeaXxTA.cache +0 -0
  100. data/test/dummy/tmp/cache/assets/development/sprockets/v3.0/zxZcbwt4p6Q1Z7yZfuWYKTXjetmNZI8MgkKazd4PjoA.cache +0 -0
  101. data/test/integration/navigation_test.rb +8 -0
  102. data/test/seapig_test.rb +7 -0
  103. data/test/test_helper.rb +20 -0
  104. metadata +294 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 25b28f9d7975a6bfd32062f20bc310c01d79735e
4
+ data.tar.gz: 186a43d3f1e36b1b8b1244d6524b344388d34e74
5
+ SHA512:
6
+ metadata.gz: 31bd90e20f73825d39bcafd121eaf070a0f35229bfd2d574ca63ae96ed1b00d7b5e33343622acae53dd5c28c9ed02cad3e7d6895ac720cdc660f5dab254d9d7d
7
+ data.tar.gz: ce55ecb9cfe3acfcb52b8d24992b2ed4b037f15700d6da06590e602b1352754a12ffaf94f9d249b6747c60b14b93de8d8734cffeada736ad30e5abf58363ac57
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2015 yunta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,36 @@
1
+ = Seapig
2
+
3
+ Nothing here yet.
4
+
5
+ To cover:
6
+
7
+ * what are seapigs and why they exist? (why not just having a db table?)
8
+ * link to https://www.youtube.com/watch?v=_y4DbZivHCY
9
+ * properties of seapig system
10
+ * data is cached in memory
11
+ * only diffs are sent
12
+ * data is generated in separate processes
13
+ * data gets regenerated on dependency change (immediately on db change)
14
+ * describe current limits
15
+ * one level deps only
16
+ * single object can have at most one generation process running at all times
17
+ * cache is dropped when last client unlinks
18
+ * no client-side timeout detection
19
+ * postgres only (but is that really a problem? ;))
20
+ * graph of server/client side seapig object states
21
+ * disclaimer on non-efficient code. it's all a draft, a test of idea.
22
+ * rails not needed
23
+ * stuff will change
24
+ * seapig = firebase for apps that need real db
25
+ * works nicely with mithril
26
+ * how to use this shit
27
+ * dummy app is a minimal demo
28
+ * bundle exec rails s
29
+ * bundle exec ruby ../../bin/seapig-server.rb
30
+ * bundle exec ruby ../../bin/seapig-worker.rb ws://127.0.0.1:3001/seapig
31
+ * bundle exec ruby ../../bin/seapig-notifier.rb ws://127.0.0.1:3001/seapig
32
+ * application.js needs: require seapig/seapig
33
+ * application.js needs: require json-patch
34
+ * ActiveRecord models that are used for triggering regeneration of data need:
35
+ * acts_as_seapig_dependency
36
+ * and seapig_dependency_changed after commits
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Seapig'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+ load 'rails/tasks/statistics.rake'
22
+
23
+
24
+
25
+ Bundler::GemHelper.install_tasks
26
+
27
+ require 'rake/testtask'
28
+
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.libs << 'lib'
31
+ t.libs << 'test'
32
+ t.pattern = 'test/**/*_test.rb'
33
+ t.verbose = false
34
+ end
35
+
36
+
37
+ task default: :test
@@ -0,0 +1,81 @@
1
+ class @SeapigServer
2
+
3
+
4
+ constructor: (url, options = {})->
5
+ @url = url
6
+ @options = options
7
+ @slave_objects = {}
8
+ @master_objects = {}
9
+ @connect()
10
+
11
+
12
+ connect: () ->
13
+ @connected = false
14
+
15
+ @socket = new WebSocket(@url,'SeaPig-0.0')
16
+
17
+ @socket.onerror = (error) =>
18
+ console.log('Seapig socket error', error)
19
+ @socket.close()
20
+
21
+ @socket.onclose = () =>
22
+ console.log('Seapig connection closed')
23
+ for object_id, object of @slave_objects
24
+ object.valid = false
25
+ setTimeout((=>@connect()), 2000)
26
+
27
+ @socket.onopen = () =>
28
+ console.log('Seapig connection opened')
29
+ @connected = true
30
+ @socket.send(JSON.stringify(action: 'client-options-set', options: @options))
31
+ for object_id, object of @slave_objects
32
+ @socket.send(JSON.stringify(action: 'object-consumer-register', id: object_id, latest_known_version: object.version))
33
+
34
+ @socket.onmessage = (event) =>
35
+ #console.log('Seapig message received', event)
36
+ data = JSON.parse(event.data)
37
+ switch data.action
38
+ when 'object-update'
39
+ @slave_objects[data.id].patch(data) if @slave_objects[data.id]
40
+ else
41
+ console.log('Seapig received a stupid message', data)
42
+
43
+
44
+ slave: (object_id) ->
45
+ @socket.send(JSON.stringify(action: 'object-consumer-register', id: object_id, latest_known_version: null)) if @connected
46
+ @slave_objects[object_id] = new SeapigObject(object_id)
47
+
48
+
49
+ unlink: (object_id) ->
50
+ delete @slave_objects[object_id]
51
+ @socket.send(JSON.stringify(action: 'unlink', id: object_id)) if @connected
52
+
53
+
54
+
55
+ class SeapigObject
56
+
57
+
58
+ constructor: (id) ->
59
+ @id = id
60
+ @valid = false
61
+ @version = null
62
+ @object = {}
63
+ @shadow = {}
64
+ @onchange = null
65
+
66
+
67
+ patch: (data) ->
68
+ if not data.old_version?
69
+ delete @object[key] for key, value of @object
70
+ else if not _.isEqual(@version, data.old_version)
71
+ console.log("Seapig lost some updates, this shouldn't ever happen", @version, data.old_version)
72
+ jsonpatch.apply(@object, data.patch)
73
+ @version = data.new_version
74
+ @valid = true
75
+ @onchange() if @onchange?
76
+
77
+ changed: () ->
78
+ @version += 1
79
+ patch = jsonpatch.compare(@shadow, @object)
80
+ console.log(patch)
81
+ @shadow = JSON.parse(JSON.stringify(@object))
@@ -0,0 +1,66 @@
1
+ #!/bin/env ruby
2
+
3
+ require './config/environment.rb'
4
+
5
+ require 'websocket-eventmachine-client'
6
+ require 'json'
7
+
8
+
9
+ Rails.application.eager_load!
10
+ notifiers = Hash[*ActiveRecord::Base.descendants.select { |cls| cls.respond_to?('seapig_dependency_version') }.map { |notifier| [notifier.name,notifier] }.flatten ]
11
+
12
+ $last_versions = {}
13
+ $payloads = Queue.new
14
+
15
+ EM.run {
16
+
17
+ socket = WebSocket::EventMachine::Client.connect(uri: ARGV[0])
18
+
19
+
20
+ on_database_change = Proc.new {
21
+ payloads = Set.new
22
+ payloads << $payloads.pop while not $payloads.empty?
23
+ payloads.each { |notifier_name|
24
+ version = notifiers[notifier_name].seapig_dependency_version #F handle wrong names
25
+ p version
26
+ if $last_versions[notifier_name] != version
27
+ #socket.send(JSON.dump(action: 'object-update', id: notifier_name, version: version))
28
+ socket.send(JSON.dump(action: 'object-patch', id: notifier_name, new_version: version, old_version: 0))
29
+ $last_versions[notifier_name] = version
30
+ end
31
+ }
32
+ }
33
+
34
+
35
+ socket.onopen {
36
+ socket.send(JSON.dump(action: 'client-options-set', options: {name: 'notifier'}))
37
+ Thread.new {
38
+ ActiveRecord::Base.connection_pool.with_connection { |connection|
39
+ connection = connection.instance_variable_get(:@connection)
40
+ connection.exec("LISTEN seapig_dependency_changed")
41
+ loop {
42
+ connection.wait_for_notify { |channel, pid, payloads|
43
+ puts "Got notification: channel="+channel+", pid="+pid.inspect+", payload="+payloads.inspect
44
+ payloads.split(",").each { |payload| $payloads << payload }
45
+ EM.schedule(on_database_change)
46
+ }
47
+ }
48
+ }
49
+ }
50
+ EM.schedule on_database_change
51
+ }
52
+
53
+
54
+ socket.onclose { |code, reason|
55
+ EM.stop
56
+ }
57
+
58
+
59
+ EM.add_periodic_timer(60) {
60
+ # socket.send(JSON.dump(action: 'object-update', id: 'Minute', version: Time.new.to_i/60))
61
+ socket.send(JSON.dump(action: 'object-patch', id: 'Minute', new_version: Time.new.to_i/60, old_version: 0))
62
+ }
63
+
64
+ }
65
+
66
+
data/config/routes.rb ADDED
@@ -0,0 +1,2 @@
1
+ Rails.application.routes.draw do
2
+ end
@@ -0,0 +1,5 @@
1
+ require "seapig/engine"
2
+ require "seapig/acts_as_seapig_dependency.rb"
3
+
4
+ module Seapig
5
+ end
@@ -0,0 +1,34 @@
1
+ module SeapigDependency
2
+
3
+ module ActsAsSeapigDependency
4
+
5
+ extend ActiveSupport::Concern
6
+
7
+
8
+ module ClassMethods
9
+
10
+ def acts_as_seapig_dependency(options = {})
11
+
12
+ self.instance_eval do
13
+
14
+ def seapig_dependency_version
15
+ self.find_by_sql('SELECT GREATEST(MAX(created_at), MAX(updated_at)) AS version FROM '+table_name).first.version.to_f
16
+ end
17
+
18
+ def seapig_dependency_changed(*tables)
19
+ tables << self.name
20
+ connection.instance_variable_get(:@connection).exec("NOTIFY seapig_dependency_changed,'"+tables.map { |table| table.kind_of?(Class) and table.name or table }.uniq.join(',')+"'")
21
+ end
22
+
23
+ end
24
+
25
+ end
26
+
27
+ end
28
+
29
+ end
30
+
31
+ end
32
+
33
+
34
+ ActiveRecord::Base.send :include, SeapigDependency::ActsAsSeapigDependency
@@ -0,0 +1,4 @@
1
+ module Seapig
2
+ class Engine < ::Rails::Engine
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module Seapig
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :seapig do
3
+ # # Task goes here
4
+ # end
@@ -0,0 +1,28 @@
1
+ == README
2
+
3
+ This README would normally document whatever steps are necessary to get the
4
+ application up and running.
5
+
6
+ Things you may want to cover:
7
+
8
+ * Ruby version
9
+
10
+ * System dependencies
11
+
12
+ * Configuration
13
+
14
+ * Database creation
15
+
16
+ * Database initialization
17
+
18
+ * How to run the test suite
19
+
20
+ * Services (job queues, cache servers, search engines, etc.)
21
+
22
+ * Deployment instructions
23
+
24
+ * ...
25
+
26
+
27
+ Please feel free to use a different markup language if you do not plan to run
28
+ <tt>rake doc:app</tt>.
@@ -0,0 +1,6 @@
1
+ # Add your own tasks in files placed in lib/tasks ending in .rake,
2
+ # for example lib/tasks/capistrano.rake, and they will automatically be available to Rake.
3
+
4
+ require File.expand_path('../config/application', __FILE__)
5
+
6
+ Rails.application.load_tasks
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require_tree .
14
+ //= require seapig/seapig
15
+ //= require json-patch
@@ -0,0 +1,392 @@
1
+ /*!
2
+ * https://github.com/Starcounter-Jack/JSON-Patch
3
+ * json-patch-duplex.js version: 0.5.4
4
+ * (c) 2013 Joachim Wester
5
+ * MIT license
6
+ */
7
+ var __extends = this.__extends || function (d, b) {
8
+ for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
9
+ function __() { this.constructor = d; }
10
+ __.prototype = b.prototype;
11
+ d.prototype = new __();
12
+ };
13
+ var OriginalError = Error;
14
+
15
+ var jsonpatch;
16
+ (function (jsonpatch) {
17
+ /* Do nothing if module is already defined.
18
+ Doesn't look nice, as we cannot simply put
19
+ `!jsonpatch &&` before this immediate function call
20
+ in TypeScript.
21
+ */
22
+ if (jsonpatch.apply) {
23
+ return;
24
+ }
25
+
26
+ var _objectKeys = (function () {
27
+ if (Object.keys)
28
+ return Object.keys;
29
+
30
+ return function (o) {
31
+ var keys = [];
32
+ for (var i in o) {
33
+ if (o.hasOwnProperty(i)) {
34
+ keys.push(i);
35
+ }
36
+ }
37
+ return keys;
38
+ };
39
+ })();
40
+
41
+ function _equals(a, b) {
42
+ switch (typeof a) {
43
+ case 'undefined':
44
+ case 'boolean':
45
+ case 'string':
46
+ case 'number':
47
+ return a === b;
48
+ case 'object':
49
+ if (a === null)
50
+ return b === null;
51
+ if (_isArray(a)) {
52
+ if (!_isArray(b) || a.length !== b.length)
53
+ return false;
54
+
55
+ for (var i = 0, l = a.length; i < l; i++)
56
+ if (!_equals(a[i], b[i]))
57
+ return false;
58
+
59
+ return true;
60
+ }
61
+
62
+ var bKeys = _objectKeys(b);
63
+ var bLength = bKeys.length;
64
+ if (_objectKeys(a).length !== bLength)
65
+ return false;
66
+
67
+ for (var i = 0; i < bLength; i++)
68
+ if (!_equals(a[i], b[i]))
69
+ return false;
70
+
71
+ return true;
72
+
73
+ default:
74
+ return false;
75
+ }
76
+ }
77
+
78
+ /* We use a Javascript hash to store each
79
+ function. Each hash entry (property) uses
80
+ the operation identifiers specified in rfc6902.
81
+ In this way, we can map each patch operation
82
+ to its dedicated function in efficient way.
83
+ */
84
+ /* The operations applicable to an object */
85
+ var objOps = {
86
+ add: function (obj, key) {
87
+ obj[key] = this.value;
88
+ return true;
89
+ },
90
+ remove: function (obj, key) {
91
+ delete obj[key];
92
+ return true;
93
+ },
94
+ replace: function (obj, key) {
95
+ obj[key] = this.value;
96
+ return true;
97
+ },
98
+ move: function (obj, key, tree) {
99
+ var temp = { op: "_get", path: this.from };
100
+ apply(tree, [temp]);
101
+ apply(tree, [
102
+ { op: "remove", path: this.from }
103
+ ]);
104
+ apply(tree, [
105
+ { op: "add", path: this.path, value: temp.value }
106
+ ]);
107
+ return true;
108
+ },
109
+ copy: function (obj, key, tree) {
110
+ var temp = { op: "_get", path: this.from };
111
+ apply(tree, [temp]);
112
+ apply(tree, [
113
+ { op: "add", path: this.path, value: temp.value }
114
+ ]);
115
+ return true;
116
+ },
117
+ test: function (obj, key) {
118
+ return _equals(obj[key], this.value);
119
+ },
120
+ _get: function (obj, key) {
121
+ this.value = obj[key];
122
+ }
123
+ };
124
+
125
+ /* The operations applicable to an array. Many are the same as for the object */
126
+ var arrOps = {
127
+ add: function (arr, i) {
128
+ arr.splice(i, 0, this.value);
129
+ return true;
130
+ },
131
+ remove: function (arr, i) {
132
+ arr.splice(i, 1);
133
+ return true;
134
+ },
135
+ replace: function (arr, i) {
136
+ arr[i] = this.value;
137
+ return true;
138
+ },
139
+ move: objOps.move,
140
+ copy: objOps.copy,
141
+ test: objOps.test,
142
+ _get: objOps._get
143
+ };
144
+
145
+ /* The operations applicable to object root. Many are the same as for the object */
146
+ var rootOps = {
147
+ add: function (obj) {
148
+ rootOps.remove.call(this, obj);
149
+ for (var key in this.value) {
150
+ if (this.value.hasOwnProperty(key)) {
151
+ obj[key] = this.value[key];
152
+ }
153
+ }
154
+ return true;
155
+ },
156
+ remove: function (obj) {
157
+ for (var key in obj) {
158
+ if (obj.hasOwnProperty(key)) {
159
+ objOps.remove.call(this, obj, key);
160
+ }
161
+ }
162
+ return true;
163
+ },
164
+ replace: function (obj) {
165
+ apply(obj, [
166
+ { op: "remove", path: this.path }
167
+ ]);
168
+ apply(obj, [
169
+ { op: "add", path: this.path, value: this.value }
170
+ ]);
171
+ return true;
172
+ },
173
+ move: objOps.move,
174
+ copy: objOps.copy,
175
+ test: function (obj) {
176
+ return (JSON.stringify(obj) === JSON.stringify(this.value));
177
+ },
178
+ _get: function (obj) {
179
+ this.value = obj;
180
+ }
181
+ };
182
+
183
+ var _isArray;
184
+ if (Array.isArray) {
185
+ _isArray = Array.isArray;
186
+ } else {
187
+ _isArray = function (obj) {
188
+ return obj.push && typeof obj.length === 'number';
189
+ };
190
+ }
191
+
192
+ //3x faster than cached /^\d+$/.test(str)
193
+ function isInteger(str) {
194
+ var i = 0;
195
+ var len = str.length;
196
+ var charCode;
197
+ while (i < len) {
198
+ charCode = str.charCodeAt(i);
199
+ if (charCode >= 48 && charCode <= 57) {
200
+ i++;
201
+ continue;
202
+ }
203
+ return false;
204
+ }
205
+ return true;
206
+ }
207
+
208
+ /// Apply a json-patch operation on an object tree
209
+ function apply(tree, patches, validate) {
210
+ var result = false, p = 0, plen = patches.length, patch, key;
211
+ while (p < plen) {
212
+ patch = patches[p];
213
+ p++;
214
+
215
+ // Find the object
216
+ var path = patch.path || "";
217
+ var keys = path.split('/');
218
+ var obj = tree;
219
+ var t = 1;
220
+ var len = keys.length;
221
+ var existingPathFragment = undefined;
222
+
223
+ while (true) {
224
+ key = keys[t];
225
+
226
+ if (validate) {
227
+ if (existingPathFragment === undefined) {
228
+ if (obj[key] === undefined) {
229
+ existingPathFragment = keys.slice(0, t).join('/');
230
+ } else if (t == len - 1) {
231
+ existingPathFragment = patch.path;
232
+ }
233
+ if (existingPathFragment !== undefined) {
234
+ this.validator(patch, p - 1, tree, existingPathFragment);
235
+ }
236
+ }
237
+ }
238
+
239
+ t++;
240
+ if (key === undefined) {
241
+ if (t >= len) {
242
+ result = rootOps[patch.op].call(patch, obj, key, tree); // Apply patch
243
+ break;
244
+ }
245
+ }
246
+ if (_isArray(obj)) {
247
+ if (key === '-') {
248
+ key = obj.length;
249
+ } else {
250
+ if (validate && !isInteger(key)) {
251
+ throw new JsonPatchError("Expected an unsigned base-10 integer value, making the new referenced value the array element with the zero-based index", "OPERATION_PATH_ILLEGAL_ARRAY_INDEX", p - 1, patch.path, patch);
252
+ }
253
+ key = parseInt(key, 10);
254
+ }
255
+ if (t >= len) {
256
+ if (validate && patch.op === "add" && key > obj.length) {
257
+ throw new JsonPatchError("The specified index MUST NOT be greater than the number of elements in the array", "OPERATION_VALUE_OUT_OF_BOUNDS", p - 1, patch.path, patch);
258
+ }
259
+ result = arrOps[patch.op].call(patch, obj, key, tree); // Apply patch
260
+ break;
261
+ }
262
+ } else {
263
+ if (key && key.indexOf('~') != -1)
264
+ key = key.replace(/~1/g, '/').replace(/~0/g, '~'); // escape chars
265
+ if (t >= len) {
266
+ result = objOps[patch.op].call(patch, obj, key, tree); // Apply patch
267
+ break;
268
+ }
269
+ }
270
+ obj = obj[key];
271
+ }
272
+ }
273
+ return result;
274
+ }
275
+ jsonpatch.apply = apply;
276
+
277
+ var JsonPatchError = (function (_super) {
278
+ __extends(JsonPatchError, _super);
279
+ function JsonPatchError(message, name, index, operation, tree) {
280
+ _super.call(this, message);
281
+ this.message = message;
282
+ this.name = name;
283
+ this.index = index;
284
+ this.operation = operation;
285
+ this.tree = tree;
286
+ }
287
+ return JsonPatchError;
288
+ })(OriginalError);
289
+ jsonpatch.JsonPatchError = JsonPatchError;
290
+
291
+ jsonpatch.Error = JsonPatchError;
292
+
293
+ /**
294
+ * Recursively checks whether an object has any undefined values inside.
295
+ */
296
+ function hasUndefined(obj) {
297
+ if (obj === undefined) {
298
+ return true;
299
+ }
300
+
301
+ if (typeof obj == "array" || typeof obj == "object") {
302
+ for (var i in obj) {
303
+ if (hasUndefined(obj[i])) {
304
+ return true;
305
+ }
306
+ }
307
+ }
308
+
309
+ return false;
310
+ }
311
+
312
+ /**
313
+ * Validates a single operation. Called from `jsonpatch.validate`. Throws `JsonPatchError` in case of an error.
314
+ * @param {object} operation - operation object (patch)
315
+ * @param {number} index - index of operation in the sequence
316
+ * @param {object} [tree] - object where the operation is supposed to be applied
317
+ * @param {string} [existingPathFragment] - comes along with `tree`
318
+ */
319
+ function validator(operation, index, tree, existingPathFragment) {
320
+ if (typeof operation !== 'object' || operation === null || _isArray(operation)) {
321
+ throw new JsonPatchError('Operation is not an object', 'OPERATION_NOT_AN_OBJECT', index, operation, tree);
322
+ } else if (!objOps[operation.op]) {
323
+ throw new JsonPatchError('Operation `op` property is not one of operations defined in RFC-6902', 'OPERATION_OP_INVALID', index, operation, tree);
324
+ } else if (typeof operation.path !== 'string') {
325
+ throw new JsonPatchError('Operation `path` property is not a string', 'OPERATION_PATH_INVALID', index, operation, tree);
326
+ } else if ((operation.op === 'move' || operation.op === 'copy') && typeof operation.from !== 'string') {
327
+ throw new JsonPatchError('Operation `from` property is not present (applicable in `move` and `copy` operations)', 'OPERATION_FROM_REQUIRED', index, operation, tree);
328
+ } else if ((operation.op === 'add' || operation.op === 'replace' || operation.op === 'test') && operation.value === undefined) {
329
+ throw new JsonPatchError('Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)', 'OPERATION_VALUE_REQUIRED', index, operation, tree);
330
+ } else if ((operation.op === 'add' || operation.op === 'replace' || operation.op === 'test') && hasUndefined(operation.value)) {
331
+ throw new JsonPatchError('Operation `value` property is not present (applicable in `add`, `replace` and `test` operations)', 'OPERATION_VALUE_CANNOT_CONTAIN_UNDEFINED', index, operation, tree);
332
+ } else if (tree) {
333
+ if (operation.op == "add") {
334
+ var pathLen = operation.path.split("/").length;
335
+ var existingPathLen = existingPathFragment.split("/").length;
336
+ if (pathLen !== existingPathLen + 1 && pathLen !== existingPathLen) {
337
+ throw new JsonPatchError('Cannot perform an `add` operation at the desired path', 'OPERATION_PATH_CANNOT_ADD', index, operation, tree);
338
+ }
339
+ } else if (operation.op === 'replace' || operation.op === 'remove' || operation.op === '_get') {
340
+ if (operation.path !== existingPathFragment) {
341
+ throw new JsonPatchError('Cannot perform the operation at a path that does not exist', 'OPERATION_PATH_UNRESOLVABLE', index, operation, tree);
342
+ }
343
+ } else if (operation.op === 'move' || operation.op === 'copy') {
344
+ var existingValue = { op: "_get", path: operation.from, value: undefined };
345
+ var error = jsonpatch.validate([existingValue], tree);
346
+ if (error && error.name === 'OPERATION_PATH_UNRESOLVABLE') {
347
+ throw new JsonPatchError('Cannot perform the operation from a path that does not exist', 'OPERATION_FROM_UNRESOLVABLE', index, operation, tree);
348
+ }
349
+ }
350
+ }
351
+ }
352
+ jsonpatch.validator = validator;
353
+
354
+ /**
355
+ * Validates a sequence of operations. If `tree` parameter is provided, the sequence is additionally validated against the object tree.
356
+ * If error is encountered, returns a JsonPatchError object
357
+ * @param sequence
358
+ * @param tree
359
+ * @returns {JsonPatchError|undefined}
360
+ */
361
+ function validate(sequence, tree) {
362
+ try {
363
+ if (!_isArray(sequence)) {
364
+ throw new JsonPatchError('Patch sequence must be an array', 'SEQUENCE_NOT_AN_ARRAY');
365
+ }
366
+
367
+ if (tree) {
368
+ tree = JSON.parse(JSON.stringify(tree)); //clone tree so that we can safely try applying operations
369
+ apply.call(this, tree, sequence, true);
370
+ } else {
371
+ for (var i = 0; i < sequence.length; i++) {
372
+ this.validator(sequence[i], i);
373
+ }
374
+ }
375
+ } catch (e) {
376
+ if (e instanceof JsonPatchError) {
377
+ return e;
378
+ } else {
379
+ throw e;
380
+ }
381
+ }
382
+ }
383
+ jsonpatch.validate = validate;
384
+ })(jsonpatch || (jsonpatch = {}));
385
+
386
+ if (typeof exports !== "undefined") {
387
+ exports.apply = jsonpatch.apply;
388
+ exports.validate = jsonpatch.validate;
389
+ exports.validator = jsonpatch.validator;
390
+ exports.JsonPatchError = jsonpatch.JsonPatchError;
391
+ exports.Error = jsonpatch.Error;
392
+ }