Module: ArSync

Defined in:
lib/ar_sync.rb,
lib/ar_sync/core.rb,
lib/ar_sync/rails.rb,
lib/ar_sync/config.rb,
lib/ar_sync/version.rb,
lib/generators/ar_sync/types/types_generator.rb,
lib/generators/ar_sync/install/install_generator.rb

Defined Under Namespace

Modules: ApiControllerConcern, ClassMethodsBase, GraphSync, Rails, StaticJsonConcern, TreeSync, TypeScript Classes: ApiNotFound, Collection, CollectionField, CollectionWithOrder, Config, DataField, Field, HasManyField, HasOneField, InstallGenerator, TypesGenerator

Constant Summary collapse

VERSION =
'1.0.1'

Class Method Summary collapse

Class Method Details

._extract_paths(args) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
# File 'lib/ar_sync/core.rb', line 119

def self._extract_paths(args)
  parsed = ArSerializer::Serializer.parse_args args
  paths = []
  extract = lambda do |path, attributes|
    paths << path
    attributes.each do |key, value|
      sub_attributes = value[:attributes]
      next unless sub_attributes
      sub_path = [*path, key]
      extract.call sub_path, sub_attributes
    end
  end
  extract.call [], parsed[:attributes]
  paths
end

.configObject



11
12
13
# File 'lib/ar_sync/config.rb', line 11

def self.config
  @config ||= Config.new :current_user, nil, nil
end

.configure {|config| ... } ⇒ Object

Yields:



15
16
17
# File 'lib/ar_sync/config.rb', line 15

def self.configure
  yield config
end

.on_notification(&block) ⇒ Object



17
18
19
# File 'lib/ar_sync/core.rb', line 17

def self.on_notification(&block)
  @sync_send_block = block
end

.serialize(record_or_records, query, user: nil) ⇒ Object



135
136
137
# File 'lib/ar_sync/core.rb', line 135

def self.serialize(record_or_records, query, user: nil)
  ArSerializer.serialize record_or_records, query, context: user, include_id: true, use: :sync
end

.signed_key(key, time) ⇒ Object



93
94
95
# File 'lib/ar_sync/core.rb', line 93

def self.signed_key(key, time)
  "#{key};#{time};#{Digest::SHA256.hexdigest("#{config.key_secret}#{key};#{time}")[0, 16]}"
end

.skip_notification?Boolean

Returns:

  • (Boolean)


31
32
33
# File 'lib/ar_sync/core.rb', line 31

def self.skip_notification?
  Thread.current[:ar_sync_skip_notification]
end

.sync_api(model, current_user, args) ⇒ Object



108
109
110
111
112
113
114
115
116
117
# File 'lib/ar_sync/core.rb', line 108

def self.sync_api(model, current_user, args)
  paths = _extract_paths args
  keys = paths.flat_map do |path|
    [sync_key(model, path), sync_key(model, path, current_user)]
  end
  {
    keys: keys,
    data: serialize(model, args, user: current_user)
  }
end

.sync_collection_api(collection, current_user, args) ⇒ Object



97
98
99
100
101
102
103
104
105
106
# File 'lib/ar_sync/core.rb', line 97

def self.sync_collection_api(collection, current_user, args)
  paths = _extract_paths args
  keys = paths.flat_map do |path|
    [sync_key(collection, path), sync_key(collection, path, current_user)]
  end
  {
    keys: keys,
    data: serialize(collection.to_a, args, user: current_user)
  }
end

.sync_graph_key(model, to_user = nil, signature: true) ⇒ Object



66
67
68
# File 'lib/ar_sync/core.rb', line 66

def self.sync_graph_key(model, to_user = nil, signature: true)
  sync_key(model, :graph_sync, to_user, signature: signature) + ';/'
end

.sync_graph_keys(model, user) ⇒ Object



70
71
72
# File 'lib/ar_sync/core.rb', line 70

