query_dam 0.1.0 → 0.1.1

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
  SHA1:
3
- metadata.gz: 0612b6f926d6c396e8a7be1e4a8c5c6ab2a888d7
4
- data.tar.gz: a8973faabf07ffeb5afeb3540b1def06187ca51a
3
+ metadata.gz: 801eba4331fd983c0db84d269e8d327a82ebd5d1
4
+ data.tar.gz: 32988056b1e8a66e56b277ed380f13d338595186
5
5
  SHA512:
6
- metadata.gz: 44d012c14b7e738279bd545156efd54a93d65dcc965031223c4fe73b4ff9cd8da66d7aa19ddc311901a67950a544f8e951b2b0af6eb2fd4c15ac526f97655a5c
7
- data.tar.gz: a294b14782c0b07cb595061b511407aada190ee135e065a5dd6a54c4d6c097bcec10d18fa440a4be906cbf82ed28e92ca6a5c9625e9f1d2f93669bd7f7f73d18
6
+ metadata.gz: 0f6d274864e35f8692ced8092c5907624dcd9ffe26578db66559c4618e4067cb2ff2c5f2cd1afaaacbd447f8d191db889a05fcc8be87b639d0dd078e15456cc1
7
+ data.tar.gz: 83c8ed5b184a71a35eaf20b0362afa26de28affddcd6a8e5918a72d06c90fc02b50273396a8c9bc605b3d96e1e6da5dd9614758f80a2f7f4db3e0f66e7e0d2a0
data/.travis.yml ADDED
@@ -0,0 +1,4 @@
1
+ rvm:
2
+ - 2.1.1
3
+
4
+ script: 'bundle exec rake spec'
data/README.md CHANGED
@@ -1,4 +1,5 @@
1
1
  # QueryDam
