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 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: []