query_dam 0.0.1 → 0.1.0

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: 5a4fd20c5b7ba8e610fd9757b7d1df78eed85b76
4
- data.tar.gz: 7ed9a41bc2ffcb516c11753882d0a5de6032aab2
3
+ metadata.gz: 0612b6f926d6c396e8a7be1e4a8c5c6ab2a888d7
4
+ data.tar.gz: a8973faabf07ffeb5afeb3540b1def06187ca51a
5
5
  SHA512:
6
- metadata.gz: 7129aa97a1b6618a0c394f26a04b1c1868722e0a118138531ad9fa4607ec01774c81d40003610d25840ebe2dd832faf1c35958698ce60b5d00ca797e429779a5
7
- data.tar.gz: b97ac6cc7920020935cee268af83f1b1e37e047754ae29355619831412ea3673ab065a0c24fac7aeb9d1c81dc931fdbcf00df61ff4ab49640646799929a824df
6
+ metadata.gz: 44d012c14b7e738279bd545156efd54a93d65dcc965031223c4fe73b4ff9cd8da66d7aa19ddc311901a67950a544f8e951b2b0af6eb2fd4c15ac526f97655a5c
7
+ data.tar.gz: a294b14782c0b07cb595061b511407aada190ee135e065a5dd6a54c4d6c097bcec10d18fa440a4be906cbf82ed28e92ca6a5c9625e9f1d2f93669bd7f7f73d18
data/.gitignore CHANGED
@@ -20,3 +20,4 @@ tmp
20
20
  *.o
21
21
  *.a
22
22
  mkmf.log
23
+ *.sqlite3
data/.rspec CHANGED
@@ -1,3 +1,2 @@
1
1
  --color
2
- --warnings
3
2
  --require spec_helper
data/README.md CHANGED
@@ -1,24 +1,60 @@
1
1
  # QueryDam
2
2
 
3
- TODO: Write a gem description
3
+ QueryDam is a tool for observing changes in ActiveRecord relation results.
4
4
 
5
5
  ## Installation
6
6
 
7
- Add this line to your application's Gemfile:
7
+ Add it to your gemfile Gemfile:
8
8
 
9
9
  gem 'query_dam'
10
10
 
11
- And then execute:
11
+ ## Usage
12
12
 
13
- $ bundle
13
+ First, include `QueryDam::Trackable` in every model you with to track.
14
14
 
15
- Or install it yourself as:
15
+ ~~~ruby
16
+ class Item < ActiveRecord::Base
17
+ include QueryDam::Trackable
18
+ end
19
+ ~~~
16
20
 
17
- $ gem install query_dam
21
+ Then start watching updates of relation:
18
22
 
19
- ## Usage
23
+ ~~~ruby
24
+ relation = Item.where(name: 'pencil')
25
+ tracking_key = QueryDam.watch_relation(relation)
26
+ ~~~
27
+
28
+ To get updates, call `QueryDam.get_updates(tracking_key)`.
29
+ 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
33
+
34
+ The call to `get_updates` clears updates/exclusions.
35
+
36
+ ~~~ruby
37
+ Item.create(name: 'pencil')
38
+ QueryDam.get_updates(tracking_key)
39
+ # =>
40
+ # {:model=>Item,
41
+ # :updates=>#<ActiveRecord::Relation [#<Item id: 1, name: "pencil">]>,
42
+ # :exclusions=>[]}
43
+
44
+ QueryDam.get_updates(tracking_key)
45
+ # =>
46
+ # {:model=>Item,
47
+ # :updates=>#<ActiveRecord::Relation []>,
48
+ # :exclusions=>[]}
49
+
50
+ Item.first.destory
20
51
 
21
- TODO: Write usage instructions here
52
+ QueryDam.get_updates(tracking_key)
53
+ # =>
54
+ # {:model=>Item,
55
+ # :updates=>#<ActiveRecord::Relation []>,
56
+ # :exclusions=>[1]}
57
+ ~~~
22
58
 
23
59
  ## Contributing
24
60
 
data/lib/query_dam.rb CHANGED
@@ -1,9 +1,48 @@
1
1
  require 'securerandom'
2
- require 'active_support/dependencies'
2
+ require 'active_support/core_ext'
3
+ require 'redis-objects'
3
4
 
4
5
  require 'query_dam/version'
6
+ require 'query_dam/concerns/trackable'
5
7
 
6
8
  module QueryDam
