pakyow-data 1.0.0.rc3 → 1.0.0.rc4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: bc46a555cdeabd3e92057164c7d73eb709e65b249d712a21efab9ac3d823d6b7
4
- data.tar.gz: de17be63f229e13b4c71dee1c2f688023722390b33ff2b629579049ba464d2ff
3
+ metadata.gz: 89ef7f94e6abbe68bb389a5c560d9eba58ce4452bdad141c7b16a0a0f8e8dcd7
4
+ data.tar.gz: 316e9f4896f4027323ae60700d8ce1d90e1146618fc1c7653089f72c2607fc23
5
5
  SHA512:
6
- metadata.gz: 515d09959682792f090f2a1584bdfe55bd3a711a3fc486052354141c46ce0087f001bb3d6b6ec1f640601e99b0fc82895d45d00af88220534aa73d35fb76c7bc
7
- data.tar.gz: c23f714272f8e8e514faa0b8163bb932e211d34c385a29f1c170c21671a13896002ca6c1eff1671f6a0b936990a8f8c1557071fbf7cfc4bd11a5fe0d1c4c8e82
6
+ metadata.gz: 331a2fbc87996b350a1d06ff1087d15f5a6b98d9a20e29fa12c05e95cc8291c410282f2c0a0d646cc81874bfd7de4dcac4bbf6843cc04bb16c9eae924e123276
7
+ data.tar.gz: 7816ae161a6d96d19dabdf0356792a2b17c69d6ea6cd6de2f6876b99143a67c27044d7a3703904a8135a87506a55e82655d0cf9c8dfd1cf94372e7b29555df6a
@@ -3,7 +3,7 @@
3
3
  module Pakyow
4
4
  module Data
5
5
  module Adapters
6
- class Abstract
6
+ class Base
7
7
  def initialize(opts, logger: nil)
8
8
  @opts, @logger = opts, logger
9
9
  end
@@ -8,7 +8,7 @@ module Pakyow
8
8
  extend Support::Extension
9
9
 
10
10
  apply_extension do
11
- command :create, performs_create: true do |values|
11
+ command :create, creates: true do |values|
12
12
  begin
13
13
  inserted_return_value = insert(values)
14
14
  if self.class.primary_key_field
@@ -27,7 +27,7 @@ module Pakyow
27
27
  end
28
28
  end
29
29
 
30
- command :update, performs_update: true do |values|
30
+ command :update, updates: true do |values|
31
31
  __getobj__.select(self.class.primary_key_field).map { |result|
32
32
  result[self.class.primary_key_field]
33
33
  }.tap do
@@ -43,7 +43,7 @@ module Pakyow
43
43
  end
44
44
  end
45
45
 
46
- command :delete, provides_dataset: false, performs_delete: true do
46
+ command :delete, provides_dataset: false, deletes: true do
47
47
  begin
48
48
  delete
49
49
  rescue Sequel::ForeignKeyConstraintViolation => error
@@ -6,13 +6,13 @@ require "pakyow/support/extension"
6
6
 
7
7
  require "pakyow/support/core_refinements/string/normalization"
8
8
 
9
- require "pakyow/data/adapters/abstract"
9
+ require "pakyow/data/adapters/base"
10
10
 
11
11
  module Pakyow
12
12
  module Data
13
13
  module Adapters
14
14
  # @api private
15
- class Sql < Abstract
15
+ class Sql < Base
16
16
  require "pakyow/data/adapters/sql/commands"
17
17
  require "pakyow/data/adapters/sql/dataset_methods"
18
18
  require "pakyow/data/adapters/sql/migrator"
@@ -91,7 +91,7 @@ module Pakyow
91
91
 
92
92
  unless joining_source
