athergin 0.0.1

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.
@@ -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,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in athergin.gemspec
4
+ gemspec
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Sarkis Karayan
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.
@@ -0,0 +1,30 @@
1
+ # Athergin
2
+
3
+ Athergin Web Framework
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'athergin'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install athergin
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
30
+
@@ -0,0 +1 @@
1
+ require 'bundler/gem_tasks'
@@ -0,0 +1,21 @@
1
+ # -*- encoding: utf-8 -*-
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'athergin/version'
5
+
6
+ Gem::Specification.new do |gem|
7
+ gem.name = 'athergin'
8
+ gem.version = Athergin::VERSION
9
+ gem.authors = ['Sarkis Karayan']
10
+ gem.email = ['skarayan@gmail.com']
11
+ gem.description = 'Athergin Web Framework'
12
+ gem.summary = 'Athergin Web Framework'
13
+ gem.homepage = 'https://github.com/skarayan/athergin'
14
+
15
+ gem.files = `git ls-files`.split($/)
16
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
+ gem.require_paths = ['lib']
19
+
20
+ gem.add_dependency 'core_extensions'
21
+ end
@@ -0,0 +1,10 @@
1
+ require 'athergin/version'
2
+ require 'athergin/attribute'
3
+ require 'athergin/platform'
4
+ require 'athergin/namespace'
5
+ require 'athergin/report'
6
+ require 'athergin/query'
7
+ require 'athergin/transformer'
8
+
9
+ module Athergin
10
+ end
@@ -0,0 +1,10 @@
1
+ module Attribute
2
+ def attribute(*names)
3
+ names.each do |name|
4
+ instance_variable_name = :"@#{ name }"
5
+ define_method name do |value=nil|
6
+ value ? instance_variable_set(instance_variable_name,value) : instance_variable_get(instance_variable_name)
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,69 @@
1
+ class Namespace
2
+ extend Attribute
3
+ attr_reader :reports, :transformers, :display_order
4
+ attribute :name, :description, :explanation
5
+
6
+ def to_h
7
+ methods = [:name, :description, :current?, :css_class_name, :report_objects]
8
+ methods.map do |method|
9
+ key, value = method.to_s.gsub(/\?$/,'').to_sym, send(method)
10
+ value = '' if value.nil?
11
+ value = value.to_data if value.is_a? Array
12
+
13
+ [key, value]
14
+ end.to_h
15
+ end
16
+
17
+ class << self
18
+ def all
19
+ Platform.namespaces.values rescue []
20
+ end
21
+
22
+ def displayable
23
+ all.select { |n| n.report_objects.present? }.sort { |a,b| a.display_order <=> b.display_order }
24
+ end
25
+
26
+ def find_by_name(name)
27
+ all.find { |namespace| namespace.name == name.try(:to_sym) }
28
+ end
29
+ end
30
+
31
+ def initialize(name, display_order)
32
+ @name, @display_order = name, display_order
33
+ @reports = {}
34
+ @transformers = {}
35
+ end
36
+
37
+ # todo: change this to reports
38
+ def report_objects
39
+ reports.values
40
+ end
41
+
42
+ # todo: change this to transformers
43
+ def transformer_objects
44
+ transformers.values
45
+ end
46
+
47
+ def report(name, &block)
48
+ warn "warning: overriding report '#{ name }'" if @reports[name]
49
+
50
+ @reports[name] = Report.new name, namespace: self
51
+ @reports[name].instance_eval &block
52
+ end
53
+
54
+ def transformer(name, &block)
55
+ warn "warning: overriding transformer '#{ name }'" if @transformers[name]
56
+
57
+ @transformers[name] = Transformer.new name
58
+ @transformers[name].instance_eval &block
59
+ end
60
+
61
+ def current?
62
+ name.eql? Platform.current_namespace.name
63
+ end
64
+
65
+ # todo: think this over, models are probably not a good place for the css class name
66
+ def css_class_name
67
+ current? ? 'active' : 'inactive'
68
+ end
69
+ end
@@ -0,0 +1,108 @@
1
+ # todo: module Athergin
2
+ module Platform
3
+ class << self
4
+ attr_reader :config, :connections, :namespaces, :eval_queue
5
+
6
+ def load_config!(config_file='config/environment.yml')
7
+ @config = Configuration.new config_file
8
+ end
9
+
10
+ def connect!
11
+ @connections = {}
12
+ config.environments.each do |env|
13
+ database = config.database_config env
14
+ begin
15
+ pool_size, pool_timeout = database.pool_size || 50, database.pool_timeout || 60
16
+ if database.hosts.present?
17
+ @connections[env] = Mongo::ReplSetConnection.new database.hosts, pool_size: pool_size, pool_timeout: pool_timeout
18
+ else
19
+ @connections[env] = Mongo::Connection.new database.hostname, database.port, pool_size: pool_size, pool_timeout: pool_timeout
20
+ end
21
+ rescue Mongo::ConnectionFailure => e
22
+ warn "Warning (could not connect to the #{ env } environment, skipping): #{ e.message }"
23
+ end
24
+ end
25
+ end
26
+
27
+ def namespace(name, opts={}, &block)
28
+ file_execution_order = namespace_display_order = opts[:display_order] || 10_000
29
+
30
+ @namespaces = {} if @namespaces.nil?
31
+ @namespaces[name] = Namespace.new(name, namespace_display_order) if @namespaces[name].nil?
32
+
33
+ @eval_queue = [] if @eval_queue.nil?
34
+ @eval_queue << [file_execution_order, @namespaces[name], block]
35
+ end
36
+
37
+ # execute the files in the right order as specified in the reports directory (for display order in index page and menu)
38
+ def run_eval_queue!
39
+ eval_queue.sort { |a,b| a.first <=> b.first }.each do |file_execution_order,namespace,block|
40
+ namespace.instance_eval &block
41
+ end
42
+ end
43
+
44
+ def params
45
+ Thread.current[:params]
46
+ end
47
+
48
+ def search_params
49
+ (params[:search] || {}).reject { |param,value| value.blank? }
50
+ end
51
+
52
+ def exact_match?
53
+ params[:exact_match].present?
54
+ end
55
+
56
+ def query_limit
57
+ params[:limit]
58
+ end
59
+
60
+ def query_offset
61
+ params[:offset]
62
+ end
63
+
64
+ def cookies
65
+ Thread.current[:cookies] || {}
66
+ end
67
+
68
+ def environment
69
+ @environment || cookies['environment'].try(:to_sym) || ENV['REPORTS_ENV'].try(:to_sym) || :development
70
+ end
71
+
72
+ def set_environment!(env)
73
+ puts "Setting environment as #{ env }"
74
+ @environment = env.to_sym
75
+ end
76
+
77
+ def connected_environments
78
+ # todo: reverse is kind of ugly here
79
+ @connections.keys.reverse rescue []
80
+ end
81
+
82
+ def connection
83
+ @connections[environment]
84
+ end
85
+
86
+ def database_name(name)
87
+ database_name = Platform.config.database_override.try(name) || name
88
+ end
89
+
90
+ def database(name)
91
+ Platform.connection[database_name(name)]
92
+ end
93
+
94
+ def current_namespace
95
+ current_report.try(:namespace)
96
+ end
97
+
98
+ def current_report
99
+ return unless params[:type].eql? 'reports'
100
+ Report.find_by_name params[:name]
101
+ end
102
+
103
+ def current_query
104
+ return unless params[:type].eql? 'queries'
105
+ Query.find_by_name params[:name]
106
+ end
107
+ end
108
+ end
@@ -0,0 +1,189 @@
1
+ class Query
2
+ extend Attribute
3
+ attribute :name, :description, :explanation, :database, :filters, :fields, :field_groups, :search_mapping
4
+ attr_accessor :partial_search_fields # todo: clean this up
5
+ attr_accessor :results_block, :query_block, :transform_block, :callback_blocks # todo: rename all these *_block attributes to remove the '_block' from the name
6
+
7
+ class << self
8
+ # todo: change this to queries
9
+ def all
10
+ Report.all.map(&:query_objects).flatten
11
+ end
12
+
13
+ def find_by_name(name)
14
+ all.find { |query| query.name == name.try(:to_sym) }
15
+ end
16
+ end
17
+
18
+ def initialize(name)
19
+ @name = name
20
+ @fields = {}
21
+ @search_mapping = {}
22
+ @partial_search_fields = []
23
+ @default_sort_fields = {}
24
+ @filters = {}
25
+ @show_all_on_load = false
26
+ @transform_block = Proc.new { raise 'Please implement the transform block in your query.' }
27
+ @results_block = Proc.new { query.to_a.map { |row| self.instance_exec row, &transform_block } }
28
+ @callback_blocks = {}
29
+ end
30
+
31
+ def collection(name=nil)
32
+ if name
33
+ if name.is_a? Symbol
34
+ @collection = name
35
+ elsif name.is_a? String
36
+ db,tbl = name.split('/').map(&:to_sym)
37
+ database db
38
+ collection tbl
39
+ end
40
+ else
41
+ @collection
42
+ end
43
+ end
44
+
45
+ # todo: change naming of this method to make it more consistent
46
+ def allow_partial_search_for(*fields)
47
+ @partial_search_fields = fields.map(&:to_sym)
48
+ end
49
+
50
+ # todo: refactor to use attribute for consistency
51
+ def limit_results_to(count)
52
+ @limit = count
53
+ end
54
+
55
+ def params
56
+ Platform.search_params
57
+ end
58
+
59
+ def limit
60
+ Platform.query_limit || @limit || 1000
61
+ end
62
+
63
+ def offset
64
+ Platform.query_offset || @offset || 0
65
+ end
66
+
67
+ def exact_match
68
+ Platform.exact_match? || @exact_match || false
69
+ end
70
+
71
+ def partial_match
72
+ !exact_match
73
+ end
74
+
75
+ # todo: remove to_sym (everywhere in code), use hash_with_indifferent_attributes
76
+ def allow_partial_match?(field=nil)
77
+ if field.present?
78
+ partial_match && partial_search_fields.include?(field.to_sym)
79
+ else
80
+ @partial_search_fields.present?
81
+ end
82
+ end
83
+
84
+ def where
85
+ params.map do |field,value|
86
+ field = search_mapping[field.to_sym]
87
+ next if field.nil?
88
+
89
+ value = /#{ value }/i if allow_partial_match? field
90
+ [field,value]
91
+ end.compact.to_h.merge @filters
92
+ end
93
+
94
+ # todo: refactor @filters and @default_sort_fields to method calls
95
+ def query
96
+ return self.instance_eval(&query_block) if query_block
97
+
98
+ # todo: add namespace and report name in error message
99
+ raise "No collection specified for '#{ name }'" if collection.nil?
100
+ raise "No database specified for '#{ name }'" if database.nil?
101
+
102
+ puts "find: #{ Platform.database_name(database.to_s) }/#{ collection } -> #{ where.inspect }"
103
+ Platform.database(database.to_s)[collection.to_s].find(where).sort(@default_sort_fields).limit(limit).skip(offset)
104
+ end
105
+
106
+ def define_query(&block)
107
+ @query_block = block
108
+ end
109
+
110
+ def results(&block)
111
+ @results_block = block
112
+ end
113
+
114
+ def transform(&block)
115
+ @transform_block = block
116
+ end
117
+
118
+ def data
119
+ self.instance_eval &results_block
120
+ end
121
+
122
+ # todo: rename for consistensy
123
+ def default_sort_by(sort_fields)
124
+ @default_sort_fields = sort_fields
125
+ end
126
+
127
+ # todo: review since it is redundant in both Report and Query
128
+ def show_all_on_load
129
+ @show_all_on_load = true
130
+ end
131
+
132
+ def require_search?
133
+ !show_all_on_load?
134
+ end
135
+
136
+ def show_all_on_load?
137
+ @show_all_on_load
138
+ end
139
+
140
+ def view_name
141
+ :query
142
+ end
143
+
144
+ def listen_for(callback,&block)
145
+ @callback_blocks[callback] = block
146
+ end
147
+
148
+ # todo: cleanup
149
+ =begin
150
+ def to_tsv
151
+ results.unshift(self.class.fields).map { |row| row.join("\t") }.join("\n")
152
+ end
153
+
154
+ def save(filename)
155
+ File.open(filename, 'w') { |f| f.write to_tsv }
156
+ end
157
+
158
+ class << self
159
+ def from(collection_name)
160
+ db,tbl = collection_name.split('/')
161
+ define_singleton_method(:from_database) { db }
162
+ define_singleton_method(:from_collection) { tbl }
163
+ end
164
+
165
+ def group_by(field_name)
166
+ define_singleton_method(:group_by_field) { field_name }
167
+ end
168
+
169
+ def aggregate(fields)
170
+ define_singleton_method(:aggregate_fields) { fields }
171
+ end
172
+
173
+ def javascript_aggregate_map_function
174
+ erb = File.read('mapreduce/aggregate_map.js.erb')
175
+ Erubis::Eruby.new(erb).result group_by: group_by_field, aggregate: aggregate_fields
176
+ end
177
+
178
+ def javascript_aggregate_reduce_function
179
+ erb = File.read('mapreduce/aggregate_reduce.js.erb')
180
+ Erubis::Eruby.new(erb).result aggregate: aggregate_fields
181
+ end
182
+
183
+ def run
184
+ m, r = javascript_aggregate_map_function, javascript_aggregate_reduce_function
185
+ Platform.database(from_database)[from_collection].map_reduce m, r, out: { replace: collection, db: database }
186
+ end
187
+ end
188
+ =end
189
+ end
@@ -0,0 +1,79 @@
1
+ class Report
2
+ extend Attribute
3
+ attr_reader :queries, :namespace
4
+ attribute :name, :description, :explanation, :search_fields
5
+
6
+ def to_h
7
+ methods = [:name, :description, :url, :current?, :css_class_name]
8
+ methods.map do |method|
9
+ key, value = method.to_s.gsub(/\?$/,'').to_sym, send(method)
10
+ value = '' if value.nil?
11
+ value = value.to_data if value.is_a? Array
12
+
13
+ [key, value]
14
+ end.to_h
15
+ end
16
+
17
+ class << self
18
+ # todo: change this to reports
19
+ def all
20
+ Namespace.all.map(&:report_objects).flatten
21
+ end
22
+
23
+ def find_by_name(name)
24
+ all.find { |report| report.name == name.try(:to_sym) }
25
+ end
26
+ end
27
+
28
+ def initialize(name,opts={})
29
+ @name = name
30
+ @namespace = opts[:namespace]
31
+ @queries = {}
32
+ @show_all_on_load = false
33
+ end
34
+
35
+ # todo: change this to queries
36
+ def query_objects
37
+ queries.values
38
+ end
39
+
40
+ def url
41
+ "/reports/#{ name }"
42
+ end
43
+
44
+ def query(name, &block)
45
+ warn "warning: overriding query '#{ name }'" if @queries[name]
46
+
47
+ @queries[name] = Query.new name
48
+ @queries[name].instance_eval &block
49
+ end
50
+
51
+ def show_all_on_load
52
+ @show_all_on_load = true
53
+ end
54
+
55
+ def require_search?
56
+ !show_all_on_load?
57
+ end
58
+
59
+ def show_all_on_load?
60
+ @show_all_on_load
61
+ end
62
+
63
+ def allow_partial_match?
64
+ query_objects.select { |query| query.allow_partial_match? }.present?
65
+ end
66
+
67
+ def view_name
68
+ :report
69
+ end
70
+
71
+ def current?
72
+ name.eql? Platform.current_report.name
73
+ end
74
+
75
+ # todo: think this over, models are probably not a good place for the css class name
76
+ def css_class_name
77
+ current? ? 'active' : 'inactive'
78
+ end
79
+ end
@@ -0,0 +1,61 @@
1
+ class Transformer
2
+ extend Attribute
3
+ attr_reader :maps
4
+ attribute :defaults, :reduce, :database
5
+
6
+ def initialize(name)
7
+ @maps, @reduce = [], []
8
+ end
9
+
10
+ def collection(name=nil)
11
+ if name
12
+ if name.is_a? Symbol
13
+ @collection = name
14
+ elsif name.is_a? String
15
+ db,tbl = name.split('/').map(&:to_sym)
16
+ database db
17
+ collection tbl
18
+ end
19
+ else
20
+ @collection
21
+ end
22
+ end
23
+
24
+ def map(hash)
25
+ raise 'Please specify collection for Transformer map' if hash[:collection].nil?
26
+ raise 'Please specify values for Transformer map' if hash[:values].nil?
27
+ @maps.push hash
28
+ end
29
+
30
+ def sum(field)
31
+ "+= value.#{ field } ? value.#{ field } : 0"
32
+ end
33
+
34
+ def mongodb_map_functions
35
+ erb = File.read('mapreduce/mongodb_map.js.erb')
36
+ maps.map do |map|
37
+ database, collection = map[:collection].split('/')
38
+ function = Erubis::Eruby.new(erb).result keys: map[:keys], values: map[:values]
39
+ [database,collection,function]
40
+ end
41
+ end
42
+
43
+ def mongodb_reduce_function
44
+ erb = File.read('mapreduce/mongodb_reduce.js.erb')
45
+ Erubis::Eruby.new(erb).result defaults: defaults, fields: reduce
46
+ end
47
+
48
+ def run_mapreduce
49
+ puts "Dropping collection #{ Platform.database_name(database) }/#{ collection }"
50
+ Platform.database(database.to_s)[collection.to_s].drop
51
+
52
+ mongodb_map_functions.each do |map_database,map_collection,mongodb_map_function|
53
+ puts "Loading #{ Platform.database_name(database.to_s) }/#{ collection } from #{ Platform.database_name(map_database) }/#{ map_collection }"
54
+ Platform.database(map_database)[map_collection].map_reduce mongodb_map_function,
55
+ mongodb_reduce_function,
56
+ out: { reduce: collection.to_s, db: Platform.database_name(database.to_s) }
57
+ end
58
+
59
+ true
60
+ end
61
+ end
@@ -0,0 +1,3 @@
1
+ module Athergin
2
+ VERSION = '0.0.1'
3
+ end
metadata ADDED
@@ -0,0 +1,71 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: athergin
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Sarkis Karayan
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-09-15 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: core_extensions
16
+ version_requirements: &2056 !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - ! '>='
19
+ - !ruby/object:Gem::Version
20
+ version: '0'
21
+ none: false
22
+ requirement: *2056
23
+ prerelease: false
24
+ type: :runtime
25
+ description: Athergin Web Framework
26
+ email:
27
+ - skarayan@gmail.com
28
+ executables: []
29
+ extensions: []
30
+ extra_rdoc_files: []
31
+ files:
32
+ - .gitignore
33
+ - Gemfile
34
+ - LICENSE.txt
35
+ - README.md
36
+ - Rakefile
37
+ - athergin.gemspec
38
+ - lib/athergin.rb
39
+ - lib/athergin/attribute.rb
40
+ - lib/athergin/namespace.rb
41
+ - lib/athergin/platform.rb
42
+ - lib/athergin/query.rb
43
+ - lib/athergin/report.rb
44
+ - lib/athergin/transformer.rb
45
+ - lib/athergin/version.rb
46
+ homepage: https://github.com/skarayan/athergin
47
+ licenses: []
48
+ post_install_message:
49
+ rdoc_options: []
50
+ require_paths:
51
+ - lib
52
+ required_ruby_version: !ruby/object:Gem::Requirement
53
+ requirements:
54
+ - - ! '>='
55
+ - !ruby/object:Gem::Version
56
+ version: '0'
57
+ none: false
58
+ required_rubygems_version: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - ! '>='
61
+ - !ruby/object:Gem::Version
62
+ version: '0'
63
+ none: false
64
+ requirements: []
65
+ rubyforge_project:
66
+ rubygems_version: 1.8.15
67
+ signing_key:
68
+ specification_version: 3
69
+ summary: Athergin Web Framework
70
+ test_files: []
71
+ ...