2
+ [![Build Status](https://travis-ci.org/romankuznietsov/query_dam.svg)](https://travis-ci.org/romankuznietsov/query_dam)
2
3
 
3
4
  QueryDam is a tool for observing changes in ActiveRecord relation results.
4
5
 
@@ -10,7 +11,7 @@ Add it to your gemfile Gemfile:
10
11
 
11
12
  ## Usage
12
13
 
13
- First, include `QueryDam::Trackable` in every model you with to track.
14
+ First, include `QueryDam::Trackable` in every model you want to track.
14
15
 
15
16
  ~~~ruby
16
17
  class Item < ActiveRecord::Base
@@ -18,7 +19,7 @@ class Item < ActiveRecord::Base
18
19
  end
19
20
  ~~~
20
21
 
21
- Then start watching updates of relation:
22
+ Then start watching updates of the relation:
22
23
 
23
24
  ~~~ruby
24
25
  relation = Item.where(name: 'pencil')
@@ -27,9 +28,9 @@ tracking_key = QueryDam.watch_relation(relation)
27
28
 
28
29
  To get updates, call `QueryDam.get_updates(tracking_key)`.
29
30
  The result is a hash with 3 keys:
30
- `:model` - the model of the relation,
31
- `:updates` - relation containing records that were updated or entered the scope of the relation
32
- `:exclusions` - array of ids of records that left the scope of the relation
31
+ * `:model` - the model of the relation,
32
+ * `:updates` - relation containing records that were updated or entered the scope of the relation
33
+ * `:exclusions` - array of ids of records that left the scope of the relation
33
34
 
34
35
  The call to `get_updates` clears updates/exclusions.
35
36
 
@@ -47,7 +48,7 @@ QueryDam.get_updates(tracking_key)
47
48
  # :updates=>#<ActiveRecord::Relation []>,
48
49
  # :exclusions=>[]}
49
50
 
50
- Item.first.destory
51
+ Item.first.destroy
51
52
 
52
53
  QueryDam.get_updates(tracking_key)
53
54
  # =>
data/Rakefile CHANGED
@@ -1,2 +1,6 @@
1
- require "bundler/gem_tasks"
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
2
3
 
4
+ task default: :spec
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
@@ -1,51 +1,74 @@
1
+ require 'pry'
2
+
1
3
  module QueryDam
2
4
  module Trackable
3
5
  extend ActiveSupport::Concern
4
6
 
5
7
  included do
6
- before_save :save_previous_query_dam_status
7
- before_destroy :save_previous_query_dam_status
8
- after_commit :update_query_dam
8
+ before_save :get_prior_results
9
+ before_destroy :get_prior_results
10
+ after_commit :update_queries
9
11
  end
10
12
 
11
- def save_previous_query_dam_status
12
- return unless persisted?
13
+ def get_prior_results
14
+ @_prior_results = {}
13
15
 
14
- @_query_dam_previous_status = {}
15
- queries_set = Redis::Set.new(self.class.name)
16
- queries_set.members.each do |key|
17
- query_hash = Redis::HashKey.new(key)
16
+ queries.each do |key, query_hash|
17
+ query = query_hash[:query]
18
18
 
19
- unless query_hash.exists?
20
- queries_set.delete(key)
21
- next
19
+ @_prior_results[key] =
20
+ self.class.connection.select_values(query).map(&:to_i)
21
+ end
22
+ end
23
+
24
+ def update_queries
25
+ queries.each do |key, query_hash|
26
+ query = query_hash[:query]
27
+ result = self.class.connection.select_values(query).map(&:to_i)
28
+ added = result - @_prior_results[key]
29
+ removed = @_prior_results[key] - result
30
+
31
+ if result.include?(id)
32
+ QueryDam.updates_set(key) << id
33
+ end
34
+
35
+ additions_set = QueryDam.additions_set(key)
36
+ updates_set = QueryDam.updates_set(key)
37
+ removals_set = QueryDam.removals_set(key)
38
+
39
+ if result.include?(id)
40
+ updates_set.add(id)
22
41
  end
23
42
 
24
- query = query_hash[:query].gsub(key, id.to_s)
43
+ added.each do |record_id|
44
+ if removals_set.member?(record_id)
45
+ removals_set.delete(record_id)
46
+ else
47
+ additions_set.add(record_id)
48
+ end
49
+ end
25
50
 
26
- @_query_dam_previous_status[key] =
27
- self.class.find_by_sql(query).any?
51
+ removed.each do |record_id|
52
+ updates_set.delete(record_id)
53
+
54
+ if additions_set.member?(record_id)
55
+ additions_set.delete(record_id)
56
+ else
57
+ removals_set.add(record_id)
58
+ end
59
+ end
28
60
  end
29
61
  end
30
62
 
31
- def update_query_dam
63
+ def queries
32
64
  queries_set = Redis::Set.new(self.class.name)
33
- queries_set.members.each do |key|
65
+ queries_set.members.reduce({}) do |acc, key|
34
66
  query_hash = Redis::HashKey.new(key)
35
-
36
- unless query_hash.exists?
67
+ if query_hash.exists?
68
+ acc.merge(key => query_hash)
69
+ else
37
70
  queries_set.delete(key)
38
- next
39
- end
40
-
41
- query = query_hash[:query].gsub(key, id.to_s)
42
-
43
- if self.class.find_by_sql(query).any?
44
- QueryDam.updates_set(key) << id
45
- QueryDam.exclusions_set(key).delete id
46
- elsif @_query_dam_previous_status[key]
47
- QueryDam.updates_set(key).delete id
48
- QueryDam.exclusions_set(key) << id
71
+ acc
49
72
  end
50
73
  end
51
74
  end
@@ -1,3 +1,3 @@
1
1
  module QueryDam
2
- VERSION = '0.1.0'
2
+ VERSION = '0.1.1'
3
3
  end
data/lib/query_dam.rb CHANGED
@@ -12,10 +12,10 @@ module QueryDam
12
12
  class << self
13
13
  def watch_relation(relation)
14
14
  key = SecureRandom.uuid
15
- query = relation.where("`#{relation.model.table_name}`.`id` = #{key}").to_sql
15
+ query = relation.select("`#{relation.model.table_name}`.`id`").to_sql
16
16
  query_hash = Redis::HashKey.new(key)
17
17
  query_hash.bulk_set(model: relation.model.name, query: query)
18
- query_hash.expire(QUERY_EXPIRE)
18
+ # query_hash.expire(QUERY_EXPIRE)
19
19
  Redis::Set.new(relation.model.name) << key
20
20
  key
21
21
  end
@@ -26,23 +26,26 @@ module QueryDam
26
26
 
27
27
  model = query_hash[:model].constantize
28
28
 
29
+ updated_ids = (additions_set(key).members +
30
+ updates_set(key).members).uniq
31
+ removed_ids = removals_set(key).members
32
+
29
33
  result = {
30
34
  model: model,
31
- updates: model.where(id: updates_set(key).to_a),
32
- exclusions: exclusions_set(key).to_a.map(&:to_i) }
35
+ updates: model.where(id: updated_ids),
36
+ exclusions: removed_ids.map(&:to_i) }
33
37
 
38
+ additions_set(key).clear
34
39
  updates_set(key).clear
35
- exclusions_set(key).clear
40
+ removals_set(key).clear
36
41
 
37
42
  result
38
43
  end
39
44
 
40
- def updates_set(key)
41
- Redis::Set.new("#{key}-updates")
42
- end
43
-
44
- def exclusions_set(key)
45
- Redis::Set.new("#{key}-exclusions")
45
+ %w[additions updates removals].each do |set_type|
46
+ define_method "#{set_type}_set" do |key|
47
+ Redis::Set.new("#{key} #{set_type}")
48
+ end
46
49
  end
47
50
  end
48
51
  end
data/query_dam.gemspec CHANGED
@@ -26,4 +26,6 @@ Gem::Specification.new do |spec|
26
26
  spec.add_development_dependency 'rake'
27
27
  spec.add_development_dependency 'rspec', '~> 3.0.0'
28
28
  spec.add_development_dependency 'sqlite3'
29
+ spec.add_development_dependency 'fakeredis'
30
+ spec.add_development_dependency 'pry'
29
31
  end
@@ -1,53 +1,106 @@
1
1
  describe QueryDam do
2
- let!(:relation) { Item.where(name: 'pencil') }
3
- let!(:key) { QueryDam.watch_relation(relation) }
2
+ describe 'simple query' do
3
+ let!(:relation) { Item.where(name: 'pencil') }
4
+ let!(:key) { QueryDam.watch_relation(relation) }
4
5
 
5
- specify 'item enters scope on creation' do
6
- item = Item.create(name: 'pencil')
6
+ specify 'item enters scope on creation' do
7
+ item = Item.create(name: 'pencil')
7
8
 
8
- expect(QueryDam.get_updates(key)).to eq(
9
- {model: Item, updates: [item], exclusions: []})
9
+ expect(QueryDam.get_updates(key)).to eq(
10
+ {model: Item, updates: [item], exclusions: []})
10
11
 
11
- expect(QueryDam.get_updates(key)).to eq(
12
- {model: Item, updates: [], exclusions: []})
13
- end
12
+ expect(QueryDam.get_updates(key)).to eq(
13
+ {model: Item, updates: [], exclusions: []})
14
+ end
14
15
 
15
- specify 'item enters scope on update' do
16
- item = Item.create
16
+ specify 'item enters scope on update' do
17
+ item = Item.create
17
18
 
18
- expect(QueryDam.get_updates(key)).to eq(
19
- {model: Item, updates: [], exclusions: []})
19
+ expect(QueryDam.get_updates(key)).to eq(
20
+ {model: Item, updates: [], exclusions: []})
20
21
 
21
- item.update_attributes!(name: 'pencil')
22
+ item.update_attributes!(name: 'pencil')
22
23
 
23
- expect(QueryDam.get_updates(key)).to eq(
24
- {model: Item, updates: [item], exclusions: []})
25
- end
24
+ expect(QueryDam.get_updates(key)).to eq(
25
+ {model: Item, updates: [item], exclusions: []})
26
+ end
26
27
 
27
- specify 'item leaves scope on update' do
28
- item = Item.create(name: 'pencil')
29
- QueryDam.get_updates(key)
28
+ specify 'item leaves scope on update' do
29
+ item = Item.create(name: 'pencil')
30
+ QueryDam.get_updates(key)
30
31
 
31
- item.update_attributes!(name: 'pen')
32
- expect(QueryDam.get_updates(key)).to eq(
33
- {model: Item, updates: [], exclusions: [item.id]})
34
- end
32
+ item.update_attributes!(name: 'pen')
33
+ expect(QueryDam.get_updates(key)).to eq(
34
+ {model: Item, updates: [], exclusions: [item.id]})
35
+ end
35
36
 
36
- specify 'item leaves scope on deletion' do
37
- item = Item.create(name: 'pencil')
38
- QueryDam.get_updates(key)
37
+ specify 'item leaves scope on deletion' do
38
+ item = Item.create(name: 'pencil')
39
+ QueryDam.get_updates(key)
39
40
 
40
- item.destroy
41
- expect(QueryDam.get_updates(key)).to eq(
42
- {model: Item, updates: [], exclusions: [item.id]})
41
+ item.destroy
42
+ expect(QueryDam.get_updates(key)).to eq(
43
+ {model: Item, updates: [], exclusions: [item.id]})
44
+ end
45
+
46
+ specify 'items gets updated' do
47
+ item = Item.create(name: 'pencil')
48
+ QueryDam.get_updates(key)
49
+
50
+ item.update_attributes!(quantity: 1)
51
+ expect(QueryDam.get_updates(key)).to eq(
52
+ {model: Item, updates: [item], exclusions: []})
53
+ end
43
54
  end
44
55
 
45
- specify 'items gets updated' do
46
- item = Item.create(name: 'pencil')
47
- QueryDam.get_updates(key)
56
+ describe 'query with limit/offset' do
57
+ let!(:item_a) { Item.create!(name: 'a') }
58
+ let!(:item_b) { Item.create!(name: 'b') }
59
+ let!(:item_c) { Item.create!(name: 'c') }
60
+ let!(:item_d) { Item.create!(name: 'd') }
61
+
62
+ let!(:relation) { Item.order('name asc').limit(2).offset(2) }
63
+ let!(:key) { QueryDam.watch_relation(relation) }
64
+
65
+ specify 'item is not on page, but shifts it' do
66
+ Item.create!(name: 'a1')
67
+
68
+ expect(QueryDam.get_updates(key)).to eq (
69
+ {model: Item, updates: [item_b], exclusions: [item_d.id]})
70
+ end
71
+
72
+ specify 'item appears on page' do
73
+ item_b1 = Item.create(name: 'b1')
74
+ expect(QueryDam.get_updates(key)).to eq (
75
+ {model: Item, updates: [item_b1], exclusions: [item_d.id]})
76
+ end
77
+
78
+ specify 'item on page is updated' do
79
+ item_c.update_attributes(quantity: 1)
80
+ expect(QueryDam.get_updates(key)).to eq (
81
+ {model: Item, updates: [item_c], exclusions: []})
82
+ end
83
+
84
+ specify 'two updates lead to no changes' do
85
+ item_c1 = Item.create(name: 'c1')
86
+ item_c1.destroy
87
+ expect(QueryDam.get_updates(key)).to eq (
88
+ {model: Item, updates: [], exclusions: []})
89
+ end
90
+
91
+ specify 'item is updated and removed' do
92
+ item_d.update_attributes!(quantity: 1)
93
+ item_d.destroy
94
+ expect(QueryDam.get_updates(key)).to eq (
95
+ {model: Item, updates: [], exclusions: [item_d.id]})
96
+ end
48
97
 
49
- item.update_attributes!(quantity: 1)
50
- expect(QueryDam.get_updates(key)).to eq(
51
- {model: Item, updates: [item], exclusions: []})
98
+ specify 'item is added, updated and removed' do
99
+ item_c1 = Item.create(name: 'c1')
100
+ item_c1.update_attributes(quantity: 1)
101
+ item_c1.destroy
102
+ expect(QueryDam.get_updates(key)).to eq (
103
+ {model: Item, updates: [], exclusions: []})
104
+ end
52
105
  end
53
106
  end
data/spec/spec_helper.rb CHANGED
@@ -1,6 +1,7 @@
1
1
  require 'query_dam'
2
2
  require 'active_record'
3
3
  require 'models/item'
4
+ require 'fakeredis/rspec'
4
5
 
5
6
  RSpec.configure do |config|
6
7
  config.order = :random
@@ -13,6 +14,10 @@ RSpec.configure do |config|
13
14
  mocks.syntax = :expect
14
15
  mocks.verify_partial_doubles = true
15
16
  end
17
+
18
+ config.after(:each) do
19
+ Item.destroy_all
20
+ end
16
21
  end
17
22
 
18
23
  ActiveRecord::Base.establish_connection(
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: query_dam
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Roman Kuznietsov
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-08-27 00:00:00.000000000 Z
11
+ date: 2014-09-04 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -108,6 +108,34 @@ dependencies:
108
108
  - - '>='
109
109
  - !ruby/object:Gem::Version
110
110
  version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: fakeredis
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - '>='
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - '>='
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: pry
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - '>='
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - '>='
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
111
139
  description: ''
112
140
  email:
113
141
  - roman.kuznietsov@gmail.com
@@ -117,6 +145,7 @@ extra_rdoc_files: []
117
145
  files:
118
146
  - .gitignore
119
147
  - .rspec
148
+ - .travis.yml
120
149
  - Gemfile
121
150
  - LICENSE.txt
122
151
  - README.md