query_dam 0.1.0 → 0.1.1

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