active-record-ex 0.1.0

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