sql_view 0.0.4 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
[![Listed on OpenSource-Heroes.com](https://opensource-heroes.com/badge-v1.svg)](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
|
+
![Demo](docs/sql_view.gif?raw=true "Demo")
|
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: []
|