active_record-framing 0.1.0.pre.1
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![License ](https://img.shields.io/github/license/TwilightCoders/active_record-framing.svg)]()
|
2
|
+
[![Version ](https://img.shields.io/gem/v/active_record-framing.svg)](https://rubygems.org/gems/active_record-framing)
|
3
|
+
[![Build Status](https://travis-ci.org/TwilightCoders/active_record-framing.svg)](https://travis-ci.org/TwilightCoders/active_record-framing)
|
4
|
+
[![Maintenence ](https://api.codeclimate.com/v1/badges/762cdcd63990efa768b0/maintainability)](https://codeclimate.com/github/TwilightCoders/active_record-framing/maintainability)
|
5
|
+
[![Coverage ](https://codeclimate.com/github/TwilightCoders/active_record-framing/badges/coverage.svg)](https://codeclimate.com/github/TwilightCoders/active_record-framing/coverage)
|
6
|
+
[![Dependencies](https://img.shields.io/librariesio/github/twilightcoders/active_record-framing.svg)](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: []
|