7
- autoload :Trackable, 'query_dam/concerns/trackable'
8
- autoload :Cacheable, 'query_dam/concerns/cacheable'
9
+
10
+ QUERY_EXPIRE = 5.minutes
11
+
12
+ class << self
13
+ def watch_relation(relation)
14
+ key = SecureRandom.uuid
15
+ query = relation.where("`#{relation.model.table_name}`.`id` = #{key}").to_sql
16
+ query_hash = Redis::HashKey.new(key)
17
+ query_hash.bulk_set(model: relation.model.name, query: query)
18
+ query_hash.expire(QUERY_EXPIRE)
19
+ Redis::Set.new(relation.model.name) << key
20
+ key
21
+ end
22
+
23
+ def get_updates(key)
24
+ query_hash = Redis::HashKey.new(key)
25
+ return nil unless query_hash.exists?
26
+
27
+ model = query_hash[:model].constantize
28
+
29
+ result = {
30
+ model: model,
31
+ updates: model.where(id: updates_set(key).to_a),
32
+ exclusions: exclusions_set(key).to_a.map(&:to_i) }
33
+
34
+ updates_set(key).clear
35
+ exclusions_set(key).clear
36
+
37
+ result
38
+ end
39
+
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")
46
+ end
47
+ end
9
48
  end
@@ -3,23 +3,49 @@ module QueryDam
3
3
  extend ActiveSupport::Concern
4
4
 
5
5
  included do
6
- after_save :update_cached_queries
7
- after_destroy :update_cached_queries
6
+ before_save :save_previous_query_dam_status
7
+ before_destroy :save_previous_query_dam_status
8
+ after_commit :update_query_dam
8
9
  end
9
10
 
10
- def update_cached_queries
11
- Redis::Set.new(self.class.name).members.each do |query_id|
12
- sql = Redis::HashKey.new(query_id)[:sql].gsub(query_id, id.to_s)
11
+ def save_previous_query_dam_status
12
+ return unless persisted?
13
13
 
14
- updated_set = Redis::Set.new("#{query_id}_updated")
15
- excluded_set = Redis::Set.new("#{query_id}_excluded")
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
18
 
17
- if self.class.find_by_sql(sql).any?
18
- updated_set << id
19
- excluded_set.delete id
20
- else
21
- updated_set.delete id
22
- excluded_set << id
19
+ unless query_hash.exists?
20
+ queries_set.delete(key)
21
+ next
22
+ end
23
+
24
+ query = query_hash[:query].gsub(key, id.to_s)
25
+
26
+ @_query_dam_previous_status[key] =
27
+ self.class.find_by_sql(query).any?
28
+ end
29
+ end
30
+
31
+ def update_query_dam
32
+ queries_set = Redis::Set.new(self.class.name)
33
+ queries_set.members.each do |key|
34
+ query_hash = Redis::HashKey.new(key)
35
+
36
+ unless query_hash.exists?
37
+ 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
23
49
  end
24
50
  end
25
51
  end
@@ -1,3 +1,3 @@
1
1
  module QueryDam
2
- VERSION = '0.0.1'
2
+ VERSION = '0.1.0'
3
3
  end
data/query_dam.gemspec CHANGED
@@ -20,9 +20,9 @@ Gem::Specification.new do |spec|
20
20
 
21
21
  spec.add_dependency 'redis', '>= 3.1.0'
22
22
  spec.add_dependency 'redis-objects', '>= 0.8.0'
23
- spec.add_dependency 'activemodel', '>= 4.0.0'
23
+ spec.add_dependency 'activerecord', '>= 4.0.0'
24
24
 
25
- spec.add_development_dependency 'bundler', "~> 1.6"
25
+ spec.add_development_dependency 'bundler', '~> 1.6'
26
26
  spec.add_development_dependency 'rake'
27
27
  spec.add_development_dependency 'rspec', '~> 3.0.0'
28
28
  spec.add_development_dependency 'sqlite3'
