activerecord-viewmatic 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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,9 @@
1
+ module Viewmatic
2
+ class Railtie < Rails::Railtie
3
+ railtie_name :viewmatic
4
+
5
+ rake_tasks do
6
+ load 'tasks/viewmatic.rake'
7
+ end
8
+ end
9
+ 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,4 @@
1
+ module Viewmatic
2
+ # Library version
3
+ VERSION = '0.1.0'.freeze
4
+ 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: []