activerecord-postgresql-cursors 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/MIT-LICENSE ADDED
@@ -0,0 +1,23 @@
1
+ Copyright (c) 2011 2167961 Ontario Inc., Zoocasa <code@zoocasa.com>
2
+
3
+ Permission is hereby granted, free of charge, to any person
4
+ obtaining a copy of this software and associated documentation
5
+ files (the "Software"), to deal in the Software without
6
+ restriction, including without limitation the rights to use,
7
+ copy, modify, merge, publish, distribute, sublicense, and/or sell
8
+ copies of the Software, and to permit persons to whom the
9
+ Software is furnished to do so, subject to the following
10
+ conditions:
11
+
12
+ The above copyright notice and this permission notice shall be
13
+ included in all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19
+ HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20
+ WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21
+ FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
+ OTHER DEALINGS IN THE SOFTWARE.
23
+
data/README.rdoc ADDED
@@ -0,0 +1,52 @@
1
+ == ActiveRecord PostgreSQL Cursors
2
+
3
+ This extension allows you to loop through record sets using cursors in an
4
+ Enumerable fashion. This allows you to cut down memory usage by only pulling
5
+ in individual records on each loop rather than pulling everything into
6
+ memory all at once.
7
+
8
+ To use a cursor, just change the first parameter to an
9
+ ActiveRecord::Base.find to :cursor instead of :first or :all or
10
+ whatever or use the ActiveRecord::Base.cursor method directly.
11
+
12
+ MyModel.find(:cursor, :conditions => 'some_column = true').each do |r|
13
+ puts r.inspect
14
+ end
15
+
16
+ MyModel.find(:cursor).collect { |r| r.foo / PI }.avg
17
+
18
+ MyModel.cursor.each do |r|
19
+ puts r.inspect
20
+ end
21
+
22
+ All ActiveRecord::Base.find options are available and should work as-is.
23
+ As a bonus, the PostgreSQLCursor object returned includes Enumerable,
24
+ so you can iterate to your heart's content.
25
+
26
+ This extension should work in both Rails 2.3 as well as Rails 3.
27
+
28
+ At the moment, this is a non-scrollable cursor -- it will only fetch
29
+ forward. Also note that these cursors are non-updateable/insensitive to
30
+ updates to the underlying data. You can write to the records themselves,
31
+ but changes outside of the cursor's transaction will not affect the
32
+ data being retrieved from the point of the cursor's creation, or rather
33
+ more specifically from the time you begin iterating.
34
+
35
+ The cursor itself is wrapped in a transaction as is required by PostgreSQL
36
+ and the cursor name is automatically generated using random numbers or
37
+ a name supplied during cursor creation. On raised SQL exceptions, the
38
+ transaction is ABORTed and the cursor CLOSEd.
39
+
40
+ Associations are handled, so you can use :include in your find options. Of
41
+ course, this requires some nonsense when moving the cursor around, but it
42
+ works all the same. In some cases, pre-loading and eager loading of
43
+ associations and whatnot creates an initial query that will grab the initial
44
+ IDs of the model being fetched and then create the actual cursor query out
45
+ of those large joins that ActiveRecord sometimes generates. This larger
46
+ query will be the query that's wrapped in the transaction and in a cursor,
47
+ while the first query is just used to build the larger joined query. This
48
+ allows for a brief window between the point that the cursor query is
49
+ created and the time it is executed. In these cases, it may be wise to wrap
50
+ your use or cursors in your own transaction to ensure that changes made to
51
+ the underlying data don't interfere with your cursor's visbility.
52
+
data/Rakefile ADDED
@@ -0,0 +1,42 @@
1
+
2
+ # -*- ruby -*-
3
+
4
+ require 'rubygems'
5
+ require 'rubygems/package_task'
6
+ require 'rake/testtask'
7
+ require 'rdoc/task'
8
+
9
+ $:.push 'lib'
10
+
11
+ version = File.read('VERSION') rescue ''
12
+
13
+ begin
14
+ require 'jeweler'
15
+ Jeweler::Tasks.new do |gem|
16
+ gem.name = "activerecord-postgresql-cursors"
17
+ gem.summary = "Provides some support for PostgreSQL cursors in ActiveRecord."
18
+ gem.description = gem.summary
19
+ gem.email = "code@zoocasa.com"
20
+ gem.homepage = "http://github.com/zoocasa/activerecord-postgresql-cursors"
21
+ gem.authors = [ "J Smith" ]
22
+ end
23
+ Jeweler::GemcutterTasks.new
24
+ rescue LoadError
25
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
26
+ end
27
+
28
+ desc 'Test PostgreSQL extensions'
29
+ Rake::TestTask.new(:test) do |t|
30
+ t.pattern = 'test/**/*_test.rb'
31
+ t.verbose = false
32
+ end
33
+
34
+ desc 'Build docs'
35
+ Rake::RDocTask.new do |t|
36
+ require 'rdoc'
37
+ t.title = "ActiveRecord PostgreSQL Cursors #{version}"
38
+ t.main = 'README.rdoc'
39
+ t.rdoc_dir = 'doc'
40
+ t.rdoc_files.include('README.rdoc', 'MIT-LICENSE', 'lib/**/*.rb')
41
+ end
42
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.1
@@ -0,0 +1,45 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{activerecord-postgresql-cursors}
8
+ s.version = "0.0.1"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = [%q{J Smith}]
12
+ s.date = %q{2011-07-11}
13
+ s.description = %q{Provides some support for PostgreSQL cursors in ActiveRecord.}
14
+ s.email = %q{code@zoocasa.com}
15
+ s.extra_rdoc_files = [
16
+ "README.rdoc"
17
+ ]
18
+ s.files = [
19
+ "MIT-LICENSE",
20
+ "README.rdoc",
21
+ "Rakefile",
22
+ "VERSION",
23
+ "activerecord-postgresql-cursors.gemspec",
24
+ "init.rb",
25
+ "lib/activerecord-postgresql-cursors.rb",
26
+ "lib/postgresql_cursors_2.rb",
27
+ "lib/postgresql_cursors_3.rb",
28
+ "test/cursor_test.rb",
29
+ "test/test_helper.rb"
30
+ ]
31
+ s.homepage = %q{http://github.com/zoocasa/activerecord-postgresql-cursors}
32
+ s.require_paths = [%q{lib}]
33
+ s.rubygems_version = %q{1.8.5}
34
+ s.summary = %q{Provides some support for PostgreSQL cursors in ActiveRecord.}
35
+
36
+ if s.respond_to? :specification_version then
37
+ s.specification_version = 3
38
+
39
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
40
+ else
41
+ end
42
+ else
43
+ end
44
+ end
45
+
data/init.rb ADDED
@@ -0,0 +1,2 @@
1
+
2
+ require File.join(File.dirname(__FILE__), 'lib', 'postgresql_cursors')
@@ -0,0 +1,109 @@
1
+
2
+ module ActiveRecord
3
+ # Exception raised when database cursors aren't supported, which they
4
+ # absolutely should be in our app.
5
+ class CursorsNotSupported < ActiveRecordError; end
6
+
7
+ module Associations
8
+ module ClassMethods
9
+ class JoinDependency
10
+ # Extra method we can use to clear out a couple of things in
11
+ # JoinDependency so we can use some of the methods for our
12
+ # cursors code.
13
+ def clear_with_cursor
14
+ @reflections = []
15
+ @base_records_hash = {}
16
+ @base_records_in_order = []
17
+ end
18
+ end
19
+ end
20
+ end
21
+
22
+ # PostgreSQLCursor is an Enumerable class so you can use each, map,
23
+ # any? and all of those nice Enumerable methods.
24
+ #
25
+ # At the moment, cursors aren't scrollable and are fetch forward-only
26
+ # and read-only.
27
+ #
28
+ # This class isn't really meant to be used outside of the
29
+ # ActiveRecord::Base#find method.
30
+ class PostgreSQLCursor
31
+ include Enumerable
32
+
33
+ attr_accessor :cursor_name
34
+
35
+ def initialize(model, cursor_name, query, join_dependency = nil)
36
+ @model = model
37
+ @cursor_name = if cursor_name
38
+ @model.connection.quote_table_name(cursor_name.gsub(/"/, '\"'))
39
+ end
40
+ @query = query
41
+ @join_dependency = join_dependency
42
+ end
43
+
44
+ def inspect
45
+ %{#<ActiveRecord::PostgreSQLCursor cursor_name: "#{cursor_name}", query: "#{@query}">}
46
+ end
47
+
48
+ # Calls block once for each record in the cursor, passing that
49
+ # record as a parameter.
50
+ def each
51
+ @model.transaction do
52
+ begin
53
+ declare_cursor
54
+ if @join_dependency
55
+ rows = Array.new
56
+ last_id = nil
57
+ while row = fetch_forward
58
+ current_id = row[@join_dependency.join_base.aliased_primary_key]
59
+ last_id ||= current_id
60
+ if last_id == current_id
61
+ rows << row
62
+ last_id = current_id
63
+ else
64
+ yield @join_dependency.instantiate(rows).first
65
+ @join_dependency.clear_with_cursor
66
+ rows = [ row ]
67
+ end
68
+ last_id = current_id
69
+ end
70
+
71
+ if !rows.empty?
72
+ yield @join_dependency.instantiate(rows).first
73
+ end
74
+ else
75
+ while row = fetch_forward
76
+ yield row
77
+ end
78
+ end
79
+ ensure
80
+ close_cursor
81
+ end
82
+ end
83
+ nil
84
+ end
85
+
86
+ private
87
+ def cursor_name
88
+ @cursor_name ||= "cursor_#{(rand * 1000000).ceil}"
89
+ end
90
+
91
+ def fetch_forward #:nodoc:
92
+ @model.find_by_sql(%{FETCH FORWARD FROM #{cursor_name}}).first
93
+ end
94
+
95
+ def declare_cursor #:nodoc:
96
+ @model.connection.execute(%{DECLARE #{cursor_name} CURSOR FOR #{@query}})
97
+ end
98
+
99
+ def close_cursor #:nodoc:
100
+ @model.connection.execute(%{CLOSE #{cursor_name}})
101
+ end
102
+ end
103
+ end
104
+
105
+ if ActiveRecord::VERSION::MAJOR >= 3
106
+ require File.join(File.dirname(__FILE__), 'postgresql_cursors_3')
107
+ else
108
+ require File.join(File.dirname(__FILE__), 'postgresql_cursors_2')
109
+ end
@@ -0,0 +1,75 @@
1
+
2
+ module ActiveRecord
3
+ class Base
4
+ class << self
5
+ # Override ActiveRecord::Base#find to allow for cursors in
6
+ # PostgreSQL. To use a cursor, set the first argument of
7
+ # find to :cursor. A PostgreSQLCursor object will be returned,
8
+ # which can then be used as an Enumerable to loop through the
9
+ # results.
10
+ #
11
+ # By default, cursor names are generated automatically using
12
+ # "cursor_#{rand}", where rand is a big ol' random number that
13
+ # is pretty unlikely to clash if you're using nested cursors.
14
+ # Alternatively, you can supply a specific cursor name by
15
+ # supplying a :cursor_name option.
16
+ def find_with_cursors *args
17
+ if args.first.to_s == 'cursor'
18
+ options = args.extract_options!
19
+ cursor_name = options.delete(:cursor_name)
20
+ validate_find_options(options)
21
+ set_readonly_option!(options)
22
+ find_cursor(cursor_name, options)
23
+ else
24
+ find_without_cursors(*args)
25
+ end
26
+ end
27
+ alias_method_chain :find, :cursors
28
+
29
+ def cursor(*args)
30
+ find(:cursor, *args)
31
+ end
32
+ end
33
+
34
+ private
35
+ # Find method for using cursors. This works just like the regular
36
+ # ActiveRecord::Base#find_every method, except it returns a
37
+ # PostgreSQLCursor object that can be used to loop through records.
38
+ def self.find_cursor(cursor_name, options)
39
+ unless connection.is_a? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
40
+ raise CursorsNotSupported, "#{connection.class} doesn't support cursors"
41
+ end
42
+
43
+ catch :invalid_query do
44
+ if options[:include]
45
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(self, merge_includes(scope(:find, :include), options[:include]), options[:joins])
46
+ return ActiveRecord::PostgreSQLCursor.new(
47
+ self,
48
+ cursor_name,
49
+ construct_finder_sql_with_included_associations(
50
+ options,
51
+ join_dependency
52
+ ),
53
+ join_dependency
54
+ )
55
+ else
56
+ return ActiveRecord::PostgreSQLCursor.new(
57
+ self,
58
+ cursor_name,
59
+ construct_finder_sql(
60
+ options
61
+ )
62
+ )
63
+ end
64
+ end
65
+ nil
66
+ end
67
+ end
68
+
69
+ class PostgreSQLCursor
70
+ def initialize_with_rails_2(model, cursor_name, query, join_dependency = nil)
71
+ initialize_without_rails_2(model, cursor_name, query, join_dependency)
72
+ end
73
+ alias_method_chain :initialize, :rails_2
74
+ end
75
+ end
@@ -0,0 +1,75 @@
1
+
2
+ module ActiveRecord
3
+ module CursorExtensions
4
+ extend ActiveSupport::Concern
5
+
6
+ included do
7
+ alias_method_chain :find, :cursors
8
+ end
9
+
10
+ # Override ActiveRecord::Base#find to allow for cursors in
11
+ # PostgreSQL. To use a cursor, set the first argument of
12
+ # find to :cursor. A PostgreSQLCursor object will be returned,
13
+ # which can then be used as an Enumerable to loop through the
14
+ # results.
15
+ #
16
+ # By default, cursor names are generated automatically using
17
+ # "cursor_#{rand}", where rand is a big ol' random number that
18
+ # is pretty unlikely to clash if you're using nested cursors.
19
+ # Alternatively, you can supply a specific cursor name by
20
+ # supplying a :cursor_name option.
21
+ def find_with_cursors(*args)
22
+ if args.first.to_s == 'cursor'
23
+ options = args.extract_options!
24
+ cursor_name = options.delete(:cursor_name)
25
+ find_cursor(cursor_name, options)
26
+ else
27
+ find_without_cursors(*args)
28
+ end
29
+ end
30
+
31
+ def cursor(*args)
32
+ find_with_cursors('cursor', *args)
33
+ end
34
+
35
+ private
36
+ # Find method for using cursors. This works just like the regular
37
+ # ActiveRecord::Base#find_every method, except it returns a
38
+ # PostgreSQLCursor object that can be used to loop through records.
39
+ def find_cursor(cursor_name, options)
40
+ unless connection.is_a? ActiveRecord::ConnectionAdapters::PostgreSQLAdapter
41
+ raise CursorsNotSupported, "#{connection.class} doesn't support cursors"
42
+ end
43
+
44
+ relation = apply_finder_options(options)
45
+ including = (relation.eager_load_values + relation.includes_values).uniq
46
+
47
+ if including.present?
48
+ join_dependency = ActiveRecord::Associations::ClassMethods::JoinDependency.new(@klass, including, nil)
49
+ join_relation = relation.construct_relation_for_association_find(join_dependency)
50
+
51
+ ActiveRecord::PostgreSQLCursor.new(self, cursor_name, join_relation, join_dependency)
52
+ else
53
+ ActiveRecord::PostgreSQLCursor.new(self, cursor_name, relation)
54
+ end
55
+ end
56
+ end
57
+
58
+ class PostgreSQLCursor
59
+ def initialize_with_rails_3(model, cursor_name, relation, join_dependency = nil)
60
+ @relation = relation
61
+ initialize_without_rails_3(model, cursor_name, relation.to_sql, join_dependency)
62
+ end
63
+ alias_method_chain :initialize, :rails_3
64
+ end
65
+ end
66
+
67
+ class ActiveRecord::Relation
68
+ include ActiveRecord::CursorExtensions
69
+ end
70
+
71
+ class ActiveRecord::Base
72
+ class << self
73
+ delegate :cursor, :to => :scoped
74
+ end
75
+ end
@@ -0,0 +1,97 @@
1
+
2
+ $: << File.dirname(__FILE__)
3
+ require 'test_helper'
4
+
5
+ class PostgreSQLCursorTests < Test::Unit::TestCase
6
+ include PostgreSQLCursorTestHelper
7
+
8
+ def test_find_cursor
9
+ cursor = Foo.find(:cursor, :order => 'id')
10
+
11
+ assert(cursor.is_a?(ActiveRecord::PostgreSQLCursor))
12
+
13
+ assert_equal(%w{ one two three four five }, cursor.collect(&:name))
14
+ end
15
+
16
+ def test_cursor_scoped
17
+ cursor = Foo.cursor(:order => 'id')
18
+
19
+ assert(cursor.is_a?(ActiveRecord::PostgreSQLCursor))
20
+
21
+ assert_equal(%w{ one two three four five }, cursor.collect(&:name))
22
+ end
23
+
24
+ def test_cursor_while_updating
25
+ cursor = Foo.cursor(:order => 'id')
26
+
27
+ cursor.each do |row|
28
+ row.name = "#{row.name}_updated"
29
+ assert(row.save)
30
+ end
31
+
32
+ assert_equal(%w{ one_updated two_updated three_updated four_updated five_updated }, cursor.collect(&:name))
33
+ end
34
+
35
+ def test_with_associations
36
+ cursor = Foo.cursor(:order => 'id')
37
+
38
+ cursor.each do |row|
39
+ assert(row.is_a?(Foo))
40
+ row.bars.each do |bar|
41
+ assert(bar.is_a?(Bar))
42
+ end
43
+ end
44
+ end
45
+
46
+ def test_with_associations_eager_loading
47
+ cursor = Foo.cursor(:order => 'foos.id', :include => :bars)
48
+
49
+ cursor.each do |row|
50
+ assert(row.is_a?(Foo))
51
+ row.bars.each do |bar|
52
+ assert(bar.is_a?(Bar))
53
+ end
54
+ end
55
+ end
56
+
57
+ def test_nested_cursors
58
+ cursor = Foo.cursor(:order => 'foos.id')
59
+
60
+ cursor.each do |row|
61
+ bars_cursor = row.bars.cursor
62
+ assert(bars_cursor.is_a?(ActiveRecord::PostgreSQLCursor))
63
+
64
+ bars_cursor.each do |bar|
65
+ assert(bar.is_a?(Bar))
66
+ end
67
+ end
68
+ end
69
+
70
+ if ActiveRecord::VERSION::MAJOR >= 3
71
+ def test_as_relation
72
+ cursor = Foo.order('foos.id').where('foos.id >= 3').cursor
73
+ assert_equal(3, cursor.to_a.length)
74
+
75
+ cursor.each do |row|
76
+ assert(row.is_a?(Foo))
77
+ assert_equal(2, row.bars.length)
78
+ row.bars.each do |bar|
79
+ assert(bar.is_a?(Bar))
80
+ end
81
+ end
82
+ end
83
+
84
+ def test_as_relation_with_associations
85
+ cursor = Foo.includes(:bars).order('foos.id').where('foos.id >= 3').cursor
86
+ assert_equal(3, cursor.to_a.length)
87
+
88
+ cursor.each do |row|
89
+ assert(row.is_a?(Foo))
90
+ assert_equal(2, row.bars.length)
91
+ row.bars.each do |bar|
92
+ assert(bar.is_a?(Bar))
93
+ end
94
+ end
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,68 @@
1
+
2
+ ACTIVERECORD_GEM_VERSION = ENV['ACTIVERECORD_GEM_VERSION'] || '~> 3.0.3'
3
+
4
+ require 'rubygems'
5
+ gem 'activerecord', ACTIVERECORD_GEM_VERSION
6
+
7
+ require 'active_support'
8
+ require 'active_support/core_ext/module/aliasing'
9
+ require 'active_record'
10
+ require 'test/unit'
11
+ require 'logger'
12
+ require File.join(File.dirname(__FILE__), *%w{ .. lib activerecord-postgresql-cursors })
13
+
14
+ puts "Testing against ActiveRecord #{Gem.loaded_specs['activerecord'].version.to_s}"
15
+
16
+ ActiveRecord::Base.logger = Logger.new("debug.log")
17
+ ActiveRecord::Base.configurations = {
18
+ 'arunit' => {
19
+ :adapter => 'postgresql',
20
+ :database => 'postgresql_cursors_unit_tests',
21
+ :min_messages => 'warning',
22
+ :schema_search_path => 'public'
23
+ }
24
+ }
25
+
26
+ ActiveRecord::Base.establish_connection 'arunit'
27
+ ARBC = ActiveRecord::Base.connection
28
+
29
+ if !ARBC.table_exists?('foos')
30
+ ActiveRecord::Migration.create_table(:foos) do |t|
31
+ t.text :name
32
+ end
33
+ end
34
+
35
+ if !ARBC.table_exists?('bars')
36
+ ActiveRecord::Migration.create_table(:bars) do |t|
37
+ t.text :name
38
+ t.integer :foo_id
39
+ end
40
+ end
41
+
42
+
43
+ class Bar < ActiveRecord::Base
44
+ belongs_to :foo
45
+ end
46
+
47
+ class Foo < ActiveRecord::Base
48
+ has_many :bars
49
+ end
50
+
51
+ module PostgreSQLCursorTestHelper
52
+ def setup
53
+ Foo.delete_all
54
+ Bar.delete_all
55
+ ARBC.execute(%{select setval('foos_id_seq', 1, false)})
56
+ ARBC.execute(%{select setval('bars_id_seq', 1, false)})
57
+
58
+ %w{ six seven eight nine ten eleven twelve thirteen fourteen fifteen }.each do |name|
59
+ Bar.create(:name => name)
60
+ end
61
+
62
+ %w{ one two three four five }.each_with_index do |name, i|
63
+ foo = Foo.new(:name => name)
64
+ foo.bar_ids = [ i + 1, i + 6 ]
65
+ foo.save
66
+ end
67
+ end
68
+ end
metadata ADDED
@@ -0,0 +1,75 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: activerecord-postgresql-cursors
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
+ - J Smith
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-07-11 00:00:00 Z
19
+ dependencies: []
20
+
21
+ description: Provides some support for PostgreSQL cursors in ActiveRecord.
22
+ email: code@zoocasa.com
23
+ executables: []
24
+
25
+ extensions: []
26
+
27
+ extra_rdoc_files:
28
+ - README.rdoc
29
+ files:
30
+ - MIT-LICENSE
31
+ - README.rdoc
32
+ - Rakefile
33
+ - VERSION
34
+ - activerecord-postgresql-cursors.gemspec
35
+ - init.rb
36
+ - lib/activerecord-postgresql-cursors.rb
37
+ - lib/postgresql_cursors_2.rb
38
+ - lib/postgresql_cursors_3.rb
39
+ - test/cursor_test.rb
40
+ - test/test_helper.rb
41
+ homepage: http://github.com/zoocasa/activerecord-postgresql-cursors
42
+ licenses: []
43
+
44
+ post_install_message:
45
+ rdoc_options: []
46
+
47
+ require_paths:
48
+ - lib
49
+ required_ruby_version: !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ hash: 3
55
+ segments:
56
+ - 0
57
+ version: "0"
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ hash: 3
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ requirements: []
68
+
69
+ rubyforge_project:
70
+ rubygems_version: 1.8.5
71
+ signing_key:
72
+ specification_version: 3
73
+ summary: Provides some support for PostgreSQL cursors in ActiveRecord.
74
+ test_files: []
75
+