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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: ba42c85d8dfc2f460a9996e6283dd998f4acdd4d92c953654d52868df30cab54
4
- data.tar.gz: d27a1d496c7f93ef63ff62820dd868532ac87eb4d10df541ed482e4febac71ac
3
+ metadata.gz: 025552fef24e528e14b29c5e5fde2f011e329a2aee4169d831d602aae26ca107
4
+ data.tar.gz: f0de63bac86f0a4ad119b972bb9357325c6556ff737c6be245514a4002021915
5
5
  SHA512:
6
- metadata.gz: 289169432b76935e91b885225281402af12779fec09d94360ea1e17ec947b4a3dec92686e8fe058a15098c6f02da140d5bc64f17649fbd1f7e5810b2d61dfe65
7
- data.tar.gz: 51bfe3fda64423d9041a0ea62c894f46c4a52a00d42eb9c8c1c4f29d59dede513f26876b101b96aa998bbbb9dda8bcee7dd8878b4a372bba125ea627abe1bf10
6
+ metadata.gz: 4213c7dc5a9c882664f9214cec6e148313221d0a799484517b3db45a288d9516094c70738c89030010ffa9a607a561e8339be9099741df291c9540bd13067247
7
+ data.tar.gz: e7177d5289085a4be25f4ff9d4bdecdab2104c1816281a7abeecdee5418be1b6aa2e18a05e0087f140370335b7fd146bf50d5017e2dcaa3c84722c3fc1ac1e46
data/README.md CHANGED
@@ -1,11 +1,98 @@
1
- # SqlView
2
- Short description and motivation.
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
- How to use my plugin.
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 install it yourself as:
20
- ```bash
21
- $ gem install sql_view
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
- Contribution directions go here.
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 "#{self.viewname}", sql: <<-\SQL
12
- CREATE #{materialized_or_not} VIEW "#{self.viewname}" AS
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
- self.kind == "m"
21
+ kind == "m"
21
22
  end
22
23
 
23
24
  def materialized_or_not
24
- materialized? ? " MATERIALIZED " : nil
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 dumpable_views_in_database.any?
39
- stream.puts
40
- end
39
+ stream.puts if sql_views.any?
41
40
 
42
- dumpable_views_in_database.each do |viewname|
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
- # make sure view was added one time, because somehow was adding views two times
53
- def already_indexed?(viewname)
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
@@ -1,3 +1,3 @@
1
1
  module SqlView
2
- VERSION = "0.0.4"
2
+ VERSION = "0.0.6"
3
3
  end
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 sql
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
- "#{parent.class}"
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
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: 2022-02-14 00:00:00.000000000 Z
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.3.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: []