queryalize 0.0.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.
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
+