active-record-ex 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 510af257c830989c351130a6613f9320e7f18e4c
4
+ data.tar.gz: 91e4ae0a1b8da4acee5c958cd28d76ca6f3444e6
5
+ SHA512:
6
+ metadata.gz: 98f6ac04aec0f9c0a4142f569be1e794f4cc7b288415db89d41791568028167f4100d33bd2924c21148f6410861d2be2c8f2e106de0bd5de90bdfd7a641d60a3
7
+ data.tar.gz: 530f2749ba17228e2a812acae3c84d3a142da31550f20579b118f723de3dcc0b850d87218dff9388b0aad9920a1c4e339fc87a5387687f5e5e320a8509513107
data/.gitignore ADDED
@@ -0,0 +1,35 @@
1
+ # Created by .ignore support plugin (hsz.mobi)
2
+
3
+ ### Ruby template
4
+ *.gem
5
+ *.rbc
6
+ /.config
7
+ /coverage/
8
+ /InstalledFiles
9
+ /pkg/
10
+ /spec/reports/
11
+ /test/tmp/
12
+ /test/version_tmp/
13
+ /tmp/
14
+
15
+ .idea
16
+
17
+ ## Documentation cache and generated files:
18
+ /.yardoc/
19
+ /_yardoc/
20
+ /doc/
21
+ /rdoc/
22
+
23
+ ## Environment normalisation:
24
+ /.bundle/
25
+ /vendor/bundle
26
+ /lib/bundler/man/
27
+
28
+ # for a library or gem, you might want to ignore these files since the code is
29
+ # intended to run in multiple environments; otherwise, check them in:
30
+ Gemfile.lock
31
+ .ruby-version
32
+ .ruby-gemset
33
+
34
+ # unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
35
+ .rvmrc
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.1.2
5
+ cache:
6
+ bundler: true
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in active-record-ex.gemspec
4
+ gemspec
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015 PagerDuty, Inc.
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # ActiveRecordEx
2
+
3
+ A library to make `ActiveRecord::Relation`s even more awesome.
4
+
5
+ [![Build Status](https://travis-ci.org/PagerDuty/active-record-ex.svg?branch=master)](https://travis-ci.org/PagerDuty/active-record-ex)
6
+
7
+ `ActiveRecordEx` is made of several [modules](#modules) that are used by `include`ing them on your `ActiveRecord` model classes.
8
+
9
+ #### Compatibility
10
+
11
+ Currently, only ActiveRecord 3.2 with Ruby 2.1 is supported. However, other versions have not been tested and may be compatible.
12
+
13
+ ## Modules
14
+
15
+ ### `AssocOrdering`
16
+
17
+ Extends setters for `has_many` associations so that ordering of association arrays is persisted.
18
+
19
+ ### `AssumeDestroy`
20
+
21
+ Changes the behavior of `accepts_nested_attributes_for` so that an explicit `_destroy: true` is not required to destroy an association model.
22
+
23
+ Instead, all models in the association will be destroyed if they are not included in the set of models used to update the association.
24
+
25
+ ### `ManyToMany`
26
+
27
+ Allows chaining of calls to `has_many` and `belongs_to` relationships.
28
+
29
+ ### `NillableFind`
30
+
31
+ Allows you to treat passing `nil` to a parent association as representing the "parent" of all of the child associations without a parent association.
32
+
33
+ ### `PolymorphicBuild`
34
+
35
+ Allows choosing the subclass of a model in an association via a passed `:type` parameter, useful for `accepts_nested_attributes_for` on a polymorphic association.
36
+
37
+ ## Development
38
+
39
+ Run tests with `rake test`.
data/Rakefile ADDED
@@ -0,0 +1,10 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rake/testtask'
3
+
4
+ Rake::TestTask.new do |t|
5
+ t.libs.push 'test'
6
+ t.test_files = FileList['test/**/*_test.rb']
7
+ end
8
+
9
+ desc 'Run tests'
10
+ task default: :test
@@ -0,0 +1,28 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'active_record_ex/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = 'active-record-ex'
8
+ spec.version = ActiveRecordEx::VERSION
9
+ spec.authors = ['Arjun Kavi', 'PagerDuty']
10
+ spec.email = ['arjun.kavi@gmail.com', 'developers@pagerduty.com']
11
+ spec.license = 'MIT'
12
+ spec.summary = 'Relation -> Relation methods'
13
+ spec.description = 'A library to make ActiveRecord::Relations even more awesome'
14
+ spec.homepage = 'https://github.com/PagerDuty/active-record-ex'
15
+
16
+ spec.files = `git ls-files -z`.split("\x0")
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ['lib']
20
+
21
+ spec.add_development_dependency 'bundler'
22
+ spec.add_development_dependency 'rake'
23
+ spec.add_development_dependency 'shoulda'
24
+ spec.add_development_dependency 'mocha'
25
+
26
+ spec.add_runtime_dependency 'activesupport', '~> 3.2'
27
+ spec.add_runtime_dependency 'activerecord', '~> 3.2'
28
+ end
@@ -0,0 +1,5 @@
1
+ require 'active_record_ex/version'
2
+ require 'active_record_ex/relation_extensions'
3
+ require 'active_record_ex/assoc_ordering'
4
+ require 'active_record_ex/assume_destroy'
5
+ require 'active_record_ex/many_to_many'
@@ -0,0 +1,57 @@
1
+ # Extends setters for has_many associations
2
+ # So that ordering of association arrays is persisted
3
+ module ActiveRecordEx
4
+ module AssocOrdering
5
+ def self.included(base)
6
+ base.extend(ClassMethods)
7
+ end
8
+
9
+ module ClassMethods
10
+ def has_many(assoc_name, options = {}, &extension)
11
+ order_field = options.delete(:order_on)
12
+ super
13
+ return unless order_field
14
+
15
+ define_model_setter(assoc_name, order_field)
16
+ end
17
+
18
+ def accepts_nested_attributes_for(assoc_name, options = {})
19
+ order_field = options.delete(:order_on)
20
+ allow_destroy = options[:allow_destroy] || options[:assume_destroy]
21
+ super
22
+ return unless order_field
23
+
24
+ define_attribute_setter(assoc_name, order_field, allow_destroy)
25
+ end
26
+
27
+ protected
28
+
29
+ def define_model_setter(assoc_name, order_field)
30
+ setter_name = "#{assoc_name}="
31
+ unordering_setter_name = "#{assoc_name}_without_ordering="
32
+ ordering_setter_name = "#{assoc_name}_with_ordering="
33
+
34
+ define_method(ordering_setter_name) do |models|
35
+ models.each_with_index{ |m, i| m.send("#{order_field}=", i + 1) }
36
+ self.send(unordering_setter_name, models)
37
+ end
38
+ alias_method_chain setter_name, :ordering
39
+ end
40
+
41
+ def define_attribute_setter(assoc_name, order_field, allow_destroy)
42
+ attrs_name = "#{assoc_name}_attributes"
43
+ setter_name = "#{attrs_name}="
44
+ unordering_setter_name = "#{attrs_name}_without_ordering="
45
+ ordering_setter_name = "#{attrs_name}_with_ordering="
46
+
47
+ define_method(ordering_setter_name) do |attrs|
48
+ new_attrs = attrs
49
+ new_attrs = attrs.reject{ |a| a[:_destroy] } if allow_destroy
50
+ new_attrs.each_with_index{ |a, i| a[order_field] = i + 1 }
51
+ self.send(unordering_setter_name, attrs)
52
+ end
53
+ alias_method_chain setter_name, :ordering
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,42 @@
1
+ # extends accepts_nested_attributes_for
2
+ # by default, accepts_nested_attributes_for "allow_destroy"s,
3
+ # ie, will destroy associations if explicitly marked by _destroy: true
4
+ # this flips that, causing an association to be destroyed
5
+ # if it's not included in the updating attrs
6
+ module ActiveRecordEx
7
+ module AssumeDestroy
8
+ def self.included base
9
+ base.extend(ClassMethods)
10
+ end
11
+
12
+ module ClassMethods
13
+ def accepts_nested_attributes_for(assoc_name, options={})
14
+ assume_destroy = options[:assume_destroy]
15
+ options.delete(:assume_destroy)
16
+ options[:allow_destroy] = assume_destroy
17
+
18
+ super assoc_name, options
19
+
20
+ return unless assume_destroy
21
+ attrs_name = ("#{assoc_name.to_s}_attributes").to_sym
22
+ setter_name = ("#{attrs_name.to_s}=").to_sym
23
+ unassuming_setter_name = ("#{attrs_name.to_s}_without_assume=").to_sym
24
+ assuming_setter_name = ("#{attrs_name.to_s}_with_assume=").to_sym
25
+
26
+ define_method(assuming_setter_name) do |attrs|
27
+ ids = attrs.map { |a| a['id'] || a[:id] }.compact
28
+ assocs = self.send(assoc_name)
29
+
30
+ dead_assocs = []
31
+ # the ternary's 'cause Arel doesn't do the right thing with an empty array
32
+ dead_assocs = assocs.where('id NOT IN (?)', ids.any? ? ids : '') unless self.new_record?
33
+ dead_attrs = dead_assocs.map {|assoc| {id: assoc.id, _destroy: true }}
34
+
35
+ attrs = attrs.concat(dead_attrs)
36
+ self.send(unassuming_setter_name, attrs)
37
+ end
38
+ alias_method_chain setter_name, :assume
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,138 @@
1
+ # allows you to chain has_manys and belongs_tos
2
+ # eg: $esc_pol.escalation_rules.escalation_targets
3
+ # eg: $usr.schedules.escalation_policies
4
+ module ActiveRecordEx
5
+ module ManyToMany
6
+ class ModelArel < ActiveRecord::Relation
7
+ def initialize(model)
8
+ super(model.class, model.class.arel_table)
9
+ @loaded = true
10
+ @records = [model]
11
+ end
12
+
13
+ def reset
14
+ # reset says "my currently loaded into memory models no longer currently represent this relation".
15
+ # That's never true for a ModelArel, so we no-op
16
+ end
17
+
18
+ def pluck(key)
19
+ key = key.name if key.respond_to? :name
20
+ @records.map(&:"#{key}")
21
+ end
22
+ end
23
+
24
+ def self.included(base)
25
+ base.extend(ClassMethods)
26
+ end
27
+
28
+ module ClassMethods
29
+ def belongs_to(name, options={})
30
+ subtypes = options.delete(:subtypes)
31
+ super
32
+ define_belongs_assoc(name, options.merge(subtypes: subtypes))
33
+ end
34
+
35
+ def has_many(name, options = {}, &extension)
36
+ super
37
+ define_has_assoc(name.to_s.singularize, options)
38
+ end
39
+
40
+ def has_one(name, options = {}, &extension)
41
+ super
42
+ define_has_assoc(name.to_s, options)
43
+ end
44
+
45
+ def singularize(method_name)
46
+ define_method(method_name) do |*params|
47
+ ModelArel.new(self).send(method_name, *params)
48
+ end
49
+ end
50
+
51
+ protected
52
+
53
+ def define_belongs_assoc(name, options)
54
+ if options[:polymorphic] && options[:subtypes]
55
+ define_polymorphic_assoc(name, options[:subtypes])
56
+ elsif !options[:polymorphic]
57
+ define_monomorphic_assoc(name, options)
58
+ end
59
+ end
60
+
61
+ def define_has_assoc(name, options)
62
+ if options[:through]
63
+ define_through_assoc(name, options)
64
+ else
65
+ define_plain_has_assoc(name, options)
66
+ end
67
+ end
68
+
69
+ def define_monomorphic_assoc(name, options)
70
+ name = name.to_s.singularize
71
+ klass_name = options[:class_name] || self.parent_string + name.camelize
72
+ key_name = options[:foreign_key] || name.foreign_key
73
+
74
+ method_name = name.pluralize.to_sym
75
+ define_singleton_method(method_name) do
76
+ klass = klass_name.constantize
77
+ foreign_key = self.arel_table[key_name]
78
+ primary_keys = self.pluck(foreign_key).uniq
79
+ # eg, Account.where(id: ids)
80
+ klass.where(klass.primary_key => primary_keys)
81
+ end
82
+ end
83
+
84
+ def define_polymorphic_assoc(name, subtypes)
85
+ Array.wrap(subtypes).each do |subtype_klass|
86
+ key_name = name.to_s.foreign_key
87
+ type_key = "#{name.to_s}_type"
88
+ type_val = subtype_klass.to_s
89
+
90
+ method_name = subtype_klass.to_s.demodulize.underscore.pluralize.to_sym
91
+ define_singleton_method(method_name) do
92
+ foreign_key = self.arel_table[key_name]
93
+ primary_keys = self.where(type_key => type_val).pluck(foreign_key).uniq
94
+ # eg, Account.where(id: ids)
95
+ subtype_klass.where(subtype_klass.primary_key => primary_keys)
96
+ end
97
+ end
98
+ end
99
+
100
+ def define_plain_has_assoc(name, options)
101
+ klass_name = options[:class_name] || self.parent_string + name.camelize
102
+
103
+ conditions = {}
104
+ if options[:as]
105
+ type_key_name = "#{options[:as].to_s}_type"
106
+ conditions[type_key_name] = self.to_s
107
+ foreign_key_name = options[:as].to_s.foreign_key
108
+ else
109
+ foreign_key_name = options[:foreign_key] || self.to_s.foreign_key
110
+ end
111
+
112
+ method_name = name.pluralize.to_sym
113
+ define_singleton_method(method_name) do
114
+ primary_key = self.arel_table[self.primary_key]
115
+ foreign_keys = self.pluck(primary_key).uniq
116
+
117
+ other_klass = klass_name.constantize
118
+ other_klass.where(conditions).where(foreign_key_name => foreign_keys)
119
+ end
120
+ end
121
+
122
+ def define_through_assoc(name, options)
123
+ through_method = options[:through].to_s.pluralize
124
+ method_name = name.pluralize.to_sym
125
+ define_singleton_method(method_name) do
126
+ through_base = self.send(through_method)
127
+ through_base.send(method_name)
128
+ end
129
+ end
130
+
131
+ def parent_string
132
+ parent = self.parent
133
+ return '' if parent == Object
134
+ "#{parent.to_s}::"
135
+ end
136
+ end
137
+ end
138
+ end
@@ -0,0 +1,33 @@
1
+ module ActiveRecordEx
2
+ module NillableFind
3
+ class NillableArel
4
+ def initialize(base, ids, parent_scope)
5
+ @base = base
6
+ @ids = ids
7
+ @parent_scope = parent_scope
8
+ end
9
+
10
+ def method_missing(method_name, *args, &block)
11
+ return super unless @base.respond_to? method_name
12
+
13
+ normals = @base.where(id: @ids).send(method_name, *args)
14
+ return normals unless @ids.include? nil
15
+
16
+ used = @base.send(method_name, *args)
17
+ # return those in normals AND those in parent scope not belonging to another
18
+ normals.disjunct(used.relative_complement(@parent_scope))
19
+ end
20
+ end
21
+
22
+ def self.included(base)
23
+ raise ArgumentError.new("#{base} must include ManyToMany") unless base.included_modules.include? ActiveRecordEx::ManyToMany
24
+ base.extend(ClassMethods)
25
+ end
26
+
27
+ module ClassMethods
28
+ def nillable_find(ids, parent_scope)
29
+ NillableArel.new(self, ids, parent_scope)
30
+ end
31
+ end
32
+ end
33
+ end
@@ -0,0 +1,30 @@
1
+ # Allows choosing the subclass of a model
2
+ # via a passed :type parameter
3
+ # this is useful for using accepts_nested_attributes_for
4
+ # with subclassed associations
5
+ module ActiveRecordEx
6
+ module PolymorphicBuild
7
+ def self.included(base)
8
+ base.extend(ClassMethods)
9
+ base.instance_eval do
10
+ attr_accessible :type
11
+
12
+ class << self
13
+ alias_method_chain :new, :typing
14
+ end
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+ def new_with_typing(attrs = {}, options = {})
20
+ if attrs[:type] && (klass = attrs[:type].constantize) < self
21
+ klass.new(attrs, options)
22
+ elsif attrs[:type] && !((klass = attrs[:type].constantize) <= self)
23
+ raise ArgumentError.new("Attempting to instantiate #{klass}, which is not a subclass of #{self}")
24
+ else
25
+ new_without_typing(attrs, options)
26
+ end
27
+ end
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,37 @@
1
+ module ActiveRecord
2
+ class Relation
3
+ def where_only(value)
4
+ relation = clone
5
+ relation.where_values = build_where(value)
6
+ relation
7
+ end
8
+
9
+ def none
10
+ self.where('1=0')
11
+ end
12
+
13
+ def disjunct(other)
14
+ other_where = other.collapsed_where
15
+ this_where = self.collapsed_where
16
+ self.where_only(this_where.or(other_where))
17
+ end
18
+
19
+ # If self and other are viewed as sets
20
+ # relative_complement represents everything
21
+ # that's in other but NOT in self
22
+ def relative_complement(other)
23
+ this_where = self.collapsed_where
24
+ other_where = other.collapsed_where
25
+ self.where_only(this_where.not.and(other_where))
26
+ end
27
+
28
+ protected
29
+
30
+ def collapsed_where
31
+ values = self.where_values
32
+ values = [true] if values.empty?
33
+ # FIXME: Needs to wrap string literal conditions (e.g., where("id > 1"))
34
+ Arel::Nodes::And.new(values)
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,3 @@
1
+ module ActiveRecordEx
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,104 @@
1
+ require 'active_record'
2
+ require 'test/unit'
3
+ require 'shoulda'
4
+ require 'mocha'
5
+
6
+ require 'active_record_ex/relation_extensions'
7
+
8
+ class ActiveSupport::TestCase
9
+ def db_expects(arel, query, response = nil)
10
+ response_columns = response.try(:first).try(:keys).try(:map, &:to_s) || []
11
+ response_rows = response.try(:map, &:values) || []
12
+ response = ActiveRecord::Result.new(response_columns, response_rows)
13
+
14
+ case query.first
15
+ when /^SELECT/
16
+ arel.connection.expects(:exec_query).with(*query).returns(response)
17
+ when /^DELETE/
18
+ arel.connection.expects(:exec_delete).with(*query).returns(response)
19
+ end
20
+ end
21
+ end
22
+
23
+ # Used as a "dummy" model in tests to avoid using a database connection
24
+ class StubModel < ActiveRecord::Base
25
+ self.abstract_class = true
26
+
27
+ conn = Class.new(ActiveRecord::ConnectionAdapters::AbstractAdapter) do
28
+ def quote_column_name(name)
29
+ "`#{name.to_s.gsub('`', '``')}`"
30
+ end
31
+
32
+ def quote_table_name(name)
33
+ quote_column_name(name).gsub('.', '`.`')
34
+ end
35
+
36
+ def select(sql, name = nil, _ = [])
37
+ exec_query(sql, name).to_a
38
+ end
39
+ end
40
+
41
+ visitor = Class.new(Arel::Visitors::ToSql) do
42
+ def table_exists?(*_)
43
+ true
44
+ end
45
+
46
+ def column_for(attr)
47
+ pk = attr == 'id'
48
+ column = ActiveRecord::ConnectionAdapters::Column.new(attr, nil, pk ? :integer : :string)
49
+ column.primary = pk
50
+ column
51
+ end
52
+ end
53
+
54
+ @@connection = conn.new({})
55
+ @@connection.visitor = visitor.new(@@connection)
56
+
57
+ class << self
58
+ def connection
59
+ @@connection
60
+ end
61
+
62
+ def columns; []; end
63
+
64
+ def get_primary_key(*_); 'id'; end
65
+ end
66
+
67
+ # prevent AR from hitting the DB to get the schema
68
+ def get_primary_key(*_); 'id'; end
69
+
70
+ def with_transaction_returning_status; yield; end
71
+ end
72
+
73
+ # SQLCounter is part of ActiveRecord but is not distributed with the gem (used for internal tests only)
74
+ # see https://github.com/rails/rails/blob/3-2-stable/activerecord/test/cases/helper.rb#L59
75
+ module ActiveRecord
76
+ class SQLCounter
77
+ cattr_accessor :ignored_sql
78
+ self.ignored_sql = [/^PRAGMA (?!(table_info))/, /^SELECT currval/, /^SELECT CAST/, /^SELECT @@IDENTITY/, /^SELECT @@ROWCOUNT/, /^SAVEPOINT/, /^ROLLBACK TO SAVEPOINT/, /^RELEASE SAVEPOINT/, /^SHOW max_identifier_length/, /^BEGIN/, /^COMMIT/]
79
+
80
+ # FIXME: this needs to be refactored so specific database can add their own
81
+ # ignored SQL. This ignored SQL is for Oracle.
82
+ ignored_sql.concat [/^select .*nextval/i, /^SAVEPOINT/, /^ROLLBACK TO/, /^\s*select .* from all_triggers/im]
83
+
84
+ cattr_accessor :log
85
+ self.log = []
86
+
87
+ attr_reader :ignore
88
+
89
+ def initialize(ignore = self.class.ignored_sql)
90
+ @ignore = ignore
91
+ end
92
+
93
+ def call(name, start, finish, message_id, values)
94
+ sql = values[:sql]
95
+
96
+ # FIXME: this seems bad. we should probably have a better way to indicate
97
+ # the query was cached
98
+ return if 'CACHE' == values[:name] || ignore.any? { |x| x =~ sql }
99
+ self.class.log << sql
100
+ end
101
+ end
102
+
103
+ ActiveSupport::Notifications.subscribe('sql.active_record', SQLCounter.new)
104
+ end
@@ -0,0 +1,71 @@
1
+ require 'test_helper'
2
+ require 'active_record_ex/assoc_ordering'
3
+
4
+ class OrderedAssoc < StubModel
5
+ attr_accessor :name
6
+ attr_accessor :order
7
+ attr_accessor :has_orderd_assoc_id
8
+ end
9
+
10
+ class DestroyableAssoc < StubModel
11
+ attr_accessor :name
12
+ attr_accessor :order
13
+ attr_accessor :has_orderd_assoc_id
14
+ end
15
+
16
+ class HasOrderedAssoc < StubModel
17
+ include ActiveRecordEx::AssocOrdering
18
+
19
+ has_many :ordered_assocs, order_on: :order
20
+ accepts_nested_attributes_for :ordered_assocs, order_on: :order
21
+
22
+ has_many :destroyable_assocs, order_on: :order
23
+ accepts_nested_attributes_for :destroyable_assocs, order_on: :order, allow_destroy: true
24
+
25
+ def save
26
+ end
27
+ end
28
+
29
+
30
+ class AssocOrderingTest < ActiveSupport::TestCase
31
+ context 'A class with ActiveREcord::AssocOrdering included' do
32
+ setup { @model = HasOrderedAssoc.new }
33
+
34
+ should 'order associations set as models' do
35
+ assocs = [OrderedAssoc.new(name: 'first'), OrderedAssoc.new(name: 'second')]
36
+ @model.ordered_assocs = assocs
37
+ sorted = @model.ordered_assocs.sort_by(&:order)
38
+ assert_equal 'first', sorted[0].name
39
+ assert_equal 1, sorted[0].order
40
+ assert_equal 'second', sorted[1].name
41
+ assert_equal 2, sorted[1].order
42
+ end
43
+
44
+ should 'order associations set as attributes' do
45
+ attrs = {ordered_assocs_attributes:
46
+ [{name: 'first'}, {name: 'second'}]
47
+ }
48
+ expected_attrs = [{name: 'first', order: 1}, {name: 'second', order: 2}]
49
+ @model.expects(:ordered_assocs_attributes_without_ordering=).with(expected_attrs)
50
+ @model.update_attributes(attrs)
51
+ end
52
+
53
+ should 'not ignore marked-for-destroy association attributes for ordering that don\'t allow destroy' do
54
+ attrs = {ordered_assocs_attributes:
55
+ [{name: 'first', _destroy: true}, {name: 'second'}]
56
+ }
57
+ expected_attrs = [{name: 'first', _destroy: true, order: 1}, {name: 'second', order: 2}]
58
+ @model.expects(:ordered_assocs_attributes_without_ordering=).with(expected_attrs)
59
+ @model.update_attributes(attrs)
60
+ end
61
+
62
+ should 'ignore marked-for-destroy association attributes for ordering that allow destroy' do
63
+ attrs = {destroyable_assocs_attributes:
64
+ [{name: 'first', _destroy: true}, {name: 'second'}]
65
+ }
66
+ expected_attrs = [{name: 'first', _destroy: true}, {name: 'second', order: 1}]
67
+ @model.expects(:destroyable_assocs_attributes_without_ordering=).with(expected_attrs)
68
+ @model.update_attributes(attrs)
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,83 @@
1
+ require 'test_helper'
2
+ require 'active_record_ex/assume_destroy'
3
+
4
+ class AssumeDestroyTest < ActiveRecord::TestCase
5
+ class AssumesDestroy < StubModel
6
+ include ActiveRecordEx::AssumeDestroy
7
+
8
+ has_many :destroyees
9
+ accepts_nested_attributes_for :destroyees, assume_destroy: true
10
+ end
11
+
12
+ class Destroyee < StubModel
13
+ end
14
+
15
+ context 'ActiveRecordEx::AssumeDestroy' do
16
+ setup do
17
+ @subject = AssumesDestroy.new
18
+ @subject.stubs(:new_record?).returns(false)
19
+ end
20
+
21
+ context 'preconditions in ActiveRecord' do
22
+ should 'DELETE records marked for destruction' do
23
+ attrs = []
24
+ stub_association_query(@subject, '\'\'', [{'id' => 1}, {'id' => 2}])
25
+ db_expects(@subject, ['SELECT `destroyees`.* FROM `destroyees` WHERE `destroyees`.`assumes_destroy_id` IS NULL AND `destroyees`.`id` IN (1, 2)', 'AssumeDestroyTest::Destroyee Load'], [{'id' => 1}, {'id' => 2}])
26
+ db_expects(@subject.destroyees, ['DELETE FROM `destroyees` WHERE `destroyees`.`id` = ?', 'SQL', [[nil, 1]]])
27
+ db_expects(@subject.destroyees, ['DELETE FROM `destroyees` WHERE `destroyees`.`id` = ?', 'SQL', [[nil, 2]]])
28
+
29
+ @subject.update_attributes(destroyees_attributes: attrs)
30
+ end
31
+ end
32
+
33
+ should 'not mark any for destruction if subject is new' do
34
+ @subject.stubs(:new_record?).returns(true)
35
+ attrs = [{name: 'one'}]
36
+ expected_attrs = [{name: 'one'}]
37
+ @subject.expects(:destroyees_attributes_without_assume=).with(expected_attrs)
38
+
39
+ # shouldn't even hit the DB
40
+ assert_no_queries { @subject.destroyees_attributes = attrs }
41
+ end
42
+
43
+ should 'mark all associations for destruction when passed an empty array' do
44
+ attrs = []
45
+ stub_association_query(@subject, '\'\'', [{'id' => 1}, {'id' => 2}])
46
+
47
+ expected_attrs = [{id: 1, _destroy: true}, {id: 2, _destroy: true}]
48
+ @subject.expects(:destroyees_attributes_without_assume=).with(expected_attrs)
49
+ @subject.destroyees_attributes = attrs
50
+ end
51
+
52
+ should 'mark all existing associations for destruction when passed an array of just new' do
53
+ attrs = [{name: 'one'}]
54
+ stub_association_query(@subject, '\'\'', [{'id' => 1}, {'id' => 2}])
55
+
56
+ expected_attrs = [{name: 'one'}, {id: 1, _destroy: true}, {id: 2, _destroy: true}]
57
+ @subject.expects(:destroyees_attributes_without_assume=).with(expected_attrs)
58
+ @subject.destroyees_attributes = attrs
59
+ end
60
+
61
+ should 'not mark explicitly passed in associations for destruction' do
62
+ attrs = [{name: 'one'}, {id: 1}]
63
+ stub_association_query(@subject, '1', [{id: 2}])
64
+
65
+ expected_attrs = [{name: 'one'}, {id: 1}, {id: 2, _destroy: true}]
66
+ @subject.expects(:destroyees_attributes_without_assume=).with(expected_attrs)
67
+ @subject.destroyees_attributes = attrs
68
+ end
69
+
70
+ should 'preserve existing marks for destruction' do
71
+ attrs = [{name: 'one'}, {id: 1, _destroy: true}]
72
+ stub_association_query(@subject, '1', [{id: 2}])
73
+
74
+ expected_attrs = [{name: 'one'}, {id: 1, _destroy: true}, {id: 2, _destroy: true}]
75
+ @subject.expects(:destroyees_attributes_without_assume=).with(expected_attrs)
76
+ @subject.destroyees_attributes = attrs
77
+ end
78
+ end
79
+
80
+ def stub_association_query(subject, id_string, id_response)
81
+ db_expects(subject, ["SELECT `destroyees`.* FROM `destroyees` WHERE `destroyees`.`assumes_destroy_id` IS NULL AND (id NOT IN (#{id_string}))", 'AssumeDestroyTest::Destroyee Load'], id_response)
82
+ end
83
+ end
@@ -0,0 +1,162 @@
1
+ require 'test_helper'
2
+ require 'active_record_ex/many_to_many'
3
+
4
+ class ManyToManyTest < ActiveSupport::TestCase
5
+ class HasManied < StubModel
6
+ include ActiveRecordEx::ManyToMany
7
+
8
+ has_one :one
9
+ has_many :simple_belongs_tos
10
+ has_many :belongs_to_throughs, through: :simple_belongs_tos
11
+ has_many :class_nameds, class_name: 'ManyToManyTest::SomeClassName'
12
+ has_many :foreign_keyeds, foreign_key: :some_foreign_key_id
13
+ has_many :aseds, as: :some_as
14
+
15
+ singularize :ones
16
+ end
17
+ class SimpleBelongsTo < StubModel
18
+ include ActiveRecordEx::ManyToMany
19
+
20
+ belongs_to :has_manied
21
+ has_many :belongs_to_throughs
22
+
23
+ singularize :has_manieds
24
+ end
25
+ class BelongsToThrough < StubModel
26
+ include ActiveRecordEx::ManyToMany
27
+
28
+ belongs_to :has_manied
29
+ end
30
+ class SomeClassName < StubModel
31
+ include ActiveRecordEx::ManyToMany
32
+
33
+ belongs_to :some_name, class_name: 'ManyToManyTest::HasManied'
34
+ end
35
+ class ForeignKeyed < StubModel
36
+ include ActiveRecordEx::ManyToMany
37
+
38
+ belongs_to :has_manied, foreign_key: :some_foreign_key_id
39
+ end
40
+ class Ased < StubModel
41
+ include ActiveRecordEx::ManyToMany
42
+
43
+ belongs_to :some_as, polymorphic: true, subtypes: [HasManied]
44
+ end
45
+ class One < StubModel
46
+ end
47
+
48
+ context 'ActiveRecord::ManyToMany' do
49
+ context '#has_one' do
50
+ setup { @arel = HasManied.scoped }
51
+ should 'handle the simple case correctly' do
52
+ db_expects(@arel, ['SELECT `has_manieds`.`id` FROM `has_manieds` '], [{id: 1}])
53
+ db_expects(@arel, ['SELECT `ones`.* FROM `ones` WHERE `ones`.`has_manied_id` IN (1)', 'ManyToManyTest::One Load'])
54
+ @arel.ones.to_a
55
+ end
56
+ end
57
+
58
+ context '#has_many' do
59
+ setup { @arel = HasManied.scoped }
60
+
61
+ should 'handle the simple case correctly' do
62
+ db_expects(@arel, ['SELECT `has_manieds`.`id` FROM `has_manieds` '], [id: 1])
63
+ db_expects(@arel, ['SELECT `simple_belongs_tos`.* FROM `simple_belongs_tos` WHERE `simple_belongs_tos`.`has_manied_id` IN (1)', 'ManyToManyTest::SimpleBelongsTo Load'])
64
+ @arel.simple_belongs_tos.to_a
65
+ end
66
+
67
+ should 'handle the empty base case correctly' do
68
+ db_expects(@arel, ['SELECT `has_manieds`.`id` FROM `has_manieds` WHERE (1=0)'], [])
69
+ db_expects(@arel, ['SELECT `simple_belongs_tos`.* FROM `simple_belongs_tos` WHERE 1=0', 'ManyToManyTest::SimpleBelongsTo Load'])
70
+ @arel.none.simple_belongs_tos.to_a
71
+ end
72
+
73
+ should 'handle the multiple base ids case correctly' do
74
+ db_expects(@arel, ['SELECT `has_manieds`.`id` FROM `has_manieds` '], [{id: 1}, {id: 2}])
75
+ db_expects(@arel, ['SELECT `simple_belongs_tos`.* FROM `simple_belongs_tos` WHERE `simple_belongs_tos`.`has_manied_id` IN (1, 2)', 'ManyToManyTest::SimpleBelongsTo Load'])
76
+ @arel.simple_belongs_tos.to_a
77
+ end
78
+
79
+ should 'chain queries for has_many through:' do
80
+ db_expects(@arel, ['SELECT `has_manieds`.`id` FROM `has_manieds` '], [{id: 1}])
81
+ db_expects(@arel, ['SELECT `simple_belongs_tos`.`id` FROM `simple_belongs_tos` WHERE `simple_belongs_tos`.`has_manied_id` IN (1)'], [{id: 1}])
82
+ db_expects(@arel, ['SELECT `belongs_to_throughs`.* FROM `belongs_to_throughs` WHERE `belongs_to_throughs`.`simple_belongs_to_id` IN (1)', 'ManyToManyTest::BelongsToThrough Load'])
83
+
84
+ @arel.belongs_to_throughs.to_a
85
+ end
86
+
87
+ should 'not N+1 has_many through:' do
88
+ db_expects(@arel, ['SELECT `has_manieds`.`id` FROM `has_manieds` '], [{id: 1}, {id: 2}])
89
+ db_expects(@arel, ['SELECT `simple_belongs_tos`.`id` FROM `simple_belongs_tos` WHERE `simple_belongs_tos`.`has_manied_id` IN (1, 2)'], [{id: 1}, {id: 2}])
90
+ db_expects(@arel, ['SELECT `belongs_to_throughs`.* FROM `belongs_to_throughs` WHERE `belongs_to_throughs`.`simple_belongs_to_id` IN (1, 2)', 'ManyToManyTest::BelongsToThrough Load'])
91
+
92
+ @arel.belongs_to_throughs.to_a
93
+ end
94
+
95
+ should 'use the class name passed in' do
96
+ db_expects(@arel, ['SELECT `has_manieds`.`id` FROM `has_manieds` '], [id: 1])
97
+ db_expects(@arel, ['SELECT `some_class_names`.* FROM `some_class_names` WHERE `some_class_names`.`has_manied_id` IN (1)', 'ManyToManyTest::SomeClassName Load'])
98
+ @arel.class_nameds.to_a
99
+ end
100
+
101
+ should 'use the foreign key passed in' do
102
+ db_expects(@arel, ['SELECT `has_manieds`.`id` FROM `has_manieds` '], [id: 1])
103
+ db_expects(@arel, ['SELECT `foreign_keyeds`.* FROM `foreign_keyeds` WHERE `foreign_keyeds`.`some_foreign_key_id` IN (1)', 'ManyToManyTest::ForeignKeyed Load'])
104
+ @arel.foreign_keyeds.to_a
105
+ end
106
+
107
+ should 'use the as passed in' do
108
+ db_expects(@arel, ['SELECT `has_manieds`.`id` FROM `has_manieds` '], [id: 1])
109
+ db_expects(@arel, ['SELECT `aseds`.* FROM `aseds` WHERE `aseds`.`some_as_type` = \'ManyToManyTest::HasManied\' AND `aseds`.`some_as_id` IN (1)', 'ManyToManyTest::Ased Load'])
110
+ @arel.aseds.to_a
111
+ end
112
+ end
113
+
114
+ context '#belongs_to' do
115
+ should 'handle the simple case correctly' do
116
+ @arel = SimpleBelongsTo.scoped
117
+ db_expects(@arel, ['SELECT `simple_belongs_tos`.`has_manied_id` FROM `simple_belongs_tos` '], [has_manied_id: 1])
118
+ db_expects(@arel, ['SELECT `has_manieds`.* FROM `has_manieds` WHERE `has_manieds`.`id` IN (1)', 'ManyToManyTest::HasManied Load'])
119
+ @arel.has_manieds.to_a
120
+ end
121
+
122
+ should 'use the class name passed in' do
123
+ @arel = SomeClassName.scoped
124
+ db_expects(@arel, ['SELECT `some_class_names`.`some_name_id` FROM `some_class_names` '], [some_name_id: 1])
125
+ db_expects(@arel, ['SELECT `has_manieds`.* FROM `has_manieds` WHERE `has_manieds`.`id` IN (1)', 'ManyToManyTest::HasManied Load'])
126
+ @arel.some_names.to_a
127
+ end
128
+
129
+ should 'use the foreign key passed in' do
130
+ @arel = ForeignKeyed.scoped
131
+ db_expects(@arel, ['SELECT `foreign_keyeds`.`some_foreign_key_id` FROM `foreign_keyeds` '], [some_foreign_key_id: 1])
132
+ db_expects(@arel, ['SELECT `has_manieds`.* FROM `has_manieds` WHERE `has_manieds`.`id` IN (1)', 'ManyToManyTest::HasManied Load'])
133
+ @arel.has_manieds.to_a
134
+ end
135
+
136
+ should 'handle polymorphic belongs_to' do
137
+ @arel = Ased.scoped
138
+ db_expects(@arel, ['SELECT `aseds`.`some_as_id` FROM `aseds` WHERE `aseds`.`some_as_type` = \'ManyToManyTest::HasManied\''], [some_as_id: 1])
139
+ db_expects(@arel, ['SELECT `has_manieds`.* FROM `has_manieds` WHERE `has_manieds`.`id` IN (1)', 'ManyToManyTest::HasManied Load'])
140
+ @arel.has_manieds.to_a
141
+ end
142
+ end
143
+
144
+ context '#singularize' do
145
+ should 'work for belongs_tos without triggering an extra query' do
146
+ @model = SimpleBelongsTo.new
147
+ @model.stubs(:has_manied_id).returns(42)
148
+ @arel = HasManied.scoped
149
+ db_expects(@arel, ['SELECT `has_manieds`.* FROM `has_manieds` WHERE `has_manieds`.`id` IN (42)', 'ManyToManyTest::HasManied Load'])
150
+ @model.has_manieds.to_a
151
+ end
152
+
153
+ should 'work for has_ones without triggering an extra query' do
154
+ @model = HasManied.new
155
+ @model.stubs(:id).returns(42)
156
+ @arel = One.scoped
157
+ db_expects(@arel, ['SELECT `ones`.* FROM `ones` WHERE `ones`.`has_manied_id` IN (42)', 'ManyToManyTest::One Load'])
158
+ @model.ones.to_a
159
+ end
160
+ end
161
+ end
162
+ end
@@ -0,0 +1,57 @@
1
+ require 'test_helper'
2
+ require 'active_record_ex/many_to_many'
3
+ require 'active_record_ex/nillable_find'
4
+
5
+ class NillableFindTest < ActiveSupport::TestCase
6
+ class Parent < StubModel
7
+ include ActiveRecordEx::ManyToMany
8
+ include ActiveRecordEx::NillableFind
9
+
10
+ has_many :children
11
+ end
12
+
13
+ class Child < StubModel
14
+ end
15
+
16
+ context 'ActiveRecordEx::NillableFind' do
17
+ context '#nillable_find' do
18
+ setup { @arel = Parent.scoped }
19
+ # RC == relative complement
20
+ should 'request the RC of the base scope in the parent scope when just passed nil' do
21
+ # fetch IDs
22
+ db_expects(@arel, ['SELECT `parents`.`id` FROM `parents` WHERE `parents`.`id` IS NULL'], [])
23
+ # fetch outside set
24
+ db_expects(@arel, ['SELECT `parents`.`id` FROM `parents` '], [{id: 1}, {id: 2}])
25
+ # disjunct
26
+ db_expects(@arel, ['SELECT `children`.* FROM `children` WHERE ((1=0 OR NOT (`children`.`parent_id` IN (1, 2)) AND `children`.`foo` = \'bar\'))', 'NillableFindTest::Child Load'], [])
27
+
28
+ Parent.nillable_find([nil], Child.where(foo: 'bar')).children.all
29
+ end
30
+
31
+ should 'request the disjunct of the RC of base scope in parent scope and all children of non-nil ids' do
32
+ # fetch IDs
33
+ db_expects(@arel, ['SELECT `parents`.`id` FROM `parents` WHERE ((`parents`.`id` IN (1) OR `parents`.`id` IS NULL))'], [{id: 1}])
34
+ # fetch outside set
35
+ db_expects(@arel, ['SELECT `parents`.`id` FROM `parents` '], [{id: 1}, {id: 2}])
36
+ # disjunct
37
+ db_expects(@arel, ['SELECT `children`.* FROM `children` WHERE ((`children`.`parent_id` IN (1) OR NOT (`children`.`parent_id` IN (1, 2)) AND `children`.`foo` = \'bar\'))', 'NillableFindTest::Child Load'], [])
38
+
39
+ Parent.nillable_find([1, nil], Child.where(foo: 'bar')).children.all
40
+ end
41
+
42
+ should 'request nothing when passed no an empty set of ids' do
43
+ db_expects(@arel, ['SELECT `parents`.`id` FROM `parents` WHERE 1=0'], [])
44
+ db_expects(@arel, ['SELECT `children`.* FROM `children` WHERE 1=0', 'NillableFindTest::Child Load'], [])
45
+
46
+ Parent.nillable_find([], Child.where(foo: 'bar')).children.all
47
+ end
48
+
49
+ should 'request as a normal many-to-many when passed only normal ids' do
50
+ db_expects(@arel, ['SELECT `parents`.`id` FROM `parents` WHERE `parents`.`id` IN (1)'], [{id: 1}])
51
+ db_expects(@arel, ['SELECT `children`.* FROM `children` WHERE `children`.`parent_id` IN (1)', 'NillableFindTest::Child Load'], [])
52
+
53
+ Parent.nillable_find([1], Child.where(foo: 'bar')).children.all
54
+ end
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,28 @@
1
+ require 'test_helper'
2
+ require 'active_record_ex/polymorphic_build'
3
+
4
+ class PolymorphicBuildTest < ActiveSupport::TestCase
5
+ class PolyBase < StubModel
6
+ include ActiveRecordEx::PolymorphicBuild
7
+ attr_accessor :type
8
+ end
9
+
10
+ class PolyChild < PolyBase
11
+ end
12
+
13
+ should 'instantiate an instance with the subclass passed in' do
14
+ inst = PolyBase.new(type: PolyChild.to_s)
15
+ assert_equal PolyChild, inst.class
16
+ end
17
+
18
+ should 'instantiate an instance with the class itself passed in' do
19
+ inst = PolyBase.new(type: PolyBase.to_s)
20
+ assert_equal PolyBase, inst.class
21
+ end
22
+
23
+ should 'throw an error if the passed in class is not a subclass' do
24
+ assert_raise(ArgumentError) do
25
+ PolyBase.new(type: StubModel.to_s)
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,62 @@
1
+ require 'test_helper'
2
+
3
+ class RelationExtensionsTest < ActiveSupport::TestCase
4
+ class HasManied < StubModel
5
+ has_many :belongs_tos
6
+ end
7
+
8
+ class BelongsTo < StubModel
9
+ belongs_to :has_manied
10
+ attr_accessor :has_manied_id
11
+ end
12
+
13
+ context '#relative_complement' do
14
+ setup do
15
+ @hm1 = HasManied.new
16
+ @hm2 = HasManied.new
17
+ @bt1 = @hm1.belongs_tos.new
18
+ end
19
+
20
+ context 'disjoint set' do
21
+ should 'return all belongs_tos' do
22
+ assert_equal @hm1.belongs_tos.all, @hm2.belongs_tos.relative_complement(@hm1.belongs_tos)
23
+ end
24
+ end
25
+
26
+ context 'identical set' do
27
+ should 'return nothing' do
28
+ assert_empty @hm1.belongs_tos.relative_complement(@hm1.belongs_tos)
29
+ end
30
+ end
31
+
32
+ context 'subset' do
33
+ should 'return all other belongs_tos' do
34
+ assert_equal @hm1.belongs_tos.where('id <> ?', @bt1.id).all, BelongsTo.where(id: @bt1.id).relative_complement(@hm1.belongs_tos).all
35
+ end
36
+ end
37
+
38
+ context 'by empty set' do
39
+ should 'return all belongs_tos' do
40
+ assert_equal @hm1.belongs_tos.all, BelongsTo.where(id: -1).relative_complement(@hm1.belongs_tos)
41
+ end
42
+ end
43
+
44
+ context 'of empty set' do
45
+ should 'return nothing' do
46
+ assert_empty @hm1.belongs_tos.relative_complement(BelongsTo.where(id: -1))
47
+ end
48
+ end
49
+
50
+ context 'by unconditional' do
51
+ should 'return nothing' do
52
+ assert_empty BelongsTo.scoped.relative_complement(@hm1.belongs_tos)
53
+ end
54
+ end
55
+
56
+ context 'of unconditional' do
57
+ should 'return all belongs_tos' do
58
+ assert_equal @hm2.belongs_tos.all, @hm1.belongs_tos.relative_complement(BelongsTo.scoped)
59
+ end
60
+ end
61
+ end
62
+ end
metadata ADDED
@@ -0,0 +1,159 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: active-record-ex
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Arjun Kavi
8
+ - PagerDuty
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-10-31 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: bundler
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ">="
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ type: :development
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - ">="
26
+ - !ruby/object:Gem::Version
27
+ version: '0'
28
+ - !ruby/object:Gem::Dependency
29
+ name: rake
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - ">="
33
+ - !ruby/object:Gem::Version
34
+ version: '0'
35
+ type: :development
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - ">="
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ - !ruby/object:Gem::Dependency
43
+ name: shoulda
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: mocha
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ">="
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - ">="
68
+ - !ruby/object:Gem::Version
69
+ version: '0'
70
+ - !ruby/object:Gem::Dependency
71
+ name: activesupport
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - "~>"
75
+ - !ruby/object:Gem::Version
76
+ version: '3.2'
77
+ type: :runtime
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - "~>"
82
+ - !ruby/object:Gem::Version
83
+ version: '3.2'
84
+ - !ruby/object:Gem::Dependency
85
+ name: activerecord
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: '3.2'
91
+ type: :runtime
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: '3.2'
98
+ description: A library to make ActiveRecord::Relations even more awesome
99
+ email:
100
+ - arjun.kavi@gmail.com
101
+ - developers@pagerduty.com
102
+ executables: []
103
+ extensions: []
104
+ extra_rdoc_files: []
105
+ files:
106
+ - ".gitignore"
107
+ - ".travis.yml"
108
+ - Gemfile
109
+ - LICENSE
110
+ - README.md
111
+ - Rakefile
112
+ - active-record-ex.gemspec
113
+ - lib/active_record_ex.rb
114
+ - lib/active_record_ex/assoc_ordering.rb
115
+ - lib/active_record_ex/assume_destroy.rb
116
+ - lib/active_record_ex/many_to_many.rb
117
+ - lib/active_record_ex/nillable_find.rb
118
+ - lib/active_record_ex/polymorphic_build.rb
119
+ - lib/active_record_ex/relation_extensions.rb
120
+ - lib/active_record_ex/version.rb
121
+ - test/test_helper.rb
122
+ - test/unit/assoc_ordering_test.rb
123
+ - test/unit/assume_destroy_test.rb
124
+ - test/unit/many_to_many_test.rb
125
+ - test/unit/nillable_find_test.rb
126
+ - test/unit/polymorphic_build_test.rb
127
+ - test/unit/relation_extensions_test.rb
128
+ homepage: https://github.com/PagerDuty/active-record-ex
129
+ licenses:
130
+ - MIT
131
+ metadata: {}
132
+ post_install_message:
133
+ rdoc_options: []
134
+ require_paths:
135
+ - lib
136
+ required_ruby_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ required_rubygems_version: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ requirements: []
147
+ rubyforge_project:
148
+ rubygems_version: 2.2.2
149
+ signing_key:
150
+ specification_version: 4
151
+ summary: Relation -> Relation methods
152
+ test_files:
153
+ - test/test_helper.rb
154
+ - test/unit/assoc_ordering_test.rb
155
+ - test/unit/assume_destroy_test.rb
156
+ - test/unit/many_to_many_test.rb
157
+ - test/unit/nillable_find_test.rb
158
+ - test/unit/polymorphic_build_test.rb
159
+ - test/unit/relation_extensions_test.rb