logux_rails 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +20 -0
  3. data/.pryrc +8 -0
  4. data/.rspec +3 -0
  5. data/.rubocop.yml +33 -0
  6. data/.rubocop_todo.yml +17 -0
  7. data/.travis.yml +11 -0
  8. data/Appraisals +13 -0
  9. data/Gemfile +8 -0
  10. data/LICENSE.txt +21 -0
  11. data/README.md +68 -0
  12. data/Rakefile +10 -0
  13. data/app/controllers/logux_controller.rb +41 -0
  14. data/app/helpers/logux_helper.rb +4 -0
  15. data/app/logux/actions.rb +3 -0
  16. data/app/logux/policies.rb +3 -0
  17. data/bin/console +15 -0
  18. data/bin/setup +8 -0
  19. data/config/routes.rb +5 -0
  20. data/docker-compose.yml +29 -0
  21. data/lib/generators/logux/model/USAGE +11 -0
  22. data/lib/generators/logux/model/model_generator.rb +28 -0
  23. data/lib/generators/logux/model/templates/migration.rb.erb +14 -0
  24. data/lib/logux.rb +107 -0
  25. data/lib/logux/action_caller.rb +42 -0
  26. data/lib/logux/action_controller.rb +6 -0
  27. data/lib/logux/actions.rb +29 -0
  28. data/lib/logux/add.rb +37 -0
  29. data/lib/logux/auth.rb +6 -0
  30. data/lib/logux/base_controller.rb +37 -0
  31. data/lib/logux/channel_controller.rb +24 -0
  32. data/lib/logux/class_finder.rb +61 -0
  33. data/lib/logux/client.rb +21 -0
  34. data/lib/logux/engine.rb +6 -0
  35. data/lib/logux/error_renderer.rb +40 -0
  36. data/lib/logux/meta.rb +36 -0
  37. data/lib/logux/model.rb +39 -0
  38. data/lib/logux/model/dsl.rb +15 -0
  39. data/lib/logux/model/proxy.rb +24 -0
  40. data/lib/logux/model/updater.rb +39 -0
  41. data/lib/logux/model/updates_deprecator.rb +54 -0
  42. data/lib/logux/node.rb +37 -0
  43. data/lib/logux/policy.rb +14 -0
  44. data/lib/logux/policy_caller.rb +34 -0
  45. data/lib/logux/process.rb +9 -0
  46. data/lib/logux/process/action.rb +60 -0
  47. data/lib/logux/process/auth.rb +27 -0
  48. data/lib/logux/process/batch.rb +59 -0
  49. data/lib/logux/response.rb +18 -0
  50. data/lib/logux/stream.rb +25 -0
  51. data/lib/logux/test.rb +35 -0
  52. data/lib/logux/test/helpers.rb +75 -0
  53. data/lib/logux/test/matchers.rb +10 -0
  54. data/lib/logux/test/matchers/base.rb +25 -0
  55. data/lib/logux/test/matchers/response_chunks.rb +48 -0
  56. data/lib/logux/test/matchers/send_to_logux.rb +51 -0
  57. data/lib/logux/test/store.rb +21 -0
  58. data/lib/logux/version.rb +5 -0
  59. data/lib/logux_rails.rb +3 -0
  60. data/lib/tasks/logux_tasks.rake +46 -0
  61. data/logux_rails.gemspec +46 -0
  62. metadata +398 -0
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logux
4
+ class ActionCaller
5
+ attr_reader :action, :meta
6
+
7
+ delegate :logger, to: :Logux
8
+
9
+ def initialize(action:, meta:)
10
+ @action = action
11
+ @meta = meta
12
+ end
13
+
14
+ def call!
15
+ Logux::Model::UpdatesDeprecator.watch(level: :error) do
16
+ logger.debug(
17
+ "Searching action for Logux action: #{action}, meta: #{meta}"
18
+ )
19
+ format(action_controller.public_send(action.action_type))
20
+ end
21
+ rescue Logux::UnknownActionError, Logux::UnknownChannelError => e
22
+ logger.warn(e)
23
+ format(nil)
24
+ end
25
+
26
+ private
27
+
28
+ def format(response)
29
+ return response if response.is_a?(Logux::Response)
30
+
31
+ Logux::Response.new(:processed, action: action, meta: meta)
32
+ end
33
+
34
+ def class_finder
35
+ @class_finder ||= Logux::ClassFinder.new(action: action, meta: meta)
36
+ end
37
+
38
+ def action_controller
39
+ class_finder.find_action_class.new(action: action, meta: meta)
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logux
4
+ class ActionController < Logux::BaseController
5
+ end
6
+ end
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logux
4
+ class Actions < ::ActionController::Parameters
5
+ def action_name
6
+ type&.split('/')&.dig(0)
7
+ end
8
+
9
+ def action_type
10
+ type&.split('/')&.last
11
+ end
12
+
13
+ def channel_name
14
+ channel&.split('/')&.dig(0)
15
+ end
16
+
17
+ def channel_id
18
+ channel&.split('/')&.last
19
+ end
20
+
21
+ def type
22
+ require(:type)
23
+ end
24
+
25
+ def channel
26
+ require(:channel)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logux
4
+ class Add
5
+ attr_reader :client, :version, :password
6
+
7
+ def initialize(client: Logux::Client.new,
8
+ version: Logux::PROTOCOL_VERSION,
9
+ password: Logux.configuration.password)
10
+ @client = client
11
+ @version = version
12
+ @password = password
13
+ end
14
+
15
+ def call(commands)
16
+ return if commands.empty?
17
+
18
+ prepared_data = prepare_data(commands)
19
+ Logux.logger.debug("Logux add: #{prepared_data}")
20
+ client.post(prepared_data)
21
+ end
22
+
23
+ private
24
+
25
+ def prepare_data(commands)
26
+ {
27
+ version: PROTOCOL_VERSION,
28
+ password: password,
29
+ commands: commands.map do |command|
30
+ action = command.first
31
+ meta = command[1]
32
+ ['action', action, meta || Meta.new]
33
+ end
34
+ }
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logux
4
+ class Auth < Hashie::Mash
5
+ end
6
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logux
4
+ class BaseController
5
+ class << self
6
+ def verify_authorized!
7
+ Logux.configuration.verify_authorized = true
8
+ end
9
+
10
+ def unverify_authorized!
11
+ Logux.configuration.verify_authorized = false
12
+ end
13
+ end
14
+
15
+ attr_reader :action, :meta
16
+
17
+ def initialize(action:, meta: {})
18
+ @action = action
19
+ @meta = meta
20
+ end
21
+
22
+ def respond(status, action: @action, meta: @meta, custom_data: nil)
23
+ Logux::Response.new(status,
24
+ action: action,
25
+ meta: meta,
26
+ custom_data: custom_data)
27
+ end
28
+
29
+ def user_id
30
+ @user_id ||= meta.user_id
31
+ end
32
+
33
+ def node_id
34
+ @node_id ||= meta.node_id
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logux
4
+ class ChannelController < BaseController
5
+ def subscribe
6
+ Logux.add_batch(initial_data.map { |d| [d, initial_meta] })
7
+ end
8
+
9
+ def initial_data
10
+ []
11
+ end
12
+
13
+ def initial_meta
14
+ { clients: [meta.client_id] }
15
+ end
16
+
17
+ def since_time
18
+ @since_time ||= begin
19
+ since = action['since'].try(:[], 'time')
20
+ Time.at(since).to_datetime if since
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logux
4
+ class ClassFinder
5
+ attr_reader :action, :meta
6
+
7
+ def initialize(action:, meta:)
8
+ @action = action
9
+ @meta = meta
10
+ end
11
+
12
+ def find_action_class
13
+ "#{class_namespace}::#{class_name}".constantize
14
+ rescue NameError
15
+ message =
16
+ "Unable to find action #{class_name.camelize}.\n" \
17
+ "Should be in app/logux/#{class_namespace.downcase}/#{class_path}.rb"
18
+ raise_error_for_failed_find(message)
19
+ end
20
+
21
+ def find_policy_class
22
+ "Policies::#{class_namespace}::#{class_name}".constantize
23
+ rescue NameError
24
+ message =
25
+ "Unable to find action policy #{class_name.camelize}.\n" \
26
+ "Should be in app/logux/#{class_namespace.downcase}/#{class_path}.rb"
27
+ raise_error_for_failed_find(message)
28
+ end
29
+
30
+ def class_name
31
+ if subscribe?
32
+ action.channel_name.camelize
33
+ else
34
+ action.type.split('/')[0..-2].map(&:camelize).join('::')
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def class_namespace
41
+ subscribe? ? 'Channels' : 'Actions'
42
+ end
43
+
44
+ def subscribe?
45
+ action.type == 'logux/subscribe'
46
+ end
47
+
48
+ def action?
49
+ !subscribe?
50
+ end
51
+
52
+ def class_path
53
+ "#{class_namespace}::#{class_name}".underscore
54
+ end
55
+
56
+ def raise_error_for_failed_find(message)
57
+ exception_class = action? ? UnknownActionError : UnknownChannelError
58
+ raise exception_class.new(message, meta: meta)
59
+ end
60
+ end
61
+ end
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logux
4
+ class Client
5
+ attr_reader :logux_host
6
+
7
+ def initialize(logux_host: Logux.configuration.logux_host)
8
+ @logux_host = logux_host
9
+ end
10
+
11
+ def post(params)
12
+ client.post(params.to_json,
13
+ content_type: :json,
14
+ accept: :json)
15
+ end
16
+
17
+ def client
18
+ @client ||= RestClient::Resource.new(logux_host, verify_ssl: false)
19
+ end
20
+ end
21
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logux
4
+ class Engine < ::Rails::Engine
5
+ end
6
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logux
4
+ class ErrorRenderer
5
+ attr_reader :exception
6
+
7
+ def initialize(exception)
8
+ @exception = exception
9
+ end
10
+
11
+ def message
12
+ case exception
13
+ when Logux::WithMetaError
14
+ build_message(exception, exception.meta.id)
15
+ when Logux::UnauthorizedError
16
+ build_message(exception, exception.message)
17
+ when StandardError
18
+ # some runtime error that should be fixed
19
+ render_stardard_error(exception)
20
+ end
21
+ end
22
+
23
+ private
24
+
25
+ def render_stardard_error(exception)
26
+ if Logux.configuration.render_backtrace_on_error
27
+ ['error', exception.message + "\n" + exception.backtrace.join("\n")]
28
+ else
29
+ ['error', 'Please look server logs for more information']
30
+ end
31
+ end
32
+
33
+ def build_message(exception, additional_info)
34
+ [
35
+ exception.class.name.demodulize.camelize(:lower).gsub(/Error/, ''),
36
+ additional_info
37
+ ]
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,36 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logux
4
+ class Meta < Hash
5
+ def initialize(source_hash = {})
6
+ merge!(source_hash.stringify_keys)
7
+
8
+ self['id'] ||= Logux.generate_action_id
9
+ self['time'] ||= self['id'].split(' ').first
10
+ end
11
+
12
+ def node_id
13
+ id.split(' ').second
14
+ end
15
+
16
+ def user_id
17
+ node_id.split(':').first
18
+ end
19
+
20
+ def client_id
21
+ node_id.split(':')[0..1].join(':')
22
+ end
23
+
24
+ def logux_order
25
+ time + ' ' + id.split(' ')[1..-1].join(' ')
26
+ end
27
+
28
+ def time
29
+ fetch('time')
30
+ end
31
+
32
+ def id
33
+ fetch('id')
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'model/updater'
4
+ require_relative 'model/proxy'
5
+ require_relative 'model/dsl'
6
+ require_relative 'model/updates_deprecator'
7
+
8
+ module Logux
9
+ module Model
10
+ class InsecureUpdateError < StandardError; end
11
+
12
+ def self.included(base)
13
+ base.extend(DSL)
14
+
15
+ base.before_update :touch_logux_order_for_changes,
16
+ unless: -> { changes.key?('logux_fields_updated_at') }
17
+ end
18
+
19
+ def logux
20
+ Proxy.new(self)
21
+ end
22
+
23
+ private
24
+
25
+ def touch_logux_order_for_changes
26
+ attributes = changed.each_with_object({}) do |attr, res|
27
+ res[attr] = send(attr)
28
+ end
29
+
30
+ updater = Updater.new(model: self, attributes: attributes)
31
+ self.logux_fields_updated_at = updater.updated_attributes
32
+
33
+ ActiveSupport::Notifications.instrument(
34
+ Logux::Model::UpdatesDeprecator::EVENT,
35
+ model: self
36
+ )
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logux
4
+ module Model
5
+ module DSL
6
+ def logux_crdt_map_attributes(*attributes)
7
+ @logux_crdt_mapped_attributes = attributes
8
+ end
9
+
10
+ def logux_crdt_mapped_attributes
11
+ @logux_crdt_mapped_attributes ||= []
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logux
4
+ module Model
5
+ class Proxy
6
+ def initialize(model)
7
+ @model = model
8
+ end
9
+
10
+ def update(meta, attributes)
11
+ updater = Updater.new(
12
+ model: @model,
13
+ logux_order: meta.logux_order,
14
+ attributes: attributes
15
+ )
16
+ @model.update_attributes(updater.updated_attributes)
17
+ end
18
+
19
+ def updated_at(field)
20
+ @model.logux_fields_updated_at[field.to_s]
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Logux
4
+ module Model
5
+ class Updater
6
+ def initialize(model:, attributes:, logux_order: Logux.generate_action_id)
7
+ @model = model
8
+ @logux_order = logux_order
9
+ @attributes = attributes
10
+ end
11
+
12
+ def updated_attributes
13
+ newer_updates.merge(logux_fields_updated_at: fields_updated_at)
14
+ end
15
+
16
+ private
17
+
18
+ def fields_updated_at
19
+ @fields_updated_at ||=
20
+ newer_updates.slice(*tracked_fields)
21
+ .keys
22
+ .reduce(@model.logux_fields_updated_at) do |acc, attr|
23
+ acc.merge(attr => @logux_order)
24
+ end
25
+ end
26
+
27
+ def newer_updates
28
+ @newer_updates ||= @attributes.reject do |attr, _|
29
+ field_updated_at = @model.logux.updated_at(attr)
30
+ field_updated_at && field_updated_at > @logux_order
31
+ end
32
+ end
33
+
34
+ def tracked_fields
35
+ @model.class.logux_crdt_mapped_attributes
36
+ end
37
+ end
38
+ end
39
+ end