logux_rails 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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