active_record-framing 0.1.0.pre.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +53 -0
- data/LICENSE +21 -0
- data/README.md +91 -0
- data/lib/active_record/framing/attribute_methods.rb +21 -0
- data/lib/active_record/framing/core_extension.rb +89 -0
- data/lib/active_record/framing/default.rb +160 -0
- data/lib/active_record/framing/named.rb +239 -0
- data/lib/active_record/framing/query_methods.rb +119 -0
- data/lib/active_record/framing/railtie.rb +19 -0
- data/lib/active_record/framing/relation.rb +162 -0
- data/lib/active_record/framing/spawn_methods.rb +13 -0
- data/lib/active_record/framing/version.rb +5 -0
- data/lib/active_record/framing.rb +3 -0
- metadata +148 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: db9fa84eb9a0af089cf82457843e6b46c05178f0dd3679253df766ba17755ec1
|
4
|
+
data.tar.gz: 37c02526ee91a9fbe029b9f4f740cac732738ddf9c18a189f7b6ba56a2614c10
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 734a4dff29f64d57563c622088256f8e3f12d992d4f4943c8eec959e47bab3e0ce91cf4058691e7b7a391cc98a4d35b689ec95c5ac6d15d13bdd69c5cb99fa04
|
7
|
+
data.tar.gz: 3abe8e57cf9d98312ab3e651fe48964854a03bb68e1577755249ad0dd33ba4dee615246229acf5c299d4593e8ea44ede1115a309b1de721ea259b5e7a02145f4
|
data/CHANGELOG.md
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
# DeletedAt
|
2
|
+
|
3
|
+
## 0.5.0 _(June 25, 2018)_
|
4
|
+
- Removed use of invasive views in preference of sub-selects
|
5
|
+
- Dropped support for Ruby 2.0, 2.1, 2.2
|
6
|
+
- Dropped support for Rails 4.1
|
7
|
+
- Default `active_record-framing` options using `Proc`
|
8
|
+
|
9
|
+
## 0.4.0 _(Never Released)_
|
10
|
+
- Specs for Rails 4.0-5.1
|
11
|
+
- Uses `combustion` gem for cleaner and more comprehensive testing
|
12
|
+
- Added badges to ReadMe
|
13
|
+
- Using `:prepend` to leverage ancestry chain
|
14
|
+
- Add logger for internal use
|
15
|
+
- DRYd up init code
|
16
|
+
- Removed partially supported features
|
17
|
+
- Added DSL in migrations/schema for adding `active_record-framing` timestamps to tables
|
18
|
+
|
19
|
+
## 0.3.0 _(May 10, 2017)_
|
20
|
+
- Add specs
|
21
|
+
- Clean up dependencies
|
22
|
+
- Auto-init models after installing views
|
23
|
+
- Remove chained `create` methods
|
24
|
+
|
25
|
+
## 0.2.6 _(April 06, 2017)_
|
26
|
+
- Add warning when no DB connection present
|
27
|
+
|
28
|
+
## 0.2.5 _(March 28, 2017)_
|
29
|
+
- Extract injections to `.load` method
|
30
|
+
|
31
|
+
## 0.2.4 _(February 03, 2017)_
|
32
|
+
- Use `becomes` to mask `::All` etc classes
|
33
|
+
|
34
|
+
## 0.2.3 _(February 03, 2017)_
|
35
|
+
- Chain `create!` method to work properly
|
36
|
+
|
37
|
+
## 0.2.2 _(February 03, 2017)_
|
38
|
+
- Chain `create` method to work properly
|
39
|
+
|
40
|
+
## 0.2.1 _(February 03, 2017)_
|
41
|
+
- More reliable table name handling
|
42
|
+
- Changed API for installing views (e.g. `destroy_deleted_view`, `uninstall_deleted_view`)
|
43
|
+
|
44
|
+
## 0.1.1 _(January 31, 2017)_
|
45
|
+
- Added instructions to readme
|
46
|
+
- Fixes stack-too-deep edge-case (by moving to `:include` over `:prepend`)
|
47
|
+
|
48
|
+
## 0.1.0 _(January 30, 2017)_
|
49
|
+
- Renames primary table to `model_name/all`
|
50
|
+
- Creates views for each model using `active_record-framing`
|
51
|
+
- `model_name/deleted`
|
52
|
+
- `model_name/present`
|
53
|
+
- Classes created to read from views (`::All`, `::Present`, `::Deleted`)
|
data/LICENSE
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
The MIT License (MIT)
|
2
|
+
|
3
|
+
Copyright (c) 2019 Dale Stevens
|
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
|
13
|
+
all 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
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
[]()
|
2
|
+
[](https://rubygems.org/gems/active_record-framing)
|
3
|
+
[](https://travis-ci.org/TwilightCoders/active_record-framing)
|
4
|
+
[](https://codeclimate.com/github/TwilightCoders/active_record-framing/maintainability)
|
5
|
+
[](https://codeclimate.com/github/TwilightCoders/active_record-framing/coverage)
|
6
|
+
[](https://depfu.com/github/TwilightCoders/active_record-framing)
|
7
|
+
|
8
|
+
# ActiveRecord::Framing
|
9
|
+
|
10
|
+
Works similar to `scopes`. Rather than modifying the where clause of the `ActiveRecord::Relation`, it creates a common table expression (CTE) to be applied upon execution.
|
11
|
+
|
12
|
+
Unlike scopes, they do not affect the values of attributes upon creation.
|
13
|
+
|
14
|
+
## Requirements
|
15
|
+
|
16
|
+
- Ruby 2.3+
|
17
|
+
- ActiveRecord 4.2+
|
18
|
+
|
19
|
+
## Installation
|
20
|
+
|
21
|
+
Add this line to your application's Gemfile:
|
22
|
+
|
23
|
+
```ruby
|
24
|
+
gem 'active_record-framing'
|
25
|
+
```
|
26
|
+
|
27
|
+
And then execute:
|
28
|
+
|
29
|
+
$ bundle
|
30
|
+
|
31
|
+
Or install it yourself as:
|
32
|
+
|
33
|
+
$ gem install active_record-framing
|
34
|
+
|
35
|
+
## Usage
|
36
|
+
|
37
|
+
Any `ActiveRecord::Base` descendant has access to two additional methods: `frame` and `default_frame`.
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
class User < ActiveRecord::Base
|
41
|
+
default_frame { where(active: true) }
|
42
|
+
# ...
|
43
|
+
end
|
44
|
+
```
|
45
|
+
Afterwards, `User.all.to_sql` yields
|
46
|
+
```sql
|
47
|
+
WITH "users" AS
|
48
|
+
(SELECT "users".* FROM "users" WHERE "users"."active" = true)
|
49
|
+
SELECT "users".* FROM "users"
|
50
|
+
```
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
class Admin < User
|
54
|
+
default_frame('admins') { where(kind: 1) }
|
55
|
+
# ...
|
56
|
+
end
|
57
|
+
```
|
58
|
+
|
59
|
+
Afterwards, `Admin.all.to_sql` yields
|
60
|
+
```sql
|
61
|
+
WITH "admins" AS
|
62
|
+
(SELECT "users".* )
|
63
|
+
```
|
64
|
+
|
65
|
+
If you're starting with a brand-new table, the existing `timestamps` DSL has been extended to accept `deleted_at: true` as an option, for convenience. Or you can do it seperately as shown above.
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
class CreatCommentsTable < ActiveRecord::Migration
|
69
|
+
|
70
|
+
def change
|
71
|
+
create_table :comments do |t|
|
72
|
+
# ...
|
73
|
+
# to the `timestamps` DSL
|
74
|
+
t.timestamps null: false, deleted_at: true
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
end
|
79
|
+
```
|
80
|
+
|
81
|
+
## Development
|
82
|
+
|
83
|
+
After checking out the repo, run `bundle` to install dependencies. Then, run `bundle exec rspec` to run the tests.
|
84
|
+
|
85
|
+
## Contributing
|
86
|
+
|
87
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/TwilightCoders/active_record-framing. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
88
|
+
|
89
|
+
## License
|
90
|
+
|
91
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Framing
|
3
|
+
module AttributeMethods
|
4
|
+
extend ActiveSupport::Concern
|
5
|
+
|
6
|
+
BLACKLISTED_CLASS_CONSTS = %w(unframed)
|
7
|
+
|
8
|
+
module ClassMethods
|
9
|
+
# A class const is 'dangerous' if it is already defined by Active Record, but
|
10
|
+
# not by any ancestors. (So 'All' is not dangerous but 'Frameless' is.)
|
11
|
+
def dangerous_class_const?(const_name)
|
12
|
+
BLACKLISTED_CLASS_CONSTS.include?(const_name.to_s) || class_const_defined_within?(const_name, Base)
|
13
|
+
end
|
14
|
+
|
15
|
+
def class_const_defined_within?(name, klass, superklass = klass.superclass) # :nodoc:
|
16
|
+
klass.const_defined?(name) || superklass.const_defined?(name)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "active_support/per_thread_registry"
|
4
|
+
|
5
|
+
module ActiveRecord
|
6
|
+
module Framing
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
included do
|
10
|
+
include Default
|
11
|
+
include Named
|
12
|
+
include AttributeMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module ClassMethods # :nodoc:
|
16
|
+
def current_frame
|
17
|
+
FrameRegistry.value_for(:current_frame, self)
|
18
|
+
end
|
19
|
+
|
20
|
+
def current_frame=(frame)
|
21
|
+
FrameRegistry.set_value_for(:current_frame, self, frame)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# This class stores the +:current_frame+ and +:ignore_default_frame+ values
|
26
|
+
# for different classes. The registry is stored as a thread local, which is
|
27
|
+
# accessed through +FrameRegistry.current+.
|
28
|
+
#
|
29
|
+
# This class allows you to store and get the frame values on different
|
30
|
+
# classes and different types of frames. For example, if you are attempting
|
31
|
+
# to get the current_frame for the +Board+ model, then you would use the
|
32
|
+
# following code:
|
33
|
+
#
|
34
|
+
# registry = ActiveRecord::Framing::FrameRegistry
|
35
|
+
# registry.set_value_for(:current_frame, Board, some_new_frame)
|
36
|
+
#
|
37
|
+
# Now when you run:
|
38
|
+
#
|
39
|
+
# registry.value_for(:current_frame, Board)
|
40
|
+
#
|
41
|
+
# You will obtain whatever was defined in +some_new_frame+. The #value_for
|
42
|
+
# and #set_value_for methods are delegated to the current FrameRegistry
|
43
|
+
# object, so the above example code can also be called as:
|
44
|
+
#
|
45
|
+
# ActiveRecord::Framing::FrameRegistry.set_value_for(:current_frame,
|
46
|
+
# Board, some_new_frame)
|
47
|
+
class FrameRegistry # :nodoc:
|
48
|
+
extend ActiveSupport::PerThreadRegistry
|
49
|
+
|
50
|
+
VALID_SCOPE_TYPES = [:current_frame, :ignore_default_frame]
|
51
|
+
|
52
|
+
def initialize
|
53
|
+
@registry = Hash.new { |hash, key| hash[key] = {} }
|
54
|
+
end
|
55
|
+
|
56
|
+
# Obtains the value for a given +frame_type+ and +model+.
|
57
|
+
def value_for(frame_type, model)
|
58
|
+
raise_invalid_frame_type!(frame_type)
|
59
|
+
return @registry[frame_type][model.name]
|
60
|
+
end
|
61
|
+
|
62
|
+
# def value_for(frame_type, model, skip_inherited_frame = false)
|
63
|
+
# raise_invalid_frame_type!(frame_type)
|
64
|
+
# return @registry[frame_type][model.name] if skip_inherited_frame
|
65
|
+
# klass = model
|
66
|
+
# base = model.base_class
|
67
|
+
# while klass <= base
|
68
|
+
# value = @registry[frame_type][klass.name]
|
69
|
+
# return value if value
|
70
|
+
# klass = klass.superclass
|
71
|
+
# end
|
72
|
+
# end
|
73
|
+
|
74
|
+
# Sets the +value+ for a given +frame_type+ and +model+.
|
75
|
+
def set_value_for(frame_type, model, value)
|
76
|
+
raise_invalid_frame_type!(frame_type)
|
77
|
+
@registry[frame_type][model.name] = value
|
78
|
+
end
|
79
|
+
|
80
|
+
private
|
81
|
+
|
82
|
+
def raise_invalid_frame_type!(frame_type)
|
83
|
+
if !VALID_SCOPE_TYPES.include?(frame_type)
|
84
|
+
raise ArgumentError, "Invalid frame type '#{frame_type}' sent to the registry. Frame types must be included in VALID_SCOPE_TYPES"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
module Framing
|
5
|
+
module Default
|
6
|
+
extend ActiveSupport::Concern
|
7
|
+
|
8
|
+
included do
|
9
|
+
# Stores the default frame for the class.
|
10
|
+
class_attribute :default_frames, instance_writer: false, instance_predicate: false
|
11
|
+
class_attribute :default_frame_override, instance_writer: false, instance_predicate: false
|
12
|
+
|
13
|
+
self.default_frames = []
|
14
|
+
self.default_frame_override = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
# Returns a frame for the model without the previously set frames.
|
19
|
+
#
|
20
|
+
# class Post < ActiveRecord::Base
|
21
|
+
# def self.default_frame
|
22
|
+
# where(published: true)
|
23
|
+
# end
|
24
|
+
# end
|
25
|
+
#
|
26
|
+
# Post.all # Fires "WITH posts AS (SELECT * FROM posts WHERE published = true) SELECT * FROM posts"
|
27
|
+
# Post.unframed.all # Fires "SELECT * FROM posts"
|
28
|
+
# Post.where(published: false).unframed.all # Fires "SELECT * FROM posts"
|
29
|
+
#
|
30
|
+
# This method also accepts a block. All queries inside the block will
|
31
|
+
# not use the previously set frames.
|
32
|
+
#
|
33
|
+
# Post.unframed {
|
34
|
+
# Post.limit(10) # Fires "SELECT * FROM posts LIMIT 10"
|
35
|
+
# }
|
36
|
+
def unframed
|
37
|
+
block_given? ? relation.framing { yield } : relation
|
38
|
+
end
|
39
|
+
|
40
|
+
# Are there attributes associated with this frame?
|
41
|
+
def frame_attributes? # :nodoc:
|
42
|
+
super || default_frames.any? || respond_to?(:default_frame)
|
43
|
+
end
|
44
|
+
|
45
|
+
def before_remove_const #:nodoc:
|
46
|
+
self.current_frame = nil
|
47
|
+
end
|
48
|
+
|
49
|
+
def ignore_default_frame?
|
50
|
+
FrameRegistry.value_for(:ignore_default_frame, base_class)
|
51
|
+
end
|
52
|
+
|
53
|
+
private
|
54
|
+
|
55
|
+
# Use this macro in your model to set a default frame for all operations on
|
56
|
+
# the model.
|
57
|
+
#
|
58
|
+
# class Article < ActiveRecord::Base
|
59
|
+
# default_frame { where(published: true) }
|
60
|
+
# end
|
61
|
+
#
|
62
|
+
# Article.all # => # Fires "WITH articles AS (SELECT * FROM articles WHERE published = true) SELECT * FROM articles"
|
63
|
+
#
|
64
|
+
# The #default_frame is not applied while updating/creating/building a record.
|
65
|
+
#
|
66
|
+
# Article.new.published # => nil
|
67
|
+
# Article.create.published # => nil
|
68
|
+
# Article.first.update(name: 'A Tale of Two Cities').published # => nil
|
69
|
+
#
|
70
|
+
# (You can also pass any object which responds to +call+ to the
|
71
|
+
# +default_frame+ macro, and it will be called when building the
|
72
|
+
# default frame.)
|
73
|
+
#
|
74
|
+
# If you use multiple #default_frame declarations in your model then
|
75
|
+
# they will be merged together:
|
76
|
+
#
|
77
|
+
# class Article < ActiveRecord::Base
|
78
|
+
# default_frame { where(published: true) }
|
79
|
+
# default_frame { where(rating: 'G') }
|
80
|
+
# end
|
81
|
+
#
|
82
|
+
# Article.all # => WITH articles AS (SELECT * FROM articles WHERE published = true AND rating = 'G') SELECT * FROM articles
|
83
|
+
#
|
84
|
+
# This is also the case with inheritance and module includes where the
|
85
|
+
# parent or module defines a #default_frame and the child or including
|
86
|
+
# class defines a second one.
|
87
|
+
#
|
88
|
+
# If you need to do more complex things with a default frame, you can
|
89
|
+
# alternatively define it as a class method:
|
90
|
+
#
|
91
|
+
# class Article < ActiveRecord::Base
|
92
|
+
# def self.default_frame
|
93
|
+
# # Should return a frame, you can call 'super' here etc.
|
94
|
+
# end
|
95
|
+
# end
|
96
|
+
def default_frame(frame = nil) # :doc:
|
97
|
+
frame = Proc.new if block_given?
|
98
|
+
|
99
|
+
if frame.is_a?(Relation) || !frame.respond_to?(:call)
|
100
|
+
raise ArgumentError,
|
101
|
+
"Support for calling #default_frame without a block is removed. For example instead " \
|
102
|
+
"of `default_frame where(color: 'red')`, please use " \
|
103
|
+
"`default_frame { where(color: 'red') }`. (Alternatively you can just redefine " \
|
104
|
+
"self.default_frame.)"
|
105
|
+
end
|
106
|
+
|
107
|
+
self.default_frames += [frame]
|
108
|
+
end
|
109
|
+
|
110
|
+
def build_default_frame(base_rel = nil)
|
111
|
+
return if abstract_class?
|
112
|
+
|
113
|
+
if default_frame_override.nil?
|
114
|
+
self.default_frame_override = !Base.is_a?(method(:default_frame).owner)
|
115
|
+
end
|
116
|
+
|
117
|
+
if default_frame_override
|
118
|
+
# The user has defined their own default frame method, so call that
|
119
|
+
# evaluate_default_frame do
|
120
|
+
# if frame = default_frame
|
121
|
+
# (base_rel ||= relation).merge!(frame)
|
122
|
+
# end
|
123
|
+
# end
|
124
|
+
warn "come back to me!"
|
125
|
+
elsif default_frames.any?
|
126
|
+
# cte_table = arel_table
|
127
|
+
cte_table = Arel::Table.new(table_name)
|
128
|
+
|
129
|
+
evaluate_default_frame do
|
130
|
+
# Create CTE here
|
131
|
+
|
132
|
+
cte_relation = default_frames.inject(relation) do |default_frame, frame|
|
133
|
+
frame = frame.respond_to?(:to_proc) ? frame : frame.method(:call)
|
134
|
+
default_frame.merge!(relation.instance_exec(&frame))
|
135
|
+
end
|
136
|
+
|
137
|
+
base_rel ||= relation
|
138
|
+
base_rel.frame!(Arel::Nodes::As.new(Arel::Table.new(table_name), cte_relation.arel))# if cte_relation
|
139
|
+
end
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
def ignore_default_frame=(ignore)
|
144
|
+
FrameRegistry.set_value_for(:ignore_default_frame, base_class, ignore)
|
145
|
+
end
|
146
|
+
|
147
|
+
# The ignore_default_frame flag is used to prevent an infinite recursion
|
148
|
+
# situation where a default frame references a frame which has a default
|
149
|
+
# frame which references a frame...
|
150
|
+
def evaluate_default_frame
|
151
|
+
return if ignore_default_frame?
|
152
|
+
self.ignore_default_frame = true
|
153
|
+
yield
|
154
|
+
ensure
|
155
|
+
self.ignore_default_frame = false
|
156
|
+
end
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,239 @@
|
|
1
|
+
require 'delegate'
|
2
|
+
|
3
|
+
module ActiveRecord
|
4
|
+
# = Active Record \Named \Frames
|
5
|
+
module Framing
|
6
|
+
module Named
|
7
|
+
extend ActiveSupport::Concern
|
8
|
+
|
9
|
+
module ClassMethods
|
10
|
+
# Returns an ActiveRecord::Relation frame object.
|
11
|
+
#
|
12
|
+
# posts = Post.all
|
13
|
+
# posts.size # Fires "select count(*) from posts" and returns the count
|
14
|
+
# posts.each {|p| puts p.name } # Fires "select * from posts" and loads post objects
|
15
|
+
#
|
16
|
+
# fruits = Fruit.all
|
17
|
+
# fruits = fruits.where(color: 'red') if options[:red_only]
|
18
|
+
# fruits = fruits.limit(10) if limited?
|
19
|
+
#
|
20
|
+
# You can define a frame that applies to all finders using
|
21
|
+
# {default_frame}[rdoc-ref:Framing::Default::ClassMethods#default_frame].
|
22
|
+
# def all
|
23
|
+
# super.tap do |rel|
|
24
|
+
# if frame = framed_all
|
25
|
+
# rel.with(frame)
|
26
|
+
# end
|
27
|
+
# end
|
28
|
+
# end
|
29
|
+
|
30
|
+
# alias this method?, framed_all
|
31
|
+
# def all
|
32
|
+
# # if current_scope
|
33
|
+
# # current_scope.clone
|
34
|
+
# # else
|
35
|
+
# # default_scoped
|
36
|
+
# # end
|
37
|
+
# if current_frame
|
38
|
+
# puts "#{name} has a current_frame: #{current_frame.to_sql}"
|
39
|
+
# super.frame(current_frame.clone)
|
40
|
+
# else
|
41
|
+
# puts "#{name} is using a default_frame: #{default_framed.to_sql}" if default_framed
|
42
|
+
# super.frame(default_framed)
|
43
|
+
# end
|
44
|
+
# end
|
45
|
+
|
46
|
+
def all
|
47
|
+
framed_all(super)
|
48
|
+
end
|
49
|
+
|
50
|
+
def framed_all(rel)
|
51
|
+
if current_frame = self.current_frame
|
52
|
+
if self == current_frame.klass
|
53
|
+
current_frame.clone
|
54
|
+
else
|
55
|
+
rel.merge!(current_frame)
|
56
|
+
end
|
57
|
+
else
|
58
|
+
default_framed(rel)
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
def const_missing(const_name)
|
63
|
+
registered_frames[const_name]&.call() || super
|
64
|
+
end
|
65
|
+
|
66
|
+
def registered_frames
|
67
|
+
@registered_frames ||= {}
|
68
|
+
end
|
69
|
+
|
70
|
+
def frame_for_association(frame = relation) # :nodoc:
|
71
|
+
current_frame = self.current_frame
|
72
|
+
|
73
|
+
if current_frame && current_frame.empty_frame?
|
74
|
+
frame
|
75
|
+
else
|
76
|
+
default_framed(frame)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# def default_framed(frame = relation) # :nodoc:
|
81
|
+
def default_framed(frame = nil) # :nodoc:
|
82
|
+
!ignore_default_frame? && build_default_frame(frame) || frame
|
83
|
+
end
|
84
|
+
|
85
|
+
# Adds a class method for retrieving and querying objects.
|
86
|
+
# The method is intended to return an ActiveRecord::Relation
|
87
|
+
# object, which is composable with other frames.
|
88
|
+
# If it returns +nil+ or +false+, an
|
89
|
+
# {all}[rdoc-ref:Framing::Named::ClassMethods#all] frame is returned instead.
|
90
|
+
#
|
91
|
+
# A \frame represents a narrowing of a database query, such as
|
92
|
+
# <tt>where(color: :red).select('shirts.*').includes(:washing_instructions)</tt>.
|
93
|
+
#
|
94
|
+
# class Shirt < ActiveRecord::Base
|
95
|
+
# frame :red, -> { where(color: 'red') }
|
96
|
+
# frame :dry_clean_only, -> { joins(:washing_instructions).where('washing_instructions.dry_clean_only = ?', true) }
|
97
|
+
# end
|
98
|
+
#
|
99
|
+
# The above calls to #frame define class methods <tt>Shirt.red</tt> and
|
100
|
+
# <tt>Shirt::DryCleanOnly</tt>. <tt>Shirt::Red</tt>, in effect,
|
101
|
+
# represents the query <tt>Shirt.where(color: 'red')</tt>.
|
102
|
+
#
|
103
|
+
# You should always pass a callable object to the frames defined
|
104
|
+
# with #frame. This ensures that the frame is re-evaluated each
|
105
|
+
# time it is called.
|
106
|
+
#
|
107
|
+
# Note that this is simply 'syntactic sugar' for defining an actual
|
108
|
+
# class method:
|
109
|
+
#
|
110
|
+
# class Shirt < ActiveRecord::Base
|
111
|
+
# def self.red
|
112
|
+
# where(color: 'red')
|
113
|
+
# end
|
114
|
+
# end
|
115
|
+
#
|
116
|
+
# Unlike <tt>Shirt.find(...)</tt>, however, the object returned by
|
117
|
+
# <tt>Shirt.red</tt> is not an Array but an ActiveRecord::Relation,
|
118
|
+
# which is composable with other frames; it resembles the association object
|
119
|
+
# constructed by a {has_many}[rdoc-ref:Associations::ClassMethods#has_many]
|
120
|
+
# declaration. For instance, you can invoke <tt>Shirt.red.first</tt>, <tt>Shirt.red.count</tt>,
|
121
|
+
# <tt>Shirt.red.where(size: 'small')</tt>. Also, just as with the
|
122
|
+
# association objects, named \frames act like an Array, implementing
|
123
|
+
# Enumerable; <tt>Shirt.red.each(&block)</tt>, <tt>Shirt.red.first</tt>,
|
124
|
+
# and <tt>Shirt.red.inject(memo, &block)</tt> all behave as if
|
125
|
+
# <tt>Shirt.red</tt> really was an array.
|
126
|
+
#
|
127
|
+
# These named \frames are composable. For instance,
|
128
|
+
# <tt>Shirt.red.dry_clean_only</tt> will produce all shirts that are
|
129
|
+
# both red and dry clean only. Nested finds and calculations also work
|
130
|
+
# with these compositions: <tt>Shirt.red.dry_clean_only.count</tt>
|
131
|
+
# returns the number of garments for which these criteria obtain.
|
132
|
+
# Similarly with <tt>Shirt.red.dry_clean_only.average(:thread_count)</tt>.
|
133
|
+
#
|
134
|
+
# All frames are available as class methods on the ActiveRecord::Base
|
135
|
+
# descendant upon which the \frames were defined. But they are also
|
136
|
+
# available to {has_many}[rdoc-ref:Associations::ClassMethods#has_many]
|
137
|
+
# associations. If,
|
138
|
+
#
|
139
|
+
# class Person < ActiveRecord::Base
|
140
|
+
# has_many :shirts
|
141
|
+
# end
|
142
|
+
#
|
143
|
+
# then <tt>elton.shirts.red.dry_clean_only</tt> will return all of
|
144
|
+
# Elton's red, dry clean only shirts.
|
145
|
+
#
|
146
|
+
# \Named frames can also have extensions, just as with
|
147
|
+
# {has_many}[rdoc-ref:Associations::ClassMethods#has_many] declarations:
|
148
|
+
#
|
149
|
+
# class Shirt < ActiveRecord::Base
|
150
|
+
# frame :red, -> { where(color: 'red') } do
|
151
|
+
# def dom_id
|
152
|
+
# 'red_shirts'
|
153
|
+
# end
|
154
|
+
# end
|
155
|
+
# end
|
156
|
+
#
|
157
|
+
# Frames cannot be used while creating/building a record.
|
158
|
+
#
|
159
|
+
# class Article < ActiveRecord::Base
|
160
|
+
# frame :published, -> { where(published: true) }
|
161
|
+
# end
|
162
|
+
#
|
163
|
+
# Article.published.new.published # => nil
|
164
|
+
# Article.published.create.published # => nil
|
165
|
+
#
|
166
|
+
# \Class methods on your model are automatically available
|
167
|
+
# on frames. Assuming the following setup:
|
168
|
+
#
|
169
|
+
# class Article < ActiveRecord::Base
|
170
|
+
# frame :published, -> { where(published: true) }
|
171
|
+
# frame :featured, -> { where(featured: true) }
|
172
|
+
#
|
173
|
+
# def self.latest_article
|
174
|
+
# order('published_at desc').first
|
175
|
+
# end
|
176
|
+
#
|
177
|
+
# def self.titles
|
178
|
+
# pluck(:title)
|
179
|
+
# end
|
180
|
+
# end
|
181
|
+
#
|
182
|
+
# We are able to call the methods like this:
|
183
|
+
#
|
184
|
+
# Article.published.featured.latest_article
|
185
|
+
# Article.featured.titles
|
186
|
+
def frame(frame_name, body, &block)
|
187
|
+
unless body.respond_to?(:call)
|
188
|
+
raise ArgumentError, "The frame body needs to be callable."
|
189
|
+
end
|
190
|
+
|
191
|
+
constant = frame_name.to_s.classify.to_sym
|
192
|
+
arel_tn = "#{frame_name}/#{self.table_name}"
|
193
|
+
|
194
|
+
the_frame = body.respond_to?(:to_proc) ? body : body.method(:call)
|
195
|
+
cte_relation = relation.merge!(relation.instance_exec(&the_frame) || relation)
|
196
|
+
|
197
|
+
# self.const_set constant, Class.new(DelegateClass(self)) do |klass|
|
198
|
+
delegator = self.name.to_sym
|
199
|
+
self.const_set(constant, self.dup).class_eval do |klass|
|
200
|
+
extend SingleForwardable
|
201
|
+
def_delegator delegator, :type_caster
|
202
|
+
def_delegator delegator, :table_name
|
203
|
+
|
204
|
+
klass.default_frames = []
|
205
|
+
|
206
|
+
@arel_table = klass.arel_table.dup.tap do |at|
|
207
|
+
at.name = arel_tn
|
208
|
+
end
|
209
|
+
|
210
|
+
klass.current_frame = build_frame(cte_relation, &block)
|
211
|
+
end
|
212
|
+
|
213
|
+
if dangerous_class_const?(constant)
|
214
|
+
raise ArgumentError, "You tried to define a frame named \"#{constant}\" " \
|
215
|
+
"on the model \"#{self.constant}\", but Active Record already defined " \
|
216
|
+
"a class method with the same name."
|
217
|
+
end
|
218
|
+
|
219
|
+
end
|
220
|
+
|
221
|
+
def build_frame(frame, &block)
|
222
|
+
extension = Module.new(&block) if block
|
223
|
+
relation.frame!(Arel::Nodes::As.new(arel_table, frame.arel)).tap do |rel|
|
224
|
+
rel.extending!(extension) if extension
|
225
|
+
end
|
226
|
+
end
|
227
|
+
|
228
|
+
private
|
229
|
+
|
230
|
+
def valid_frame_name?(name)
|
231
|
+
if respond_to?(name, true) && logger
|
232
|
+
logger.warn "Creating frame :#{name}. " \
|
233
|
+
"Overwriting existing method #{self.name}.#{name}."
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
end
|
238
|
+
end
|
239
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
module Framing
|
3
|
+
module QueryMethods
|
4
|
+
|
5
|
+
if ::ActiveRecord.version >= Gem::Version.new("5.1") # 5.1+
|
6
|
+
def frames_values
|
7
|
+
get_value(:frames)
|
8
|
+
end
|
9
|
+
def frames_values=(value)
|
10
|
+
set_value(:frames, value)
|
11
|
+
end
|
12
|
+
::ActiveRecord::Relation::DEFAULT_VALUES[:frames] = ::ActiveRecord::Relation::FROZEN_EMPTY_HASH
|
13
|
+
elsif ::ActiveRecord.version >= Gem::Version.new("5.0") # 5.0+
|
14
|
+
def frames_values
|
15
|
+
@values[:frames] || ::ActiveRecord::Relation::FROZEN_EMPTY_HASH
|
16
|
+
end
|
17
|
+
def frames_values=(values)
|
18
|
+
assert_mutability!
|
19
|
+
@values[:frames] = values
|
20
|
+
end
|
21
|
+
elsif ::ActiveRecord.version >= Gem::Version.new("4.2") # 4.2+
|
22
|
+
def frames_values
|
23
|
+
@values[:frames] || {}
|
24
|
+
end
|
25
|
+
def frames_values=(values)
|
26
|
+
raise ImmutableRelation if @loaded
|
27
|
+
check_cached_relation
|
28
|
+
@values[:frames] = values
|
29
|
+
end
|
30
|
+
else
|
31
|
+
raise NotImplementedError, "ActiveRecord::Framing does not support Rails #{::ActiveRecord.version}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def from!(value, subquery_name = nil) # :nodoc:
|
35
|
+
super.tap do |rel|
|
36
|
+
frames_values = frames_values.merge(value.frames_values) if value.is_a?(::ActiveRecord::Relation)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def frame(value)
|
41
|
+
spawn.frame!(value)
|
42
|
+
end
|
43
|
+
|
44
|
+
def frame!(value)
|
45
|
+
if key = frame_key(value)
|
46
|
+
self.frames_values = self.frames_values.merge(key => value)
|
47
|
+
end
|
48
|
+
self
|
49
|
+
end
|
50
|
+
|
51
|
+
# Removes an unwanted relation that is already defined on a chain of relations.
|
52
|
+
# This is useful when passing around chains of relations and would like to
|
53
|
+
# modify the relations without reconstructing the entire chain.
|
54
|
+
#
|
55
|
+
# User.order('email DESC').unframe(:order) == User.all
|
56
|
+
#
|
57
|
+
# The method arguments are symbols which correspond to the names of the methods
|
58
|
+
# which should be unframed. The valid arguments are given in VALID_UNSCOPING_VALUES.
|
59
|
+
# The method can also be called with multiple arguments. For example:
|
60
|
+
#
|
61
|
+
# User.order('email DESC').select('id').where(name: "John")
|
62
|
+
# .unframe(:order, :select, :where) == User.all
|
63
|
+
#
|
64
|
+
# One can additionally pass a hash as an argument to unframe specific +:where+ values.
|
65
|
+
# This is done by passing a hash with a single key-value pair. The key should be
|
66
|
+
# +:where+ and the value should be the where value to unframe. For example:
|
67
|
+
#
|
68
|
+
# User.where(name: "John", active: true).unframe(where: :name)
|
69
|
+
# == User.where(active: true)
|
70
|
+
#
|
71
|
+
# This method is similar to #except, but unlike
|
72
|
+
# #except, it persists across merges:
|
73
|
+
#
|
74
|
+
# User.order('email').merge(User.except(:order))
|
75
|
+
# == User.order('email')
|
76
|
+
#
|
77
|
+
# User.order('email').merge(User.unframe(:order))
|
78
|
+
# == User.all
|
79
|
+
#
|
80
|
+
# This means it can be used in association definitions:
|
81
|
+
#
|
82
|
+
# has_many :comments, -> { unframe(where: :trashed) }
|
83
|
+
#
|
84
|
+
# def unframe(*args)
|
85
|
+
# check_if_method_has_arguments!(:unframe, args)
|
86
|
+
# spawn.unframe!(*args)
|
87
|
+
# end
|
88
|
+
|
89
|
+
# def unframe!(*args) # :nodoc:
|
90
|
+
# args.flatten!
|
91
|
+
# self.unframe_values += args
|
92
|
+
|
93
|
+
# args.each do |frame|
|
94
|
+
# case frame
|
95
|
+
# when Symbol
|
96
|
+
# frame = :left_outer_joins if frame == :left_joins
|
97
|
+
# if !VALID_UNSCOPING_VALUES.include?(frame)
|
98
|
+
# raise ArgumentError, "Called unframe() with invalid unframing argument ':#{frame}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
99
|
+
# end
|
100
|
+
# set_value(frame, DEFAULT_VALUES[frame])
|
101
|
+
# when Hash
|
102
|
+
# frame.each do |key, target_value|
|
103
|
+
# if key != :where
|
104
|
+
# raise ArgumentError, "Hash arguments in .unframe(*args) must have :where as the key."
|
105
|
+
# end
|
106
|
+
|
107
|
+
# target_values = Array(target_value).map(&:to_s)
|
108
|
+
# self.where_clause = where_clause.except(*target_values)
|
109
|
+
# end
|
110
|
+
# else
|
111
|
+
# raise ArgumentError, "Unrecognized framing: #{args.inspect}. Use .unframe(where: :attribute_name) or .unframe(:order), for example."
|
112
|
+
# end
|
113
|
+
# end
|
114
|
+
|
115
|
+
# self
|
116
|
+
# end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
require 'rails/railtie'
|
2
|
+
require 'active_record/framing/core_extension'
|
3
|
+
require 'active_record/framing/query_methods'
|
4
|
+
require 'active_record/framing/spawn_methods'
|
5
|
+
require 'active_record/framing/attribute_methods'
|
6
|
+
require 'active_record/framing/relation'
|
7
|
+
|
8
|
+
module ActiveRecord::Framing
|
9
|
+
class Railtie < Rails::Railtie
|
10
|
+
initializer 'active_record-framing.load' do |_app|
|
11
|
+
ActiveSupport.on_load(:active_record) do
|
12
|
+
::ActiveRecord::Base.include(ActiveRecord::Framing)
|
13
|
+
::ActiveRecord::Relation.prepend(ActiveRecord::Framing::Relation)
|
14
|
+
::ActiveRecord::Relation.include(ActiveRecord::Framing::QueryMethods)
|
15
|
+
::ActiveRecord::Relation.prepend(ActiveRecord::Framing::SpawnMethods)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,162 @@
|
|
1
|
+
module ActiveRecord
|
2
|
+
# = Active Record \Relation
|
3
|
+
module Framing
|
4
|
+
module Relation
|
5
|
+
|
6
|
+
# def arel_without_frames
|
7
|
+
# klass.ignore_default_frame
|
8
|
+
# Thread.currently(:without_frames, true) do
|
9
|
+
# @arel_without_frames ||= build_arel_without_frames
|
10
|
+
# end
|
11
|
+
# end
|
12
|
+
|
13
|
+
# def build_arel_without_frames
|
14
|
+
# @arel, old = nil, @arel
|
15
|
+
# arel
|
16
|
+
# ensure
|
17
|
+
# @arel = old
|
18
|
+
# end
|
19
|
+
|
20
|
+
def build_arel(*)
|
21
|
+
super.tap do |ar|
|
22
|
+
unless ignore_default_frame?
|
23
|
+
# alias_tracker.aliased_table_for(
|
24
|
+
# reflection.table_name,
|
25
|
+
# table_alias_for(reflection, parent, reflection != node.reflection),
|
26
|
+
# reflection.klass.type_caster
|
27
|
+
# )
|
28
|
+
build_frames(ar)
|
29
|
+
|
30
|
+
frames_values.each do |k,v|
|
31
|
+
puts "#{k} => #{v.to_sql}"
|
32
|
+
end
|
33
|
+
|
34
|
+
ar.with(*frames_values.values) if frames_values.any?
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# This is all very unfortunate (rails 4.2):
|
40
|
+
# ActiveRecord, in it's infinite wisdom, has decided
|
41
|
+
# to create JoinDependency objects with arel_tables using a
|
42
|
+
# generic engine (ActiveRecord::Base) as opposed to that of
|
43
|
+
# the driving class. For example:
|
44
|
+
#
|
45
|
+
# #<Arel::Nodes::InnerJoin:0x00007f974e6eb008
|
46
|
+
# @left=
|
47
|
+
# #<Arel::Table:0x00007f97508f42f8
|
48
|
+
# @aliases=[],
|
49
|
+
# @columns=nil,
|
50
|
+
# @engine=ActiveRecord::Base, <=== Problem, should be `User`
|
51
|
+
# @name="users",
|
52
|
+
# @primary_key=nil,
|
53
|
+
# @table_alias=nil>,
|
54
|
+
# NOTE: In Rails 5.2 (at least) we could use the InnerJoin.left.type_caster
|
55
|
+
def build_frames(manager)
|
56
|
+
join_names = manager.join_sources.collect do |source|
|
57
|
+
source.left.name.to_s # TODO: Need to_s?
|
58
|
+
end
|
59
|
+
|
60
|
+
# scopes = klass.reflections.slice(*join_names).values.inject(Hash.new) do |collector, assoc|
|
61
|
+
# NOTE: We cannot early exclude associations because some associations are different from their table names
|
62
|
+
klass.reflect_on_all_associations.each do |assoc|
|
63
|
+
# collector.merge(assoc.klass.default_scopes) if join_names.include?(assoc.table_name)
|
64
|
+
# (collector[assoc.klass] ||= Set.new).merge(assoc.klass.default_scopes) if join_names.include?(assoc.table_name) && assoc.klass.default_scopes.any?
|
65
|
+
if join_names.include?(assoc.table_name) && assoc.klass.default_frames.any? && assoc_default_frame = assoc.klass.send(:build_default_frame)
|
66
|
+
merge!(assoc_default_frame)
|
67
|
+
# collector[assoc_default_frame.table_name] ||= assoc_default_frame
|
68
|
+
end
|
69
|
+
|
70
|
+
# collector
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def frame_key(cte)
|
75
|
+
case cte
|
76
|
+
when ActiveRecord::Relation
|
77
|
+
cte.table.name
|
78
|
+
when Arel::Nodes::As
|
79
|
+
cte.left.name
|
80
|
+
when String
|
81
|
+
cte
|
82
|
+
else
|
83
|
+
nil
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
# def reframe(*args) # :nodoc:
|
88
|
+
# args.compact!
|
89
|
+
# args.flatten!
|
90
|
+
# # binding.pry
|
91
|
+
# self
|
92
|
+
# end
|
93
|
+
|
94
|
+
# def reframe!(*args) # :nodoc:
|
95
|
+
# args.flatten!
|
96
|
+
# self.unframe_values += args
|
97
|
+
|
98
|
+
# args.each do |frame|
|
99
|
+
# case frame
|
100
|
+
# when Symbol
|
101
|
+
# frame = :left_outer_joins if frame == :left_joins
|
102
|
+
# if !VALID_UNSCOPING_VALUES.include?(frame)
|
103
|
+
# raise ArgumentError, "Called unframe() with invalid unframing argument ':#{frame}'. Valid arguments are :#{VALID_UNSCOPING_VALUES.to_a.join(", :")}."
|
104
|
+
# end
|
105
|
+
# set_value(frame, DEFAULT_VALUES[frame])
|
106
|
+
# when Hash
|
107
|
+
# frame.each do |key, target_value|
|
108
|
+
# if key != :where
|
109
|
+
# raise ArgumentError, "Hash arguments in .unframe(*args) must have :where as the key."
|
110
|
+
# end
|
111
|
+
|
112
|
+
# target_values = Array(target_value).map(&:to_s)
|
113
|
+
# self.where_clause = where_clause.except(*target_values)
|
114
|
+
# end
|
115
|
+
# else
|
116
|
+
# raise ArgumentError, "Unrecognized framing: #{args.inspect}. Use .unframe(where: :attribute_name) or .unframe(:order), for example."
|
117
|
+
# end
|
118
|
+
# end
|
119
|
+
|
120
|
+
# self
|
121
|
+
# end
|
122
|
+
|
123
|
+
# Frame all queries to the current frame.
|
124
|
+
#
|
125
|
+
# Comment.where(post_id: 1).framing do
|
126
|
+
# Comment.first
|
127
|
+
# end
|
128
|
+
# # => WITH "comments" AS (
|
129
|
+
# # => SELECT "comments".* FROM "comments" WHERE "comments"."post_id" = 1
|
130
|
+
# # => ) SELECT "comments".* FROM "comments" ORDER BY "comments"."id" ASC LIMIT 1
|
131
|
+
#
|
132
|
+
# Please check unframed if you want to remove all previous frames (including
|
133
|
+
# the default_frame) during the execution of a block.
|
134
|
+
def framing
|
135
|
+
previous, klass.current_frame = klass.current_frame, self unless @delegate_to_klass
|
136
|
+
yield
|
137
|
+
ensure
|
138
|
+
klass.current_frame = previous unless @delegate_to_klass
|
139
|
+
end
|
140
|
+
|
141
|
+
def scoping
|
142
|
+
framing { super }
|
143
|
+
end
|
144
|
+
|
145
|
+
# def _exec_frame(*args, &block) # :nodoc:
|
146
|
+
# @delegate_to_klass = true
|
147
|
+
# instance_exec(*args, &block) || self
|
148
|
+
# ensure
|
149
|
+
# @delegate_to_klass = false
|
150
|
+
# end
|
151
|
+
|
152
|
+
# def frame_for_create
|
153
|
+
# where_values_hash.merge!(create_with_value.stringify_keys)
|
154
|
+
# end
|
155
|
+
|
156
|
+
# def empty_frame? # :nodoc:
|
157
|
+
# @values == klass.unframed.values
|
158
|
+
# end
|
159
|
+
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
metadata
ADDED
@@ -0,0 +1,148 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: active_record-framing
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0.pre.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Dale Stevens
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-05-08 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activerecord
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.2'
|
20
|
+
- - "<"
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '6'
|
23
|
+
type: :runtime
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: !ruby/object:Gem::Requirement
|
26
|
+
requirements:
|
27
|
+
- - ">="
|
28
|
+
- !ruby/object:Gem::Version
|
29
|
+
version: '4.2'
|
30
|
+
- - "<"
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '6'
|
33
|
+
- !ruby/object:Gem::Dependency
|
34
|
+
name: pg
|
35
|
+
requirement: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - "~>"
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
type: :development
|
41
|
+
prerelease: false
|
42
|
+
version_requirements: !ruby/object:Gem::Requirement
|
43
|
+
requirements:
|
44
|
+
- - "~>"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '0'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: pry-byebug
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - "~>"
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '3'
|
54
|
+
type: :development
|
55
|
+
prerelease: false
|
56
|
+
version_requirements: !ruby/object:Gem::Requirement
|
57
|
+
requirements:
|
58
|
+
- - "~>"
|
59
|
+
- !ruby/object:Gem::Version
|
60
|
+
version: '3'
|
61
|
+
- !ruby/object:Gem::Dependency
|
62
|
+
name: bundler
|
63
|
+
requirement: !ruby/object:Gem::Requirement
|
64
|
+
requirements:
|
65
|
+
- - ">="
|
66
|
+
- !ruby/object:Gem::Version
|
67
|
+
version: '0'
|
68
|
+
type: :development
|
69
|
+
prerelease: false
|
70
|
+
version_requirements: !ruby/object:Gem::Requirement
|
71
|
+
requirements:
|
72
|
+
- - ">="
|
73
|
+
- !ruby/object:Gem::Version
|
74
|
+
version: '0'
|
75
|
+
- !ruby/object:Gem::Dependency
|
76
|
+
name: rake
|
77
|
+
requirement: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - "~>"
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '12.0'
|
82
|
+
type: :development
|
83
|
+
prerelease: false
|
84
|
+
version_requirements: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - "~>"
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '12.0'
|
89
|
+
- !ruby/object:Gem::Dependency
|
90
|
+
name: combustion
|
91
|
+
requirement: !ruby/object:Gem::Requirement
|
92
|
+
requirements:
|
93
|
+
- - "~>"
|
94
|
+
- !ruby/object:Gem::Version
|
95
|
+
version: '0.7'
|
96
|
+
type: :development
|
97
|
+
prerelease: false
|
98
|
+
version_requirements: !ruby/object:Gem::Requirement
|
99
|
+
requirements:
|
100
|
+
- - "~>"
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
version: '0.7'
|
103
|
+
description: Allows for larger level scoping (framing) that affect complicated queries
|
104
|
+
more holistically
|
105
|
+
email:
|
106
|
+
- dale@twilightcoders.net
|
107
|
+
executables: []
|
108
|
+
extensions: []
|
109
|
+
extra_rdoc_files: []
|
110
|
+
files:
|
111
|
+
- CHANGELOG.md
|
112
|
+
- LICENSE
|
113
|
+
- README.md
|
114
|
+
- lib/active_record/framing.rb
|
115
|
+
- lib/active_record/framing/attribute_methods.rb
|
116
|
+
- lib/active_record/framing/core_extension.rb
|
117
|
+
- lib/active_record/framing/default.rb
|
118
|
+
- lib/active_record/framing/named.rb
|
119
|
+
- lib/active_record/framing/query_methods.rb
|
120
|
+
- lib/active_record/framing/railtie.rb
|
121
|
+
- lib/active_record/framing/relation.rb
|
122
|
+
- lib/active_record/framing/spawn_methods.rb
|
123
|
+
- lib/active_record/framing/version.rb
|
124
|
+
homepage: https://github.com/TwilightCoders/active_record-framing
|
125
|
+
licenses:
|
126
|
+
- MIT
|
127
|
+
metadata:
|
128
|
+
allowed_push_host: https://rubygems.org
|
129
|
+
post_install_message:
|
130
|
+
rdoc_options: []
|
131
|
+
require_paths:
|
132
|
+
- lib
|
133
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
134
|
+
requirements:
|
135
|
+
- - ">="
|
136
|
+
- !ruby/object:Gem::Version
|
137
|
+
version: '2.3'
|
138
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
139
|
+
requirements:
|
140
|
+
- - ">"
|
141
|
+
- !ruby/object:Gem::Version
|
142
|
+
version: 1.3.1
|
143
|
+
requirements: []
|
144
|
+
rubygems_version: 3.0.3
|
145
|
+
signing_key:
|
146
|
+
specification_version: 4
|
147
|
+
summary: Provides larger level scopes (frames) through the use of common table expressions.
|
148
|
+
test_files: []
|