pakyow-data 1.0.0.rc3 → 1.0.0.rc4

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.
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