@@ -0,0 +1,3 @@
1
+ class Item < ActiveRecord::Base
2
+ include QueryDam::Trackable
3
+ end
@@ -0,0 +1,53 @@
1
+ describe QueryDam do
2
+ let!(:relation) { Item.where(name: 'pencil') }
3
+ let!(:key) { QueryDam.watch_relation(relation) }
4
+
5
+ specify 'item enters scope on creation' do
6
+ item = Item.create(name: 'pencil')
7
+
8
+ expect(QueryDam.get_updates(key)).to eq(
9
+ {model: Item, updates: [item], exclusions: []})
10
+
11
+ expect(QueryDam.get_updates(key)).to eq(
12
+ {model: Item, updates: [], exclusions: []})
13
+ end
14
+
15
+ specify 'item enters scope on update' do
16
+ item = Item.create
17
+
18
+ expect(QueryDam.get_updates(key)).to eq(
19
+ {model: Item, updates: [], exclusions: []})
20
+
21
+ item.update_attributes!(name: 'pencil')
22
+
23
+ expect(QueryDam.get_updates(key)).to eq(
24
+ {model: Item, updates: [item], exclusions: []})
25
+ end
26
+
27
+ specify 'item leaves scope on update' do
28
+ item = Item.create(name: 'pencil')
29
+ QueryDam.get_updates(key)
30
+
31
+ item.update_attributes!(name: 'pen')
32
+ expect(QueryDam.get_updates(key)).to eq(
33
+ {model: Item, updates: [], exclusions: [item.id]})
34
+ end
35
+
36
+ specify 'item leaves scope on deletion' do
37
+ item = Item.create(name: 'pencil')
38
+ QueryDam.get_updates(key)
39
+
40
+ item.destroy
41
+ expect(QueryDam.get_updates(key)).to eq(
42
+ {model: Item, updates: [], exclusions: [item.id]})
43
+ end
44
+
45
+ specify 'items gets updated' do
46
+ item = Item.create(name: 'pencil')
47
+ QueryDam.get_updates(key)
48
+
49
+ item.update_attributes!(quantity: 1)
50
+ expect(QueryDam.get_updates(key)).to eq(
51
+ {model: Item, updates: [item], exclusions: []})
52
+ end
53
+ end
data/spec/spec_helper.rb CHANGED
@@ -1,3 +1,7 @@
1
+ require 'query_dam'
2
+ require 'active_record'
3
+ require 'models/item'
4
+
1
5
  RSpec.configure do |config|
2
6
  config.order = :random
3
7
 
@@ -10,3 +14,17 @@ RSpec.configure do |config|
10
14
  mocks.verify_partial_doubles = true
11
15
  end
12
16
  end
17
+
18
+ ActiveRecord::Base.establish_connection(
19
+ adapter: 'sqlite3',
20
+ database: 'spec/testdb.sqlite3')
21
+
22
+ ActiveRecord::Base.connection.execute 'drop table if exists items'
23
+ ActiveRecord::Base.connection.execute <<-SQL
24
+ create table items (
25
+ id integer primary key,
26
+ name varchar(255),
27
+ quantity integer)
28
+ SQL
29
+
30
+ Redis.current = Redis.new(host: '127.0.0.1', port: 6379)
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.0.1
4
+ version: 0.1.0
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-22 00:00:00.000000000 Z
11
+ date: 2014-08-27 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: redis
@@ -39,7 +39,7 @@ dependencies:
39
39
  - !ruby/object:Gem::Version
40
40
  version: 0.8.0
41
41
  - !ruby/object:Gem::Dependency
42
- name: activemodel
42
+ name: activerecord
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - '>='
@@ -122,10 +122,11 @@ files:
122
122
  - README.md
123
123
  - Rakefile
124
124
  - lib/query_dam.rb
125
- - lib/query_dam/concerns/cacheable.rb
126
125
  - lib/query_dam/concerns/trackable.rb
127
126
  - lib/query_dam/version.rb
128
127
  - query_dam.gemspec
128
+ - spec/models/item.rb
129
+ - spec/query_dam_spec.rb
129
130
  - spec/spec_helper.rb
130
131
  homepage: https://github.com/romankuznietsov/query_dam
131
132
  licenses:
@@ -152,4 +153,6 @@ signing_key:
152
153
  specification_version: 4
153
154
  summary: Detecting query result changes
154
155
  test_files:
156
+ - spec/models/item.rb
157
+ - spec/query_dam_spec.rb
155
158
  - spec/spec_helper.rb
@@ -1,34 +0,0 @@
1
- module QueryDam
2
- module Cacheable
3
- extend ActiveSupport::Concern
4
-
5
- def cache_query(relation)
6
- query_id = SecureRandom.uuid
7
- sql = relation.where("`#{relation.model.table_name}`.`id` = #{query_id}").to_sql
8
- Redis::HashKey.new(query_id).bulk_set(sql: sql, model: relation.model.name)
9
- Redis::Set.new(relation.model.name) << query_id
10
- query_id
11
- end
12
-
13
- def query_updates(query_ids)
14
- query_ids.map do |query_id|
15
- model = Redis::HashKey.new(query_id)[:model].constantize
16
- updated_set = Redis::Set.new("#{query_id}_updated")
17
- excluded_set = Redis::Set.new("#{query_id}_excluded")
18
-
19
- updated_ids = updated_set.to_a
20
- excluded_ids = excluded_set.to_a
21
-
22
- updated_set.clear
23
- excluded_set.clear
24
-
25
- updated_records = model.where(id: updated_ids)
26
- updated_records = yield(updated_records) if block_given?
27
-
28
- {query_id: query_id,
29
- updated: updated_records,
30
- excluded: excluded_ids}
31
- end
32
- end
33
- end
34
- end