def self.sync_graph_keys(model, user)
  [sync_graph_key(model), sync_graph_key(model, user)]
end

.sync_graph_send(to:, action:, model:, path: nil, to_user: nil) ⇒ Object



55
56
57
58
59
60
61
62
63
64
# File 'lib/ar_sync/core.rb', line 55

def self.sync_graph_send(to:, action:, model:, path: nil, to_user: nil)
  key = sync_graph_key to, to_user, signature: false
  event = ["#{key}#{path}", action: action, class_name: model.class.base_class.name, id: model.id]
  buffer = Thread.current[:ar_sync_compact_notifications]
  if buffer
    buffer << event
  else
    @sync_send_block&.call [event]
  end
end

.sync_key(model, path, to_user = nil, signature: true) ⇒ Object



74
75
76
77
78
79
80
81
82
83
# File 'lib/ar_sync/core.rb', line 74

def self.sync_key(model, path, to_user = nil, signature: true)
  if model.is_a? ArSync::Collection
    key = [to_user&.id, model.klass.name, model.name, path].join '/'
  else
    key = [to_user&.id, model.class.name, model.id, path].join '/'
  end
  key = Digest::SHA256.hexdigest("#{config.key_secret}#{key}")[0, 32] if config.key_secret
  key = "#{config.key_prefix}#{key}" if config.key_prefix
  signature && config.key_expires_in ? signed_key(key, Time.now.to_i.to_s) : key
end

.sync_tree_send(to:, action:, path:, data:, to_user: nil, ordering: nil) ⇒ Object



44
45
46
47
48
49
50
51
52
53
# File 'lib/ar_sync/core.rb', line 44

def self.sync_tree_send(to:, action:, path:, data:, to_user: nil, ordering: nil)
  key = sync_key to, path.grep(Symbol), to_user, signature: false
  event = [key, action: action, path: path, data: data, ordering: ordering]
  buffer = Thread.current[:ar_sync_compact_notifications]
  if buffer
    buffer << event
  else
    @sync_send_block&.call [event]
  end
end

.use(mode, klass: ActiveRecord::Base) ⇒ Object



4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# File 'lib/ar_sync.rb', line 4

def self.use(mode, klass: ActiveRecord::Base)
  case mode
  when :tree
    if klass.ancestors.include? ArSync::GraphSync
      raise ArgumentError, 'already activated ArSync::GraphSync'
    end
    klass.include ArSync::TreeSync
  when :graph
    if klass.ancestors.include? ArSync::TreeSync
      raise ArgumentError, 'already activated ArSync::TreeSync'
    end
    klass.include ArSync::GraphSync
  else
    raise ArgumentError, 'argument should be :tree or :graph'
  end
end

.validate_expiration(signed_key) ⇒ Object



85
86
87
88
89
90
91
# File 'lib/ar_sync/core.rb', line 85

def self.validate_expiration(signed_key)
  signed_key = signed_key.to_s
  return signed_key unless config.key_expires_in
  key, time, signature, other = signed_key.split ';', 4
  return unless signed_key(key, time) == [key, time, signature].join(';')
  [key, other].compact.join ';' if Time.now.to_i - time.to_i < config.key_expires_in
end

.with_compact_notificationObject



21
22
23
24
25
26
27
28
29
# File 'lib/ar_sync/core.rb', line 21

def self.with_compact_notification
  key = :ar_sync_compact_notifications
  Thread.current[key] = []
  yield
ensure
  events = Thread.current[key]
  Thread.current[key] = nil
  @sync_send_block&.call events if events.present?
end

.without_notificationObject



35
36
37
38
39
40
41
42
# File 'lib/ar_sync/core.rb', line 35

def self.without_notification
  key = :ar_sync_skip_notification
  flag_was = Thread.current[key]
  Thread.current[key] = true
  yield
ensure
  Thread.current[key] = flag_was
end