activerecord-viewmatic 0.1.0
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/README.md +90 -0
- data/lib/activerecord-viewmatic.rb +29 -0
- data/lib/tasks/viewmatic.rake +23 -0
- data/lib/viewmatic/migration.rb +24 -0
- data/lib/viewmatic/model.rb +31 -0
- data/lib/viewmatic/railtie.rb +9 -0
- data/lib/viewmatic/schema.rb +78 -0
- data/lib/viewmatic/schema_statements.rb +14 -0
- data/lib/viewmatic/version.rb +4 -0
- data/lib/viewmatic/view.rb +40 -0
- metadata +73 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 133bff3848fe0b250115e32cae7733867d018903
|
4
|
+
data.tar.gz: 7c128e357ef568478c0a48c67e8a269592e2375b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 44389971cb0a3024360b92601d3c21deae97760f9d8ff03d645ae847be82b0fc1ef4ee5566274c3641617f92906bdaf1f8f1c0bdced0dd282b504862a433a3ba
|
7
|
+
data.tar.gz: 9c35803b88d5b73de127965d44866e027bf7c3cf30c30543441523fa87e683dca9a3f5157bc8a73a5368b8e691337995decafcbbdab337506becd3c27892ff82
|
data/README.md
ADDED
@@ -0,0 +1,90 @@
|
|
1
|
+
# Viewmatic
|
2
|
+
|
3
|
+
Helpers for ActiveRecord apps that want to use database views easily. Both live and materialized views are supported. Easy to use:
|
4
|
+
|
5
|
+
1. Add `activerecord-viewmatic` to your Gemfile.
|
6
|
+
2. Define your views in `db/views.rb` or `db/views/*.rb`.
|
7
|
+
3. Use migrations to create or drop the defined views. (Your views will be automatically created when you run commands like `rake db:setup` and `rake db:test:prepare`.)
|
8
|
+
4. Define ActiveRecord models to access your views.
|
9
|
+
|
10
|
+
## Define your views
|
11
|
+
|
12
|
+
**db/views.rb**
|
13
|
+
|
14
|
+
```ruby
|
15
|
+
Viewmatic::Schema.define do
|
16
|
+
view :reticulated_splines do |v|
|
17
|
+
v.query "SELECT * FROM splines WHERE reticulated = 't'"
|
18
|
+
end
|
19
|
+
|
20
|
+
view :pending_orders do |v|
|
21
|
+
v.query "SELECT * FROM orders WHERE status = 'Pending'"
|
22
|
+
v.materialized true # only available in Postgres
|
23
|
+
end
|
24
|
+
end
|
25
|
+
```
|
26
|
+
|
27
|
+
## Create your migrations
|
28
|
+
```ruby
|
29
|
+
class AddMyNewViews < ActiveRecord::Migration
|
30
|
+
include Viewmatic::Migration
|
31
|
+
|
32
|
+
def up
|
33
|
+
create_view :reticulated_splines
|
34
|
+
create_view :pending_orders
|
35
|
+
end
|
36
|
+
|
37
|
+
def down
|
38
|
+
drop_view :reticulated_splines
|
39
|
+
drop_view :pending_orders
|
40
|
+
end
|
41
|
+
end
|
42
|
+
```
|
43
|
+
|
44
|
+
## Create your models
|
45
|
+
|
46
|
+
**app/models/views/reticulated_spline.rb**
|
47
|
+
|
48
|
+
```ruby
|
49
|
+
class Views::ReticulatedSpline < ActiveRecord::Base
|
50
|
+
include Viewmatic::Model
|
51
|
+
|
52
|
+
belongs_to :category
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
**app/models/views/pending_order.rb**
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
class Views::PendingOrder < ActiveRecord::Base
|
60
|
+
include Viewmatic::Model
|
61
|
+
|
62
|
+
has_many :line_items
|
63
|
+
end
|
64
|
+
```
|
65
|
+
|
66
|
+
You can access your view just like any other ActiveRecord model. Note that they are all read-only.
|
67
|
+
|
68
|
+
```ruby
|
69
|
+
spline = Views::ReticulatedSpline.find(42)
|
70
|
+
spline.readonly?
|
71
|
+
=> true
|
72
|
+
|
73
|
+
Views::PendingOrder.where(created_on: Date.today).preload(:line_items).find_each do |order|
|
74
|
+
# do stuff with each order
|
75
|
+
end
|
76
|
+
```
|
77
|
+
|
78
|
+
For materialized views, you can refresh the data simply by calling `refresh!` on the model:
|
79
|
+
|
80
|
+
```ruby
|
81
|
+
PendingOrder.refresh!
|
82
|
+
```
|
83
|
+
|
84
|
+
## Using ActiveRecord without Rails?
|
85
|
+
|
86
|
+
No problem! Everything will work fine - just add this line near the top of your `Rakefile`:
|
87
|
+
|
88
|
+
```ruby
|
89
|
+
load 'tasks/viewmatic.rake'
|
90
|
+
```
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'active_record'
|
2
|
+
|
3
|
+
# Container module for all Viewmatic features.
|
4
|
+
module Viewmatic
|
5
|
+
autoload :View, 'viewmatic/view'
|
6
|
+
autoload :Model, 'viewmatic/model'
|
7
|
+
autoload :Schema, 'viewmatic/schema'
|
8
|
+
autoload :SchemaStatements, 'viewmatic/schema_statements'
|
9
|
+
autoload :Migration, 'viewmatic/migration'
|
10
|
+
autoload :VERSION, 'viewmatic/version'
|
11
|
+
|
12
|
+
class << self
|
13
|
+
# @return [Array<Viewmatic::Schema] all schemas loaded from file(s)
|
14
|
+
attr_reader :schemas
|
15
|
+
end
|
16
|
+
@schemas = []
|
17
|
+
|
18
|
+
#
|
19
|
+
# Fetch the first view named "name" from all schemas. If it can't be found an exception is raised.
|
20
|
+
#
|
21
|
+
# @return [Viewmatic::View]
|
22
|
+
#
|
23
|
+
def self.view(name)
|
24
|
+
schema = schemas.detect { |s| s.views.has_key? name }
|
25
|
+
schema ? schema.views[name] : raise(ArgumentError, "Could not find view named `#{name}`")
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
require 'viewmatic/railtie' if defined? Rails
|
@@ -0,0 +1,23 @@
|
|
1
|
+
Dir.glob(Viewmatic::Schema.paths).each { |f| require f }
|
2
|
+
|
3
|
+
namespace :db do
|
4
|
+
namespace :views do
|
5
|
+
desc "Load all database views"
|
6
|
+
task :load => :environment do
|
7
|
+
Viewmatic.schemas.each(&:load!)
|
8
|
+
end
|
9
|
+
|
10
|
+
desc "Drop all database views"
|
11
|
+
task :drop => :environment do
|
12
|
+
Viewmatic.schemas.each(&:drop!)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
Rake::Task['db:schema:load'].enhance do
|
18
|
+
Rake::Task['db:views:load'].invoke
|
19
|
+
end
|
20
|
+
|
21
|
+
Rake::Task['db:test:prepare'].enhance do
|
22
|
+
Rake::Task['db:views:load'].invoke
|
23
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module Viewmatic
|
2
|
+
#
|
3
|
+
# Helpers for creating and dropping views in ActiveRecord migrations.
|
4
|
+
#
|
5
|
+
module Migration
|
6
|
+
#
|
7
|
+
# Create the view named in "name". It must be defined in the view definitions file.
|
8
|
+
#
|
9
|
+
# @param [Symbol] name
|
10
|
+
#
|
11
|
+
def create_view(name)
|
12
|
+
execute SchemaStatements.create_view Viewmatic.view name
|
13
|
+
end
|
14
|
+
|
15
|
+
#
|
16
|
+
# Drop the view named in "name". It must be defined in the view definitions file.
|
17
|
+
#
|
18
|
+
# @param [Symbol] name
|
19
|
+
#
|
20
|
+
def drop_view(name)
|
21
|
+
execute SchemaStatements.drop_view Viewmatic.view name
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Viewmatic
|
2
|
+
#
|
3
|
+
# Helpers for view-based ActiveRecord models.
|
4
|
+
#
|
5
|
+
module Model
|
6
|
+
def self.included(model)
|
7
|
+
model.extend ClassMethods
|
8
|
+
end
|
9
|
+
|
10
|
+
#
|
11
|
+
# All view record are read-only.
|
12
|
+
#
|
13
|
+
# @return [Boolea] will always be true
|
14
|
+
#
|
15
|
+
def readonly?
|
16
|
+
true
|
17
|
+
end
|
18
|
+
|
19
|
+
#
|
20
|
+
# Class-level helper methods.
|
21
|
+
#
|
22
|
+
module ClassMethods
|
23
|
+
#
|
24
|
+
# Refresh the underlying materialized view.
|
25
|
+
#
|
26
|
+
def refresh!
|
27
|
+
connection.execute "REFRESH MATERIALIZED VIEW #{table_name}"
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
module Viewmatic
|
2
|
+
#
|
3
|
+
# Represents a schema of views to be built.
|
4
|
+
#
|
5
|
+
class Schema
|
6
|
+
class << self
|
7
|
+
# @return [Array<String>] Array of globs matching view definition files. default: ['db/views.rb', 'db/views/*.rb']
|
8
|
+
attr_reader :paths
|
9
|
+
end
|
10
|
+
|
11
|
+
@paths = [
|
12
|
+
File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'views.rb'),
|
13
|
+
File.join(ActiveRecord::Tasks::DatabaseTasks.db_dir, 'views', '*.rb'),
|
14
|
+
]
|
15
|
+
|
16
|
+
#
|
17
|
+
# Define a new schema. If you pass a block, it will be eval'd inside the Schema instance.
|
18
|
+
#
|
19
|
+
# @return [Viewmatic::Schema]
|
20
|
+
#
|
21
|
+
def self.define(&block)
|
22
|
+
schema = new(&block)
|
23
|
+
Viewmatic.schemas << schema
|
24
|
+
schema
|
25
|
+
end
|
26
|
+
|
27
|
+
# @return [Array<Viewmatic::View] all defined views in this schema
|
28
|
+
attr_reader :views
|
29
|
+
|
30
|
+
#
|
31
|
+
# Initialize a new schema. If you pass a block it will be eval'd inside the instance.
|
32
|
+
#
|
33
|
+
def initialize(&block)
|
34
|
+
@views = {}
|
35
|
+
@conn_proc = -> { ActiveRecord::Base.connection }
|
36
|
+
instance_exec(&block) if block
|
37
|
+
end
|
38
|
+
|
39
|
+
#
|
40
|
+
# Define a new view.
|
41
|
+
#
|
42
|
+
# @param name [Symbol] what to call the view
|
43
|
+
#
|
44
|
+
def view(name)
|
45
|
+
view = views[name] = View.new(name)
|
46
|
+
yield view if block_given?
|
47
|
+
view
|
48
|
+
end
|
49
|
+
|
50
|
+
#
|
51
|
+
# Override the default connection to use.
|
52
|
+
#
|
53
|
+
def connection(&block)
|
54
|
+
@conn_proc = block if block
|
55
|
+
@conn_proc
|
56
|
+
end
|
57
|
+
|
58
|
+
#
|
59
|
+
# Create all views defined in this schema.
|
60
|
+
#
|
61
|
+
def load!
|
62
|
+
conn = @conn_proc.call
|
63
|
+
views.each do |_name, view|
|
64
|
+
conn.execute SchemaStatements.create_view view
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
#
|
69
|
+
# Drop all views defined in this schema.
|
70
|
+
#
|
71
|
+
def drop!
|
72
|
+
conn = @conn_proc.call
|
73
|
+
views.each do |_name, view|
|
74
|
+
conn.execute SchemaStatements.drop_view view
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Viewmatic
|
2
|
+
module SchemaStatements
|
3
|
+
def self.create_view(v)
|
4
|
+
mat = v.materialized ? 'MATERIALIZED' : nil
|
5
|
+
cols = v.column_names ? "(#{v.column_names.join ', '})" : nil
|
6
|
+
%Q(CREATE #{mat} VIEW #{v.name} #{cols} AS #{v.query})
|
7
|
+
end
|
8
|
+
|
9
|
+
def self.drop_view(v)
|
10
|
+
mat = v.materialized ? 'MATERIALIZED' : nil
|
11
|
+
%Q(DROP #{mat} VIEW IF EXISTS #{v.name})
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module Viewmatic
|
2
|
+
#
|
3
|
+
# Represents a view to be built.
|
4
|
+
#
|
5
|
+
class View
|
6
|
+
#
|
7
|
+
# Initialize a new view.
|
8
|
+
#
|
9
|
+
# @param name [Symbol] name of the view as it will appear in the database
|
10
|
+
#
|
11
|
+
def initialize(name)
|
12
|
+
@name = name
|
13
|
+
@materialized = false
|
14
|
+
end
|
15
|
+
|
16
|
+
# Get/set the view name
|
17
|
+
def name(val = nil)
|
18
|
+
@name = val unless val.nil?
|
19
|
+
@name
|
20
|
+
end
|
21
|
+
|
22
|
+
# Get/set the query backing the view
|
23
|
+
def query(val = nil)
|
24
|
+
@query = val unless val.nil?
|
25
|
+
@query
|
26
|
+
end
|
27
|
+
|
28
|
+
# Get/set the query column name overrides
|
29
|
+
def column_names(val = nil)
|
30
|
+
@column_names = val unless val.nil?
|
31
|
+
@column_names
|
32
|
+
end
|
33
|
+
|
34
|
+
# Get/set the materialized status
|
35
|
+
def materialized(val = nil)
|
36
|
+
@materialized = val unless val.nil?
|
37
|
+
@materialized
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
metadata
ADDED
@@ -0,0 +1,73 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: activerecord-viewmatic
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jordan Hollinger
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-10-10 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: '5.2'
|
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: '5.2'
|
33
|
+
description: Helpers for using live and materialized views ActiveRecord
|
34
|
+
email: jordan.hollinger@gmail.com
|
35
|
+
executables: []
|
36
|
+
extensions: []
|
37
|
+
extra_rdoc_files: []
|
38
|
+
files:
|
39
|
+
- README.md
|
40
|
+
- lib/activerecord-viewmatic.rb
|
41
|
+
- lib/tasks/viewmatic.rake
|
42
|
+
- lib/viewmatic/migration.rb
|
43
|
+
- lib/viewmatic/model.rb
|
44
|
+
- lib/viewmatic/railtie.rb
|
45
|
+
- lib/viewmatic/schema.rb
|
46
|
+
- lib/viewmatic/schema_statements.rb
|
47
|
+
- lib/viewmatic/version.rb
|
48
|
+
- lib/viewmatic/view.rb
|
49
|
+
homepage: https://github.com/jhollinger/activerecord-viewmatic
|
50
|
+
licenses:
|
51
|
+
- MIT
|
52
|
+
metadata: {}
|
53
|
+
post_install_message:
|
54
|
+
rdoc_options: []
|
55
|
+
require_paths:
|
56
|
+
- lib
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
58
|
+
requirements:
|
59
|
+
- - ">="
|
60
|
+
- !ruby/object:Gem::Version
|
61
|
+
version: 2.1.0
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
63
|
+
requirements:
|
64
|
+
- - ">="
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
requirements: []
|
68
|
+
rubyforge_project:
|
69
|
+
rubygems_version: 2.5.2
|
70
|
+
signing_key:
|
71
|
+
specification_version: 4
|
72
|
+
summary: An easy way to use database views in ActiveRecord
|
73
|
+
test_files: []
|