devlin 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,8 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in devlin.gemspec
4
+ gemspec
5
+ gem 'rspec'
6
+ gem 'sqlite3'
7
+ gem 'pry'
8
+ gem 'factory_girl'
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Paul Spieker
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,80 @@
1
+ # Devlin
2
+
3
+ Devlin gives users the ability to define reporting database queries.
4
+ The queries are defined in yaml and using predefined scopes. Since
5
+ the sql statements are defined by the developer, the user is not able
6
+ to corrupt the database.
7
+
8
+ ## Installation
9
+
10
+ Add this line to your application's Gemfile:
11
+
12
+ gem 'devlin', :git => 'https://github.com/spieker/devlin.git'
13
+
14
+ And then execute:
15
+
16
+ $ bundle
17
+
18
+ ## Usage
19
+
20
+ First you have to setup Devlin and define base scopes which can be used
21
+ to define queries. In Rails you can do this in an initializer.
22
+
23
+ # config/initializers/devlin.rb
24
+
25
+ class ReportBuilder < Devlin::Base
26
+ scope :transaction do |params|
27
+ relation Transaction.where(user_id: params[:user_id]).scoped
28
+
29
+ column :manufacturer, "manufacturer"
30
+ column :year, "YEAR(date)"
31
+ column :month, "MONTH(date)" do |value|
32
+ months = %W(~ Jan Feb Mar Apr May Jun Jul Aug Sep Okt Nov Dec)
33
+ months[value]
34
+ end
35
+ column :earnings, "SUM(costs*IF(direction='in', -1, 1))"
36
+ end
37
+
38
+ # more scopes ...
39
+ end
40
+
41
+ Now you can define queries on the configured scopes
42
+
43
+ # app/controllers/report_controller.rb
44
+
45
+ ...
46
+ def report
47
+ @relation = Devlin.new(user_id: current_user.id).query(params[:q]).result
48
+ end
49
+ ...
50
+
51
+ The parameter "q" has to contain the query defined in yaml. The query
52
+ can look like this:
53
+
54
+ scope: transaction
55
+ select:
56
+ - manufacturer
57
+ - month
58
+ - earnings
59
+ conditions:
60
+ year: 2012
61
+ group:
62
+ - manufacturer
63
+ - month
64
+
65
+ The generated query returns a sum of earnings for each manufacturer and
66
+ each month in the year 2012.
67
+
68
+ ## Todo
69
+
70
+ * Params for columns to have i.e. one column for each month and a list
71
+ of manufacturers
72
+
73
+
74
+ ## Contributing
75
+
76
+ 1. Fork it
77
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
78
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
79
+ 4. Push to the branch (`git push origin my-new-feature`)
80
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
data/devlin.gemspec ADDED
@@ -0,0 +1,20 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/devlin/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Paul Spieker"]
6
+ gem.email = ["p.spieker@duenos.de"]
7
+ gem.description = %q{Devlin gives users the ability to define reporting database queries}
8
+ gem.summary = %q{Devlin gives users the ability to define reporting database queries. The queries are defined in yaml and using predefined scopes. Since the sql statements are defined by the developer, the user is not able to corrupt the database.}
9
+ gem.homepage = "https://github.com/spieker/devlin"
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "devlin"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = Devlin::VERSION
17
+
18
+ gem.add_dependency 'activerecord', '>= 3.0.0'
19
+ gem.add_dependency 'activesupport', '>= 3.0.0'
20
+ end
@@ -0,0 +1,32 @@
1
+ module Devlin
2
+ class Base
3
+ def initialize(params)
4
+ @params = params
5
+ end
6
+
7
+ # returns the keys of the defined scopes
8
+ def self.scopes
9
+ @scopes.keys
10
+ end
11
+
12
+ def self.[](name)
13
+ @scopes[name.to_sym]
14
+ end
15
+
16
+ def scope(name)
17
+ @scope ||= {}
18
+ @scope[name.to_sym] ||= Scope.new(@params, &(self.class[name.to_sym]))
19
+ @scope[name.to_sym]
20
+ end
21
+
22
+ def query(q)
23
+ Query.new(self, q)
24
+ end
25
+
26
+ private
27
+ def self.scope(name, &block)
28
+ @scopes ||= {}
29
+ @scopes[name.to_sym] = block
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,24 @@
1
+ module Devlin
2
+ class Column
3
+ def initialize(name, config)
4
+ @name = name
5
+ @config = config
6
+ end
7
+
8
+ def select_definition
9
+ "#{@config[:definition]} AS #{@name}"
10
+ end
11
+
12
+ def definition
13
+ @config[:definition]
14
+ end
15
+
16
+ def value(value)
17
+ if @config[:getter].respond_to?(:call)
18
+ @config[:getter].call(value)
19
+ else
20
+ value
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,26 @@
1
+ module Devlin
2
+ class Query
3
+ attr_reader :query, :scope, :select, :conditions, :group
4
+
5
+ def initialize(parent, q)
6
+ @parent = parent
7
+ @query = YAML.load(q)
8
+ @scope = parent.scope(query['scope'])
9
+ @select = query['select']
10
+ @conditions = query['conditions']
11
+ @group = query['group']
12
+ end
13
+
14
+ # This method returns the resulting relation to calculate the given
15
+ # query
16
+ def result
17
+ res = @scope.relation
18
+ res = res.select(self.select.map { |c| @scope.column(c).select_definition })
19
+ @conditions.each do |col, val|
20
+ res = res.where(["#{@scope.column(col).definition}=?", val.to_s])
21
+ end
22
+ res = res.group(self.group.map { |c| @scope.column(c).definition })
23
+ res
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,50 @@
1
+ module Devlin
2
+ class Scope
3
+ # The configuration object simplifies the configuration of the scope
4
+ class Config
5
+ attr_reader :params
6
+
7
+ def initialize(scope, params, &block)
8
+ @scope = scope
9
+ @params = params
10
+ instance_eval(&block)
11
+ end
12
+
13
+ def relation(rel)
14
+ @scope.relation = rel
15
+ end
16
+
17
+ def column(name, definition, &block)
18
+ @scope.add_column(name, definition, &block)
19
+ end
20
+ end
21
+
22
+ attr_accessor :relation
23
+
24
+ def initialize(params, &block)
25
+ Config.new(self, params, &block)
26
+ end
27
+
28
+ # add a column definition to the scope
29
+ def add_column(name, definition, &block)
30
+ @columns ||= {}
31
+ @columns[name.to_sym] = Column.new name, {
32
+ definition: definition,
33
+ getter: block
34
+ }
35
+ end
36
+
37
+ def columns
38
+ @columns ||= {}
39
+ @columns.keys
40
+ end
41
+
42
+ def column(name)
43
+ @columns ||= {}
44
+ column = @columns[name.to_sym]
45
+ raise "no column '#{name}' found" if column.blank?
46
+ column
47
+ end
48
+
49
+ end
50
+ end
@@ -0,0 +1,3 @@
1
+ module Devlin
2
+ VERSION = "0.0.1"
3
+ end
data/lib/devlin.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "devlin/version"
2
+ require 'active_record'
3
+ require 'active_support'
4
+ require "devlin/base"
5
+ require "devlin/scope"
6
+ require "devlin/column"
7
+ require "devlin/query"
8
+ require 'yaml'
9
+
10
+ module Devlin
11
+ # Your code goes here...
12
+ end
data/spec/factories.rb ADDED
@@ -0,0 +1,10 @@
1
+ require 'factory_girl'
2
+
3
+ FactoryGirl.define do
4
+ factory :transaction do
5
+ user_id 1
6
+ manufacturer 'M1'
7
+ date Date.parse('2012-08-18')
8
+ costs 100
9
+ end
10
+ end
@@ -0,0 +1,39 @@
1
+ require 'spec_helper'
2
+
3
+ describe TestReport do
4
+ before :each do
5
+ 100.times do |i|
6
+ FactoryGirl.create :transaction, date: Date.parse('2012-08-18')+i.days
7
+ FactoryGirl.create :transaction, date: Date.parse('2012-08-18')+i.days, manufacturer: 'M2'
8
+ end
9
+ @q = <<-EOF
10
+ scope: transaction
11
+ select:
12
+ - month
13
+ - manufacturer
14
+ - earnings
15
+ conditions:
16
+ year: 2012
17
+ group:
18
+ - manufacturer
19
+ - month
20
+ EOF
21
+ end
22
+
23
+ describe 'query' do
24
+ it 'should contain the given query' do
25
+ rep = TestReport.new user_id: 1
26
+ rep.query(@q).scope.should eq(rep.scope(:transaction))
27
+ rep.query(@q).select.should eq(['month', 'manufacturer', 'earnings'])
28
+ rep.query(@q).conditions.should eq('year' => 2012)
29
+ rep.query(@q).group.should eq(['manufacturer', 'month'])
30
+ end
31
+
32
+ describe 'result' do
33
+ it 'should only contain the selected columns' do
34
+ rep = TestReport.new user_id: 1
35
+ rep.query(@q).result.first.attributes.keys.map(&:to_sym).should eq([:month, :manufacturer, :earnings])
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,42 @@
1
+ require 'spec_helper'
2
+
3
+ describe TestReport do
4
+ it 'should contain all configured scopes' do
5
+ TestReport.scopes.should eq([:transaction])
6
+ end
7
+
8
+ describe 'transaction scope' do
9
+ it 'should have a relation' do
10
+ rep = TestReport.new(user_id: 1)
11
+ scope = rep.scope(:transaction)
12
+ scope.relation.class.should eq(ActiveRecord::Relation)
13
+ end
14
+
15
+ it 'should have all configured columns' do
16
+ rep = TestReport.new(user_id: 1)
17
+ scope = rep.scope(:transaction)
18
+ scope.columns.should eq([:manufacturer, :year, :month, :earnings])
19
+ end
20
+
21
+ describe 'column' do
22
+ it 'should return the column definition including "AS"' do
23
+ rep = TestReport.new(user_id: 1)
24
+ scope = rep.scope(:transaction)
25
+ scope.column(:manufacturer).select_definition.should eq('manufacturer AS manufacturer')
26
+ scope.column(:year).select_definition.should eq("strftime('%Y', date) AS year")
27
+ end
28
+
29
+ it 'should return the column value as is, if no getter is defined' do
30
+ rep = TestReport.new(user_id: 1)
31
+ scope = rep.scope(:transaction)
32
+ scope.column(:manufacturer).value('hallo welt').should eq('hallo welt')
33
+ end
34
+
35
+ it 'should return the value calculated by the getter method if defined' do
36
+ rep = TestReport.new(user_id: 1)
37
+ scope = rep.scope(:transaction)
38
+ scope.column(:month).value(2).should eq('Feb')
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,51 @@
1
+ require 'rubygems'
2
+ require 'bundler/setup'
3
+ require 'devlin'
4
+ require 'sqlite3'
5
+ require 'pry'
6
+ require 'factory_girl'
7
+
8
+ root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
9
+
10
+ RSpec.configure do |config|
11
+ config.mock_with :rspec
12
+ config.before :each do
13
+ ActiveRecord::Base.connection.increment_open_transactions
14
+ ActiveRecord::Base.connection.begin_db_transaction
15
+ end
16
+ config.after :each do
17
+ ActiveRecord::Base.connection.rollback_db_transaction
18
+ ActiveRecord::Base.connection.decrement_open_transactions
19
+ end
20
+ end
21
+
22
+ ActiveRecord::Base.establish_connection(
23
+ adapter: "sqlite3",
24
+ database: "#{root}/spec/test.sqlite3"
25
+ )
26
+
27
+ class TestReport < Devlin::Base
28
+ scope :transaction do |scope|
29
+ relation Transaction.where(user_id: scope.params[:user_id]).scoped
30
+
31
+ column :manufacturer, "manufacturer"
32
+ column :year, "strftime('%Y', date)"
33
+ column :month, "strftime('%m', date)" do |value|
34
+ months = %W(~ Jan Feb Mar Apr May Jun Jul Aug Sep Okt Nov Dec)
35
+ months[value]
36
+ end
37
+ column :earnings, "SUM(costs)"
38
+ end
39
+ end
40
+
41
+ ActiveRecord::Base.connection.execute("DROP TABLE IF EXISTS 'transactions'")
42
+ ActiveRecord::Base.connection.create_table(:transactions) do |t|
43
+ t.integer :user_id
44
+ t.string :manufacturer
45
+ t.datetime :date
46
+ t.float :costs
47
+ end
48
+ class Transaction < ActiveRecord::Base
49
+ end
50
+
51
+ FactoryGirl.find_definitions
data/spec/test.sqlite3 ADDED
Binary file
metadata ADDED
@@ -0,0 +1,102 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: devlin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Paul Spieker
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-08-17 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activerecord
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 3.0.0
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 3.0.0
30
+ - !ruby/object:Gem::Dependency
31
+ name: activesupport
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 3.0.0
38
+ type: :runtime
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ! '>='
44
+ - !ruby/object:Gem::Version
45
+ version: 3.0.0
46
+ description: Devlin gives users the ability to define reporting database queries
47
+ email:
48
+ - p.spieker@duenos.de
49
+ executables: []
50
+ extensions: []
51
+ extra_rdoc_files: []
52
+ files:
53
+ - .gitignore
54
+ - Gemfile
55
+ - LICENSE
56
+ - README.md
57
+ - Rakefile
58
+ - devlin.gemspec
59
+ - lib/devlin.rb
60
+ - lib/devlin/base.rb
61
+ - lib/devlin/column.rb
62
+ - lib/devlin/query.rb
63
+ - lib/devlin/scope.rb
64
+ - lib/devlin/version.rb
65
+ - spec/factories.rb
66
+ - spec/query_spec.rb
67
+ - spec/setup_spec.rb
68
+ - spec/spec_helper.rb
69
+ - spec/test.sqlite3
70
+ homepage: https://github.com/spieker/devlin
71
+ licenses: []
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ required_rubygems_version: !ruby/object:Gem::Requirement
83
+ none: false
84
+ requirements:
85
+ - - ! '>='
86
+ - !ruby/object:Gem::Version
87
+ version: '0'
88
+ requirements: []
89
+ rubyforge_project:
90
+ rubygems_version: 1.8.24
91
+ signing_key:
92
+ specification_version: 3
93
+ summary: Devlin gives users the ability to define reporting database queries. The
94
+ queries are defined in yaml and using predefined scopes. Since the sql statements
95
+ are defined by the developer, the user is not able to corrupt the database.
96
+ test_files:
97
+ - spec/factories.rb
98
+ - spec/query_spec.rb
99
+ - spec/setup_spec.rb
100
+ - spec/spec_helper.rb
101
+ - spec/test.sqlite3
102
+ has_rdoc: