query_dam 0.0.1 → 0.1.0

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