sql_view 0.0.4 → 0.0.6
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 +4 -4
- data/README.md +145 -8
- data/lib/sql_view/schema_dumper.rb +13 -33
- data/lib/sql_view/version.rb +1 -1
- data/lib/sql_view.rb +7 -5
- metadata +6 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 025552fef24e528e14b29c5e5fde2f011e329a2aee4169d831d602aae26ca107
|
4
|
+
data.tar.gz: f0de63bac86f0a4ad119b972bb9357325c6556ff737c6be245514a4002021915
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4213c7dc5a9c882664f9214cec6e148313221d0a799484517b3db45a288d9516094c70738c89030010ffa9a607a561e8339be9099741df291c9540bd13067247
|
7
|
+
data.tar.gz: e7177d5289085a4be25f4ff9d4bdecdab2104c1816281a7abeecdee5418be1b6aa2e18a05e0087f140370335b7fd146bf50d5017e2dcaa3c84722c3fc1ac1e46
|
data/README.md
CHANGED
@@ -1,11 +1,98 @@
|
|
1
|
-
#
|
2
|
-
|
1
|
+
# Rails + SQL View
|
2
|
+
|
3
|
+
[](https://opensource-heroes.com/r/igorkasyanchuk/sql_view)
|
4
|
+
|
5
|
+
## The easist way to add and work with SQL view in your app.
|
6
|
+
|
7
|
+
If you are lazy and don't like to write SQL to create SQL view but you know AR use your skills to create views.
|
8
|
+
|
9
|
+
Production-ready.
|
10
|
+
|
11
|
+

|
3
12
|
|
4
13
|
## Usage
|
5
|
-
|
14
|
+
|
15
|
+
The most simple way to add a view is to call a generator (examples below):
|
16
|
+
|
17
|
+
```bash
|
18
|
+
rails g sql_view:view DeletedProjects 'Project.only_deleted'
|
19
|
+
rails g sql_view:view ActiveUsers 'User.confirmed.where(active: true)' --materialized
|
20
|
+
```
|
21
|
+
|
22
|
+
Depending on whether you need a materialized view or not add `--materialized` flag (later you can change in "view" class). Materialized views works in Postgres.
|
23
|
+
|
24
|
+
Generator will create a file similar to:
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
class ActiveUserView < SQLView::Model
|
28
|
+
materialized
|
29
|
+
|
30
|
+
schema -> { User.where(age: 18..60) }
|
31
|
+
|
32
|
+
extend_model_with do
|
33
|
+
# sample how you can extend it, similar to regular AR model
|
34
|
+
#
|
35
|
+
# include SomeConcern
|
36
|
+
#
|
37
|
+
# belongs_to :user
|
38
|
+
# has_many :posts
|
39
|
+
#
|
40
|
+
# scope :ordered, -> { order(:created_at) }
|
41
|
+
# scope :by_role, ->(role) { where(role: role) }
|
42
|
+
end
|
43
|
+
end
|
44
|
+
```
|
45
|
+
|
46
|
+
or if you want to use SQL to create a regular view:
|
47
|
+
|
48
|
+
|
49
|
+
```ruby
|
50
|
+
class ActiveUserView < SQLView::Model
|
51
|
+
schema -> { "SELECT * FROM users WHERE active = TRUE" }
|
52
|
+
end
|
53
|
+
```
|
54
|
+
|
55
|
+
or the same but materialized:
|
56
|
+
|
57
|
+
```ruby
|
58
|
+
class ActiveUserView < SQLView::Model
|
59
|
+
materialized
|
60
|
+
schema -> { "SELECT * FROM users WHERE active = TRUE" }
|
61
|
+
end
|
62
|
+
```
|
63
|
+
|
64
|
+
Later with view you can work same way as with any model(ActiveRecord class). For example:
|
65
|
+
|
66
|
+
```ruby
|
67
|
+
ActiveUserView.model.count
|
68
|
+
# or
|
69
|
+
ActiveUserView.count
|
70
|
+
# ----
|
71
|
+
ActiveUserView.find(42)
|
72
|
+
# you can apply scopes, relations, methods, BUT add them in extend_model_with block
|
73
|
+
|
74
|
+
ActiveUserView.model.by_role("admin").count
|
75
|
+
ActiveUserView.where(role: "admin").exists?
|
76
|
+
ActiveUserView.model.includes(:profile)
|
77
|
+
```
|
78
|
+
|
79
|
+
If you need to refresh materialized view - `ActiveUserView.sql_view.refresh` (if you need to do it concerrently - `.refresh(concurrently: false)`.
|
80
|
+
|
81
|
+
It can also be used with your other models:
|
82
|
+
|
83
|
+
```ruby
|
84
|
+
class Account < ApplicationRecord
|
85
|
+
has_many :users
|
86
|
+
|
87
|
+
has_one :account_stat_view, class_name: AccountStatViewView.model.to_s, foreign_key: :account_id
|
88
|
+
has_many :active_users, join_table: :active_users_views, class_name: ActiveUserView.model.to_s, foreign_key: :account_id
|
89
|
+
end
|
90
|
+
```
|
91
|
+
|
92
|
+
|
93
|
+
More examples in this file: `./test/sql_view_test.rb`
|
6
94
|
|
7
95
|
## Installation
|
8
|
-
Add this line to your application's Gemfile:
|
9
96
|
|
10
97
|
```ruby
|
11
98
|
gem "sql_view"
|
@@ -16,17 +103,67 @@ And then execute:
|
|
16
103
|
$ bundle
|
17
104
|
```
|
18
105
|
|
19
|
-
Or
|
20
|
-
|
21
|
-
|
106
|
+
And use generator. Or you can connect it to existing view with `view_name=`:
|
107
|
+
|
108
|
+
```ruby
|
109
|
+
class OldUserView < SqlView::Model
|
110
|
+
self.view_name = "all_old_users"
|
111
|
+
|
112
|
+
materialized
|
113
|
+
|
114
|
+
schema -> { User.where("age > 18") }
|
115
|
+
|
116
|
+
extend_model_with do
|
117
|
+
scope :ordered, -> { order(:id) }
|
118
|
+
|
119
|
+
def test_instance_method
|
120
|
+
42
|
121
|
+
end
|
122
|
+
end
|
123
|
+
end
|
22
124
|
```
|
23
125
|
|
126
|
+
## Materialized view + concurrent update
|
127
|
+
|
128
|
+
1. add index
|
129
|
+
|
130
|
+
```ruby
|
131
|
+
add_index SomeView.view_name, :user_id, unique: true
|
132
|
+
```
|
133
|
+
|
134
|
+
2. refresh with parameter
|
135
|
+
|
136
|
+
```ruby
|
137
|
+
SomeView.sql_view.refresh(concurrently: true)
|
138
|
+
```
|
139
|
+
|
140
|
+
3. profit :)
|
141
|
+
|
142
|
+
## TODO
|
143
|
+
|
144
|
+
- CI with different versions of Rails/Ruby
|
145
|
+
- make unit tests works with `rake test`
|
146
|
+
- `cascade` option
|
147
|
+
- move classes to own files
|
148
|
+
- code coverage
|
149
|
+
- verify how it works with other DB's
|
150
|
+
- check if schema was changed on migrate or schema dump?
|
151
|
+
|
24
152
|
## Testing
|
25
153
|
|
26
154
|
`ruby ./test/sql_view_test.rb` (because somehow `rake test` not works, not critical for now)
|
27
155
|
|
28
156
|
## Contributing
|
29
|
-
|
157
|
+
|
158
|
+
You are welcome to contribute.
|
159
|
+
|
160
|
+
## Credits
|
161
|
+
|
162
|
+
I know about and actually using `gem scenic`, which is very nice and I tool some examples from it how to dump view into schema.rb but this gem was created to simplify life and reduce amount of time needed to write SQL to create a sql view.
|
30
163
|
|
31
164
|
## License
|
165
|
+
|
32
166
|
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
167
|
+
|
168
|
+
[<img src="https://github.com/igorkasyanchuk/rails_time_travel/blob/main/docs/more_gems.png?raw=true"
|
169
|
+
/>](https://www.railsjazz.com/?utm_source=github&utm_medium=bottom&utm_campaign=sql_view)
|
@@ -8,20 +8,21 @@ module SqlView
|
|
8
8
|
class DBView < OpenStruct
|
9
9
|
def to_schema
|
10
10
|
<<-DEFINITION
|
11
|
-
create_sql_view "#{
|
12
|
-
CREATE
|
11
|
+
create_sql_view "#{viewname}", sql: <<-\SQL
|
12
|
+
CREATE#{materialized_or_not}VIEW "#{viewname}" AS
|
13
13
|
#{escaped_definition.indent(2)}
|
14
14
|
SQL\n
|
15
15
|
DEFINITION
|
16
16
|
end
|
17
17
|
|
18
18
|
private
|
19
|
+
|
19
20
|
def materialized?
|
20
|
-
|
21
|
+
kind == "m"
|
21
22
|
end
|
22
23
|
|
23
24
|
def materialized_or_not
|
24
|
-
materialized? ? " MATERIALIZED " :
|
25
|
+
materialized? ? " MATERIALIZED " : " "
|
25
26
|
end
|
26
27
|
|
27
28
|
def escaped_definition
|
@@ -35,40 +36,18 @@ module SqlView
|
|
35
36
|
end
|
36
37
|
|
37
38
|
def views(stream)
|
38
|
-
if
|
39
|
-
stream.puts
|
40
|
-
end
|
39
|
+
stream.puts if sql_views.any?
|
41
40
|
|
42
|
-
|
43
|
-
next if already_indexed?(viewname)
|
44
|
-
view = DBView.new(get_view_info(viewname))
|
41
|
+
sql_views.each do |view|
|
45
42
|
stream.puts(view.to_schema)
|
46
|
-
indexes(viewname, stream)
|
43
|
+
indexes(view.viewname, stream)
|
47
44
|
end
|
48
45
|
end
|
49
46
|
|
50
47
|
private
|
51
48
|
|
52
|
-
|
53
|
-
|
54
|
-
@already_indexed ||= []
|
55
|
-
return true if @already_indexed.include?(viewname)
|
56
|
-
@already_indexed << viewname
|
57
|
-
false
|
58
|
-
end
|
59
|
-
|
60
|
-
def dumpable_views_in_database
|
61
|
-
@dumpable_views_in_database ||= ActiveRecord::Base.connection.views.reject do |viewname|
|
62
|
-
ignored?(viewname)
|
63
|
-
end
|
64
|
-
end
|
65
|
-
|
66
|
-
def get_view_info(viewname)
|
67
|
-
views_schema.detect{|e| e['viewname'] == viewname}
|
68
|
-
end
|
69
|
-
|
70
|
-
def views_schema
|
71
|
-
@views_schema ||= ActiveRecord::Base.connection.execute(<<-SQL)
|
49
|
+
def sql_views
|
50
|
+
@sql_views ||= ActiveRecord::Base.connection.execute(<<-SQL)
|
72
51
|
SELECT
|
73
52
|
c.relname as viewname,
|
74
53
|
pg_get_viewdef(c.oid) AS definition,
|
@@ -79,10 +58,11 @@ module SqlView
|
|
79
58
|
WHERE
|
80
59
|
c.relkind IN ('m', 'v')
|
81
60
|
AND c.relname NOT IN (SELECT extname FROM pg_extension)
|
61
|
+
AND c.relname != 'pg_stat_statements_info'
|
82
62
|
AND n.nspname = ANY (current_schemas(false))
|
83
63
|
ORDER BY c.oid
|
84
64
|
SQL
|
85
|
-
.to_a
|
65
|
+
.to_a.map(&DBView.method(:new)).reject { |view| ignored?(view.viewname) }
|
86
66
|
end
|
87
67
|
|
88
68
|
unless ActiveRecord::SchemaDumper.private_instance_methods(false).include?(:ignored?)
|
@@ -101,4 +81,4 @@ module SqlView
|
|
101
81
|
end
|
102
82
|
end
|
103
83
|
|
104
|
-
SQLView = SqlView
|
84
|
+
SQLView = SqlView
|
data/lib/sql_view/version.rb
CHANGED
data/lib/sql_view.rb
CHANGED
@@ -68,7 +68,7 @@ module SqlView
|
|
68
68
|
sql = <<-SQL
|
69
69
|
REFRESH#{materialized_or_not}VIEW#{concurrently_or_not}#{parent.view_name};
|
70
70
|
SQL
|
71
|
-
execute(sql)
|
71
|
+
execute(sql, log: false)
|
72
72
|
end
|
73
73
|
|
74
74
|
def up
|
@@ -88,8 +88,8 @@ module SqlView
|
|
88
88
|
execute(sql)
|
89
89
|
end
|
90
90
|
|
91
|
-
def execute(sql)
|
92
|
-
SqlView.log
|
91
|
+
def execute(sql, log: true)
|
92
|
+
SqlView.log(sql) if log
|
93
93
|
ActiveRecord::Base.connection.execute sql#.wp
|
94
94
|
end
|
95
95
|
|
@@ -103,6 +103,7 @@ module SqlView
|
|
103
103
|
|
104
104
|
class ClassBuilder
|
105
105
|
def ClassBuilder.create_model(parent)
|
106
|
+
class_name = "#{parent}Model"
|
106
107
|
klass = Class.new(ActiveRecord::Base) do
|
107
108
|
def self.model_name
|
108
109
|
ActiveModel::Name.new(self, nil, parent.view_name)
|
@@ -120,10 +121,11 @@ module SqlView
|
|
120
121
|
# because of the error undefined scan for nil class
|
121
122
|
klass.class_eval %Q{
|
122
123
|
def self.name
|
123
|
-
"#{
|
124
|
+
"#{class_name}"
|
124
125
|
end
|
125
126
|
}
|
126
|
-
klass
|
127
|
+
Object.const_set(class_name, klass) unless const_defined?(class_name)
|
128
|
+
Object.const_get(class_name)
|
127
129
|
end
|
128
130
|
end
|
129
131
|
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: sql_view
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Igor Kasyanchuk
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-06-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: rails
|
@@ -89,7 +89,7 @@ licenses:
|
|
89
89
|
- MIT
|
90
90
|
metadata:
|
91
91
|
homepage_uri: https://github.com/igorkasyanchuk/sql_view
|
92
|
-
post_install_message:
|
92
|
+
post_install_message:
|
93
93
|
rdoc_options: []
|
94
94
|
require_paths:
|
95
95
|
- lib
|
@@ -104,8 +104,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
104
104
|
- !ruby/object:Gem::Version
|
105
105
|
version: '0'
|
106
106
|
requirements: []
|
107
|
-
rubygems_version: 3.
|
108
|
-
signing_key:
|
107
|
+
rubygems_version: 3.4.13
|
108
|
+
signing_key:
|
109
109
|
specification_version: 4
|
110
110
|
summary: Simple way to create and interact with your SQL views using ActiveRecord.
|
111
111
|
test_files: []
|