93
93
  joining_source = source.ancestors.find { |ancestor|
94
- ancestor != source && ancestor.ancestors.include?(Sources::Abstract)
94
+ ancestor != source && ancestor.ancestors.include?(Sources::Base)
95
95
  }.make(
96
96
  joining_source_name,
97
97
  adapter: source.adapter,
@@ -125,7 +125,7 @@ module Pakyow
125
125
  subscriptions = []
126
126
 
127
127
  if subscribable?
128
- subscriptions << {
128
+ subscription = {
129
129
  source: @source.source_name,
130
130
  ephemeral: @source.is_a?(Sources::Ephemeral),
131
131
  handler: handler,
@@ -134,6 +134,10 @@ module Pakyow
134
134
  proxy: self
135
135
  }
136
136
 
137
+ unless subscriptions.include?(subscription)
138
+ subscriptions << subscription
139
+ end
140
+
137
141
  @nested_proxies.each do |related_proxy|
138
142
  subscriptions.concat(
139
143
  related_proxy.subscribe_related(
@@ -154,7 +158,7 @@ module Pakyow
154
158
 
155
159
  if association = parent_source.class.find_association_to_source(@source)
156
160
  parent_source.each do |parent_result|
157
- subscriptions << {
161
+ subscription = {
158
162
  source: @source.source_name,
159
163
  handler: handler,
160
164
  payload: payload,
@@ -163,6 +167,10 @@ module Pakyow
163
167
  ),
164
168
  proxy: serialized_proxy
165
169
  }
170
+
171
+ unless subscriptions.include?(subscription)
172
+ subscriptions << subscription
173
+ end
166
174
  end
167
175
  else
168
176
  Pakyow.logger.error "tried to subscribe a related source, but we don't know how it's related"
@@ -226,9 +234,21 @@ module Pakyow
226
234
  # Populate argument qualifications with argument values.
227
235
  #
228
236
  qualifications_for_proxied_call.each do |qualification_key, qualification_value|
229
- next unless qualification_value.to_s.start_with?("__arg")
230
- arg_number = qualification_value.to_s.gsub(/[^0-9]/, "").to_i
231
- qualifications_for_proxied_call[qualification_key] = @source.class.attributes[qualification_key][proxied_call[1][arg_number]]
237
+ if qualification_value.to_s.start_with?("__arg")
238
+ arg_number = qualification_value.to_s.gsub(/[^0-9]/, "").to_i
239
+
240
+ arg_value = proxied_call[1][arg_number]
241
+ arg_value = case arg_value
242
+ when Array
243
+ arg_value.map { |each_value|
244
+ @source.class.attributes[qualification_key][each_value]
245
+ }
246
+ else
247
+ @source.class.attributes[qualification_key][arg_value]
248
+ end
249
+
250
+ qualifications_for_proxied_call[qualification_key] = arg_value
251
+ end
232
252
  end
233
253
 
234
254
  qualifications.merge(qualifications_for_proxied_call)
@@ -5,6 +5,9 @@ require "delegate"
5
5
  module Pakyow
6
6
  module Data
7
7
  class Result < SimpleDelegator
8
+ # @api private
9
+ attr_reader :__proxy
10
+
8
11
  def initialize(result, proxy, originating_method: nil, originating_args: [])
9
12
  @__proxy = proxy
10
13
  @originating_method = originating_method
@@ -7,7 +7,7 @@ require "pakyow/support/class_state"
7
7
  module Pakyow
8
8
  module Data
9
9
  module Sources
10
- class Abstract < SimpleDelegator
10
+ class Base < SimpleDelegator
11
11
  extend Support::ClassState
12
12
  class_state :__finalized, default: false, inheritable: true
13
13
 
@@ -5,12 +5,12 @@ require "securerandom"
5
5
 
6
6
  require "pakyow/support/core_refinements/array/ensurable"
7
7
 
8
- require "pakyow/data/sources/abstract"
8
+ require "pakyow/data/sources/base"
9
9
 
10
10
  module Pakyow
11
11
  module Data
12
12
  module Sources
13
- class Ephemeral < Abstract
13
+ class Ephemeral < Base
14
14
  using Support::Refinements::Array::Ensurable
15
15
  attr_reader :type, :qualifications
16
16
 
@@ -12,8 +12,8 @@ module Pakyow
12
12
  using Support::DeepDup
13
13
  using Support::Refinements::Array::Ensurable
14
14
 
15
- def initialize(name, block:, source:, provides_dataset:, performs_create:, performs_update:, performs_delete:)
16
- @name, @block, @source, @provides_dataset, @performs_create, @performs_update, @performs_delete = name, block, source, provides_dataset, performs_create, performs_update, performs_delete
15
+ def initialize(name, block:, source:, provides_dataset:, creates:, updates:, deletes:)
16
+ @name, @block, @source, @provides_dataset, @creates, @updates, @deletes = name, block, source, provides_dataset, creates, updates, deletes
17
17
  end
18
18
 
19
19
  def call(values = {})
@@ -24,7 +24,7 @@ module Pakyow
24
24
  #
25
25
  @source.class.attributes.each do |attribute_name, attribute|
26
26
  if attribute.meta[:required]
27
- if @performs_create && !values.include?(attribute_name)
27
+ if @creates && !values.include?(attribute_name)
28
28
  raise NotNullViolation.new_with_message(attribute: attribute_name)
29
29
  end
30
30
 
@@ -66,7 +66,7 @@ module Pakyow
66
66
  # Update timestamp fields.
67
67
  #
68
68
  if timestamp_fields = @source.class.timestamp_fields
69
- if @performs_create
69
+ if @creates
70
70
  timestamp_fields.values.each do |timestamp_field|
71
71
  final_values[timestamp_field] = Time.now
72
72
  end
@@ -77,7 +77,7 @@ module Pakyow
77
77
  end
78
78
  end
79
79
 
80
- if @performs_create
80
+ if @creates
81
81
  # Set default values.
82
82
  #
83
83
  @source.class.attributes.each do |attribute_name, attribute|
@@ -102,7 +102,7 @@ module Pakyow
102
102
  case association_value
103
103
  when Proxy
104
104
  if association_value.source.class == association.associated_source
105
- if association.result_type == :one && (association_value.count > 1 || (@performs_update && @source.count > 1))
105
+ if association.result_type == :one && (association_value.count > 1 || (@updates && @source.count > 1))
106
106
  raise ConstraintViolation.new_with_message(
107
107
  :associate_multiple,
108
108
  association: association.name
@@ -244,20 +244,20 @@ module Pakyow
244
244
  end
245
245
  end
246
246
 
247
- original_dataset = if @performs_update
247
+ original_dataset = if @updates
248
248
  # Hold on to the original values so we can update them locally.
249
249
  @source.dup.to_a
250
250
  else
251
251
  nil
252
252
  end
253
253
 
254
- unless @provides_dataset || @performs_update
254
+ unless @provides_dataset || @updates
255
255
  # Cache the result prior to running the command.
256
256
  @source.to_a
257
257
  end
258
258
 
259
259
  @source.transaction do
260
- if @performs_delete
260
+ if @deletes
261
261
  @source.class.associations.values.flatten.select(&:dependents?).each do |association|
262
262
  dependent_values = @source.class.container.connection.adapter.restrict_to_attribute(
263
263
  @source.class.primary_key_field, @source
@@ -326,12 +326,13 @@ module Pakyow
326
326
  end
327
327
  end
328
328
 
329
- if @performs_create || @performs_update
329
+ if @creates || @updates
330
330
  # Ensure that has_one associations only have one associated object.
331
331
  #
332
332
  @source.class.associations[:belongs_to].flat_map { |belongs_to_association|
333
333
  belongs_to_association.associated_source.associations[:has_one].select { |has_one_association|
334
- has_one_association.associated_query_field == belongs_to_association.query_field
334
+ has_one_association.associated_source == @source.class &&
335
+ has_one_association.associated_query_field == belongs_to_association.query_field
335
336
  }
336
337
  }.each do |association|
337
338
  value = final_values.dig(
@@ -354,7 +355,7 @@ module Pakyow
354
355
 
355
356
  command_result = @source.instance_exec(final_values, &@block)
356
357
 
357
- final_result = if @performs_update
358
+ final_result = if @updates
358
359
  # For updates, we fetch the values prior to performing the update and
359
360
  # return a source containing locally updated values. This lets us see
360
361
  # the original values but prevents us from fetching twice.
@@ -382,7 +383,7 @@ module Pakyow
382
383
  @source
383
384
  end
384
385
 
385
- if @performs_create || @performs_update
386
+ if @creates || @updates
386
387
  # Update records associated with the data we just changed.
387
388
  #
388
389
  future_associated_changes.each do |association, association_value|
@@ -4,7 +4,7 @@ require "pakyow/support/makeable"
4
4
  require "pakyow/support/class_state"
5
5
  require "pakyow/support/inflector"
6
6
 
7
- require "pakyow/data/sources/abstract"
7
+ require "pakyow/data/sources/base"
8
8
 
9
9
  module Pakyow
10
10
  module Data
@@ -54,7 +54,7 @@ module Pakyow
54
54
  # data.posts.by_id(1).first
55
55
  # => #<Pakyow::Data::Object @values={:id => 1, :title => "foo", :created_at => "2018-11-30 10:55:05 -0800", :updated_at => "2018-11-30 10:55:05 -0800"}>
56
56
  #
57
- class Relational < Sources::Abstract
57
+ class Relational < Sources::Base
58
58
  require "pakyow/data/sources/relational/associations/belongs_to"
59
59
  require "pakyow/data/sources/relational/associations/has_many"
60
60
  require "pakyow/data/sources/relational/associations/has_one"
@@ -188,9 +188,9 @@ module Pakyow
188
188
  block: command[:block],
189
189
  source: self,
190
190
  provides_dataset: command[:provides_dataset],
191
- performs_create: command[:performs_create],
192
- performs_update: command[:performs_update],
193
- performs_delete: command[:performs_delete]
191
+ creates: command[:creates],
192
+ updates: command[:updates],
193
+ deletes: command[:deletes]
194
194
  )
195
195
  else
196
196
  raise(
@@ -418,20 +418,20 @@ module Pakyow
418
418
  class_state :timestamp_fields
419
419
  class_state :primary_key_field
420
420
  class_state :attributes, default: {}
421
- class_state :qualifications, default: {}, getter: false
421
+ class_state :qualifications, default: {}, reader: false
422
422
  class_state :associations, default: { belongs_to: [], has_many: [], has_one: [] }
423
423
  class_state :commands, default: {}
424
424
 
425
425
  class << self
426
426
  attr_reader :name, :adapter, :connection
427
427
 
428
- def command(command_name, provides_dataset: true, performs_create: false, performs_update: false, performs_delete: false, &block)
428
+ def command(command_name, provides_dataset: true, creates: false, updates: false, deletes: false, &block)
429
429
  @commands[command_name] = {
430
430
  block: block,
431
431
  provides_dataset: provides_dataset,
432
- performs_create: performs_create,
433
- performs_update: performs_update,
434
- performs_delete: performs_delete
432
+ creates: creates,
433
+ updates: updates,
434
+ deletes: deletes
435
435
  }
436
436
  end
437
437
 
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require "digest/sha1"
4
+ require "zlib"
4
5
 
5
6
  require "redis"
6
7
  require "concurrent/timer_task"
@@ -20,7 +21,7 @@ module Pakyow
20
21
  class Redis
21
22
  class << self
22
23
  def stringify_subscription(subscription)
23
- Marshal.dump(subscription)
24
+ Zlib::Deflate.deflate(Marshal.dump(subscription))
24
25
  end
25
26
 
26
27
  def generate_subscription_id(subscription_string)
@@ -154,7 +155,7 @@ module Pakyow
154
155
  key_subscription_id(subscription_id)
155
156
  }).zip(subscription_ids).map { |subscription_string, subscription_id|
156
157
  begin
157
- Marshal.restore(subscription_string).tap do |subscription|
158
+ Marshal.restore(Zlib::Inflate.inflate(subscription_string)).tap do |subscription|
158
159
  subscription[:id] = subscription_id
159
160
  end
160
161
  rescue TypeError
@@ -56,19 +56,45 @@ module Pakyow
56
56
  end
57
57
 
58
58
  def did_mutate(source_name, changed_values = nil, result_source = nil)
59
- @executor << Proc.new {
60
- begin
61
- @adapter.subscriptions_for_source(source_name).select { |subscription|
62
- process?(subscription, changed_values, result_source)
63
- }.uniq.each do |subscription|
64
- if subscription[:version] == @app.config.data.subscriptions.version
59
+ @executor.post(source_name, changed_values, result_source, Pakyow.logger.target) do |source_name, changed_values, result_source, logger|
60
+ logger.internal {
61
+ "[Pakyow::Data::Subscribers] did mutate #{source_name}"
62
+ }
63
+
64
+ subscriptions = @adapter.subscriptions_for_source(source_name)
65
+
66
+ logger.internal {
67
+ "[Pakyow::Data::Subscribers] fetched #{subscriptions.count} subscriptions"
68
+ }
69
+
70
+ subscriptions.uniq { |subscription|
71
+ subscription.dig(:payload, :id) || subscription
72
+ }.select { |subscription|
73
+ process?(subscription, changed_values, result_source)
74
+ }.each do |subscription|
75
+ if subscription[:version] == @app.config.data.subscriptions.version
76
+ begin
77
+ logger.internal {
78
+ "[Pakyow::Data::Subscribers] processing subscription #{subscription[:id]}"
79
+ }
80
+
65
81
  process(subscription, result_source)
82
+
83
+ logger.internal {
84
+ "[Pakyow::Data::Subscribers] finished processing subscription #{subscription[:id]}"
85
+ }
86
+ rescue => error
87
+ logger.error {
88
+ "[Pakyow::Data::Subscribers] did_mutate failed: #{error}"
89
+ }
66
90
  end
67
91
  end
68
- rescue => error
69
- Pakyow.logger.error "[Pakyow::Data::Subscribers] did_mutate failed: #{error}"
70
92
  end
71
- }
93
+
94
+ logger.internal {
95
+ "[Pakyow::Data::Subscribers] finished mutate for #{source_name}"
96
+ }
97
+ end
72
98
  end
73
99
 
74
100
  def unsubscribe(subscriber)
@@ -134,13 +160,19 @@ module Pakyow
134
160
  QUALIFIABLE_TYPES = [Hash, Support::IndifferentHash].freeze
135
161
  def qualified?(qualifications, changed_values, changed_results, original_results)
136
162
  qualifications.all? do |key, value|
137
- (QUALIFIABLE_TYPES.include?(changed_values.class) && changed_values.to_h[key] == value) || qualified_result?(key, value, changed_results, original_results)
163
+ (
164
+ QUALIFIABLE_TYPES.include?(changed_values.class) && (
165
+ (
166
+ value.is_a?(Array) && value.include?(changed_values.to_h[key])
167
+ ) || changed_values.to_h[key] == value
168
+ )
169
+ ) || qualified_result?(key, value, changed_results, original_results)
138
170
  end
139
171
  end
140
172
 
141
173
  def qualified_result?(key, value, changed_results, original_results)
142
174
  original_results.concat(changed_results).any? do |result|
143
- result[key] == value
175
+ (value.is_a?(Array) && value.include?(result[key])) || result[key] == value
144
176
  end
145
177
  end
146
178
  end
@@ -7,19 +7,27 @@ module Pakyow
7
7
  # Validates that the value is unique within its data source.
8
8
  #
9
9
  module Unique
10
- def self.name
11
- :unique
12
- end
13
-
14
10
  def self.message(**)
15
11
  "must be unique"
16
12
  end
17
13
 
18
14
  def self.valid?(value, source:, **options)
19
- options[:context].app.data.public_send(source).public_send(:"by_#{options[:key]}", value).count == 0
15
+ query = options[:context].app.data.public_send(source).public_send(:"by_#{options[:key]}", value)
16
+
17
+ if updating = options[:updating]
18
+ if updating.is_a?(Data::Result)
19
+ query.count == 0 || query.any? { |result|
20
+ result[updating.__proxy.source.class.primary_key_field] == updating[updating.__proxy.source.class.primary_key_field]
21
+ }
22
+ else
23
+ raise ArgumentError, "Expected `#{updating.class}' to be a `Pakyow::Data::Result'"
24
+ end
25
+ else
26
+ query.count == 0
27
+ end
20
28
  end
21
29
  end
22
30
 
23
- Validator.register_validation(Unique)
31
+ Validator.register_validation(Unique, :unique)
24
32
  end
25
33
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: pakyow-data
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0.rc3
4
+ version: 1.0.0.rc4
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Powell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2019-07-09 00:00:00.000000000 Z
11
+ date: 2019-07-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: pakyow-core
@@ -16,28 +16,28 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 1.0.0.rc3
19
+ version: 1.0.0.rc4
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 1.0.0.rc3
26
+ version: 1.0.0.rc4
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: pakyow-support
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
31
  - - '='
32
32
  - !ruby/object:Gem::Version
33
- version: 1.0.0.rc3
33
+ version: 1.0.0.rc4
34
34
  type: :runtime
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
38
  - - '='
39
39
  - !ruby/object:Gem::Version
40
- version: 1.0.0.rc3
40
+ version: 1.0.0.rc4
41
41
  - !ruby/object:Gem::Dependency
42
42
  name: concurrent-ruby
43
43
  requirement: !ruby/object:Gem::Requirement
@@ -118,7 +118,7 @@ files:
118
118
  - LICENSE
119
119
  - README.md
120
120
  - lib/pakyow/data.rb
121
- - lib/pakyow/data/adapters/abstract.rb
121
+ - lib/pakyow/data/adapters/base.rb
122
122
  - lib/pakyow/data/adapters/sql.rb
123
123
  - lib/pakyow/data/adapters/sql/commands.rb
124
124
  - lib/pakyow/data/adapters/sql/dataset_methods.rb
@@ -143,7 +143,7 @@ files:
143
143
  - lib/pakyow/data/object.rb
144
144
  - lib/pakyow/data/proxy.rb
145
145
  - lib/pakyow/data/result.rb
146
- - lib/pakyow/data/sources/abstract.rb
146
+ - lib/pakyow/data/sources/base.rb
147
147
  - lib/pakyow/data/sources/ephemeral.rb
148
148
  - lib/pakyow/data/sources/relational.rb
149
149
  - lib/pakyow/data/sources/relational/association.rb