queryalize 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .rvmrc
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in queryalize.gemspec
4
+ gemspec
data/MIT-LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (C) 2011 by Peter Brindisi, frestyl
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ THE SOFTWARE.
data/README.markdown ADDED
@@ -0,0 +1,85 @@
1
+ **Queryalize** lets you use Rails 3 to build queries just like with `ActiveRecord::QueryMethods`,
2
+ except you can serialize the end result. This is useful for running queries that potentially
3
+ return large result sets in the background using something like Resque or Delayed::Job.
4
+
5
+ Normally, using `ActiveRecord::QueryMethods`, you build queries like this:
6
+
7
+ query = User.where(:name => "something").order("created_at DESC")
8
+
9
+ With **Queryalize**, it's only a little different:
10
+
11
+ query = Querialize.new(User).where(:name => "something").order("created_at DESC")
12
+
13
+ However, now you get all of this goodness:
14
+
15
+ # NOTE the following methods DO NOT query the database,
16
+ # they return a representation of the query itself in one
17
+ # of the following formats
18
+
19
+ json = query.to_json # => query as json data
20
+ yaml = query.to_yaml # => query as yaml data
21
+ hash = query.to_hash # => query as ruby hash
22
+
23
+ new_query_from_json = Queryalize.from_json(json)
24
+ new_query_from_yaml = Queryalize.from_yaml(yaml)
25
+ new_query_from_hash = Queryalize.from_hash(hash)
26
+
27
+ # Why?
28
+
29
+ Imagine, for example, that you have a database that organizes music into several genres.
30
+ You have built an admin interface that allows the administrator to filter the catalog of
31
+ music by genre, and run updates against the result set. However, the database is large,
32
+ and the query for "electronica" returns 1,000,000+ results. The administrator wants to
33
+ re-process these entries such that the genre is "electronic" (without the annoying 'a'
34
+ at the end).
35
+
36
+ Unfortunately, your schema is setup in such a way that you cannot simply run a single
37
+ "UPDATE." Rather, you must iterate through each individual record and update its genre.
38
+ Ouch. There is no way you can allow this to happen during the request, or it will certainly
39
+ timeout. So you decide to queue the update, but how do you tell the queue workers which
40
+ records to update? You could try to capture just the ids from the records, but you'd still
41
+ need to store 1,000,000+ ids somewhere so the queue worker can reference them later, not to
42
+ mention that actually collecting the ids takes a healthy amount of time and memory, and
43
+ will probably also time out. You could build up your query and then use `to_sql` to pass
44
+ the raw SQL to the queue worker, but then you can't use useful methods like 'find_each' in
45
+ the queue task.
46
+
47
+ The solution is to serialize the query you've built, and then rebuild it in the queue task.
48
+ It ends up looking something like this (if you're using Delayed::Job):
49
+
50
+ query = Queryalize.new(Music).joins("JOIN #{Genre.table_name} ON #{Genre.table_name}.music_id = #{Music.table_name}.id").where(["#{Genre.table_name}.name = ?", 'electronica'])
51
+ # see 1. below
52
+
53
+ worker = GenreWorker.new({
54
+ :update => 'electronic',
55
+ :query => query.to_json
56
+ })
57
+
58
+ Delayed::Job.enqueue(worker)
59
+
60
+ # 1.
61
+ # written this way to demonstrate chaining, but a slightly cleaner way would be:
62
+ # genres = Genre.table_name
63
+ # query = Queryalize.new(Music)
64
+ # query = query.joins("JOIN #{genres} ON #{genres}.music_id = #{genres}.id")
65
+ # query = query.where(["#{genres}.name = ?", 'electronica'])
66
+
67
+ The `GenreWorker` class looks something like this:
68
+
69
+ class GenreWorker
70
+
71
+ def initialize(args)
72
+ @update = args[:update]
73
+ @query = args[:query]
74
+ end
75
+
76
+ def perform
77
+ Queryalize.from_json(@query).find_each do |music|
78
+ music.genre.update_attribute(:name => @update)
79
+ end
80
+ end
81
+ end
82
+ end
83
+
84
+ Notice the query was serialized and reconstructed to its original state, so you
85
+ can seamlessly use ActiveRecord features like `find_each`. Simple!
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
@@ -0,0 +1,106 @@
1
+ module Queryalize
2
+
3
+ class SerializableQuery
4
+
5
+ attr_reader :chain_methods
6
+
7
+ def self.deserialize(data, mode = :json)
8
+
9
+ data = parse(data, mode)
10
+ klass = data[:class].constantize
11
+ chain_methods = data[:chain_methods]
12
+
13
+ scope = klass
14
+ chain_methods.each do |method, args|
15
+ scope = scope.send(method, *args)
16
+ end
17
+
18
+ new(klass, scope, chain_methods)
19
+ end
20
+
21
+ def self.from_hash(hash)
22
+ deserialize(hash, :hash)
23
+ end
24
+
25
+ def self.from_json(json)
26
+ deserialize(json, :json)
27
+ end
28
+
29
+ def self.from_yaml(yaml)
30
+ deserialize(yaml, :yaml)
31
+ end
32
+
33
+ class << self
34
+ alias_method :_load, :from_json
35
+ end
36
+
37
+ def initialize(klass, scope = nil, chain_methods = { })
38
+ @klass = klass
39
+ if scope
40
+ @scope = scope
41
+ else
42
+ @scope = klass
43
+ end
44
+
45
+ @chain_methods = chain_methods
46
+ end
47
+
48
+ def serialize(mode = :json)
49
+ send("to_#{mode}")
50
+ end
51
+
52
+ def to_hash
53
+ { :class => @klass.name, :chain_methods => chain_methods }
54
+ end
55
+
56
+ def to_json
57
+ to_hash.to_json
58
+ end
59
+
60
+ def to_yaml(opts = { })
61
+ to_hash.to_yaml(opts)
62
+ end
63
+
64
+ def _dump(depth)
65
+ to_json
66
+ end
67
+
68
+ def query_method?(name)
69
+ ActiveRecord::QueryMethods.public_instance_methods.include?(name.to_s)
70
+ end
71
+
72
+ def chain(name, *args)
73
+ self.class.new(@klass, @scope.send(name, *args), @chain_methods.merge(name => args))
74
+ end
75
+
76
+ def method_missing(name, *args, &block)
77
+ if query_method?(name)
78
+ chain(name, *args)
79
+
80
+ elsif @scope.respond_to?(name)
81
+ @scope.send(name, *args, &block)
82
+
83
+ else
84
+ super(name, *args, &block)
85
+ end
86
+ end
87
+
88
+ def inspect
89
+ if @chain_methods.empty?
90
+ @klass.name
91
+ else
92
+ @klass.name + "." + @chain_methods.collect { |method, args| "#{method}(#{args.collect(&:inspect).join(", ")})" }.join(".")
93
+ end
94
+ end
95
+
96
+ private
97
+
98
+ def self.parse(data, mode)
99
+ case mode
100
+ when :json then data = JSON::parse(data)
101
+ when :yaml then data = YAML.load(data)
102
+ end
103
+ data.symbolize_keys
104
+ end
105
+ end
106
+ end
@@ -0,0 +1,3 @@
1
+ module Queryalize
2
+ VERSION = "0.0.1"
3
+ end
data/lib/queryalize.rb ADDED
@@ -0,0 +1,19 @@
1
+ require 'queryalize/serializable_query'
2
+
3
+ module Queryalize
4
+ def self.new(klass)
5
+ SerializableQuery.new(klass)
6
+ end
7
+
8
+ def self.from_hash(hash)
9
+ SerializableQuery.from_hash(hash)
10
+ end
11
+
12
+ def self.from_json(json)
13
+ SerializableQuery.from_json(json)
14
+ end
15
+
16
+ def self.from_yaml(yaml)
17
+ SerializableQuery.from_yaml(yaml)
18
+ end
19
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "queryalize/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "queryalize"
7
+ s.version = Queryalize::VERSION
8
+ s.platform = Gem::Platform::RUBY
9
+ s.authors = ["Peter Brindisi", "frestyl"]
10
+ s.email = ['npj@frestyl.com', 'info@frestyl.com']
11
+ s.license = 'MIT'
12
+ s.homepage = 'http://github.com/frestyl/queryalize'
13
+ s.summary = %q{Serialize chainable queries constructed with ActiveRecord::QueryMethods}
14
+ s.description = %q{
15
+ Queryalize lets you use Rails 3 to build queries just like with ActiveRecord::QueryMethods,
16
+ except you can serialize the end result. This is useful for running queries that potentially
17
+ return large result sets in the background using something like Resque or Delayed::Job.
18
+ }
19
+
20
+ s.rubyforge_project = "queryalize"
21
+
22
+ s.files = `git ls-files`.split("\n")
23
+ s.test_files = `git ls-files -- {spec}/*`.split("\n")
24
+ s.require_paths = ["lib"]
25
+
26
+ s.add_dependency('activerecord', [">= 3.0.0"])
27
+ end
metadata ADDED
@@ -0,0 +1,93 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: queryalize
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Peter Brindisi
14
+ - frestyl
15
+ autorequire:
16
+ bindir: bin
17
+ cert_chain: []
18
+
19
+ date: 2011-05-26 00:00:00 +02:00
20
+ default_executable:
21
+ dependencies:
22
+ - !ruby/object:Gem::Dependency
23
+ name: activerecord
24
+ prerelease: false
25
+ requirement: &id001 !ruby/object:Gem::Requirement
26
+ none: false
27
+ requirements:
28
+ - - ">="
29
+ - !ruby/object:Gem::Version
30
+ hash: 7
31
+ segments:
32
+ - 3
33
+ - 0
34
+ - 0
35
+ version: 3.0.0
36
+ type: :runtime
37
+ version_requirements: *id001
38
+ description: "\n Queryalize lets you use Rails 3 to build queries just like with ActiveRecord::QueryMethods,\n except you can serialize the end result. This is useful for running queries that potentially\n return large result sets in the background using something like Resque or Delayed::Job.\n "
39
+ email:
40
+ - npj@frestyl.com
41
+ - info@frestyl.com
42
+ executables: []
43
+
44
+ extensions: []
45
+
46
+ extra_rdoc_files: []
47
+
48
+ files:
49
+ - .gitignore
50
+ - Gemfile
51
+ - MIT-LICENSE
52
+ - README.markdown
53
+ - Rakefile
54
+ - lib/queryalize.rb
55
+ - lib/queryalize/serializable_query.rb
56
+ - lib/queryalize/version.rb
57
+ - queryalize.gemspec
58
+ has_rdoc: true
59
+ homepage: http://github.com/frestyl/queryalize
60
+ licenses:
61
+ - MIT
62
+ post_install_message:
63
+ rdoc_options: []
64
+
65
+ require_paths:
66
+ - lib
67
+ required_ruby_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 3
73
+ segments:
74
+ - 0
75
+ version: "0"
76
+ required_rubygems_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ hash: 3
82
+ segments:
83
+ - 0
84
+ version: "0"
85
+ requirements: []
86
+
87
+ rubyforge_project: queryalize
88
+ rubygems_version: 1.5.2
89
+ signing_key:
90
+ specification_version: 3
91
+ summary: Serialize chainable queries constructed with ActiveRecord::QueryMethods
92
+ test_files: []
93
+