omnis 0.0.1 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,14 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+
4
+ guard 'rspec', :cli => "--color --format nested --fail-fast", :notification => true,
5
+ :run_all => { :cli => "--color --fail-fast" } do
6
+ watch(%r{^spec/.+_spec\.rb$})
7
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
8
+ watch('spec/spec_helper.rb') { "spec" }
9
+
10
+ # Turnip features and steps
11
+ # watch(%r{^spec/acceptance/(.+)\.feature$})
12
+ # watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) { |m| Dir[File.join("**/#{m[1]}.feature")][0] || 'spec/acceptance' }
13
+ end
14
+
data/README.md CHANGED
@@ -1,6 +1,135 @@
1
1
  # Omnis
2
+ The goal is to simplify standard and repetetive queries to Mongo and presenting their results.
3
+ To do this Omnis provides a Query and a Transformer, both can be configured using a DSL.
2
4
 
3
- TODO: Write a gem description
5
+ ## Query
6
+ Converts a params Hash into Operators to be able to easily build queries against databases et al. This is a generic way to process incoming parameters.
7
+
8
+ ```ruby
9
+ { "ref_anixe" => "1abc"}
10
+ ```
11
+ becomes
12
+ ```ruby
13
+ Matches.new(:ref_anixe, "1abc")
14
+ ```
15
+
16
+ Example:
17
+ ```ruby
18
+ class SomeQuery
19
+ include Omnis::Query
20
+
21
+ def self.parse_date(params, name)
22
+ param = params[name]
23
+ return nil if param.nil?
24
+ time = Time.parse(param)
25
+ Between.new(name, time.getlocal.beginning_of_day..time.getlocal.end_of_day)
26
+ end
27
+
28
+ param :ref_anixe, Matches
29
+ param :passenger, Equals
30
+ param(:date, Between) {|params| self.parse_date(params, :date) }
31
+ end
32
+ ```
33
+
34
+ If a lambda used for extraction returns `nil`, the parameter will be removed.
35
+
36
+ Params also support defaults as values or as lambdas which will be executed at the time the extraction of the values happens. This way you can build pre-defined queries and if required only override some values. The difference to normal blocks for extraction is that, the latter is not called if the param is not in the inputs - in this case the default will be used.
37
+
38
+ ```ruby
39
+ param :date_from, Between, :default => Between.new("services.date_from", tomorrow.beginning_of_day..tomorrow.end_of_day)
40
+ param :contract, Matches, :default => "^wotra."
41
+ ```
42
+
43
+ ## MongoQuery
44
+ This covers a standard use case where you have a bunch of params in a Hash, for instance from a web request and you need validation, and transformation of the incoming values.
45
+ No actual calls to mongo are done.
46
+
47
+ Example:
48
+ ```ruby
49
+ class BookingQuery
50
+ include Omnis::MongoQuery
51
+
52
+ # collection Mongo::Connection.new['bms']['bookings'] # planned!?
53
+
54
+ param :ref_anixe, Equals
55
+ param :contract, Matches
56
+ param :description, Matches
57
+ param :status, Matches
58
+ param :product, BeginsWith
59
+ param :agency, Equals
60
+
61
+ # if this param is in the query, fetch the field "ref_customer"
62
+ param :ref_customer, Matches, :field => "ref_customer"
63
+
64
+ # those fields are always fetched
65
+ fields %w[ref_anixe contract description status product agency passengers date_status_modified services]
66
+ end
67
+ ```
68
+
69
+ Usage:
70
+ ```ruby
71
+ query = BookingQuery.new("ref_anixe" => "1abc", "product" => "HOT")
72
+ mongo = query.to_mongo
73
+ Mongo::Connection.new['bms']['bookings'].find(mongo.selector, mongo.opts)
74
+ ```
75
+
76
+ ## Transformer
77
+ Transforms some data into another form of (flattened) data. Extractors can be used to get values from the data source.
78
+ If the first parameter of a property denotes the output field, the second is a string which is passed as argument to the extractor.
79
+
80
+ Example:
81
+ ```ruby
82
+ class BookingTransformer
83
+ include Omnis::DataTransformer
84
+ extractor Omnis::NestedHashExtractor.new
85
+
86
+ property :ref_anixe, "ref_anixe"
87
+ property :ref_customer, "ref_customer"
88
+ property :status, "status"
89
+ property(:passenger) {|doc| Maybe(doc)['passengers'].map {|v| v.first.values.slice(1..2).join(' ') }.or('Unknown').fetch.to_s }
90
+ property :date "date_status_modified", :default => Time.at(0), :format => ->v { v.to_s(:date) }
91
+ property :description, "description"
92
+ property :product, "product"
93
+ property :contract, "contract"
94
+ property :agency, "agency"
95
+ property :date_from, "services.0.date_from", :default => "n/a", :format => ->v { v.to_s(:date) }
96
+ property :date_to, "services.0.date_to", :default => "n/a", :format => ->v { v.to_s(:date) }
97
+ end
98
+ ```
99
+
100
+ Usage:
101
+ ```ruby
102
+ transformer = BookingTransformer.new
103
+ transformer.transform(doc)
104
+ ```
105
+ This will produce a Hash like `{:ref_anixe => "1abc", :status => "book_confirmed" ... }`
106
+
107
+ If you provide blocks for all properties, an Extractor is not required
108
+
109
+ ```ruby
110
+ class ExtractorlessTransformer
111
+ include Omnis::DataTransformer
112
+ property(:ref) {|src| src["ref_anixe"] }
113
+ end
114
+ ```
115
+
116
+ If you provide a `#to_object(hash)` method in the Transformer definition, it will be used to convert the output Hash into the object of you desire.
117
+
118
+ ## Putting it all together
119
+
120
+ ```ruby
121
+ query = BookingQuery.new("ref_anixe" => "1abc", "product" => "HOT").to_mongo
122
+ transformer = BookingTransformer.new.to_proc
123
+ collection = Mongo::Connection.new['bms']['bookings']
124
+
125
+ table = collection.find(query.selector, query.opts.merge(:transformer => transformer))
126
+
127
+ table = Omnis::MongoTable.new(connection, params, BookingQuery, BookingTransformer)
128
+
129
+ table.call.each do |row|
130
+ row.
131
+ end
132
+ ```
4
133
 
5
134
  ## Installation
6
135
 
@@ -1,5 +1,9 @@
1
1
  require "omnis/version"
2
2
 
3
- module Omnis
4
- # Your code goes here...
5
- end
3
+ require 'active_support/core_ext/hash'
4
+ require 'omnis/nested_hash_extractor'
5
+ require 'omnis/transformer'
6
+ require 'omnis/operators'
7
+ require 'omnis/query'
8
+ require 'omnis/mongo_query'
9
+ require 'omnis/mongo_report'
@@ -0,0 +1,79 @@
1
+ require 'ostruct'
2
+
3
+ module Omnis
4
+ module MongoQuery
5
+ include Omnis::Query
6
+ def self.included(base)
7
+ base.class_eval do
8
+ include InstanceMethods
9
+ extend ClassMethods
10
+ end
11
+ end
12
+
13
+ module ClassMethods
14
+ include Omnis::Query::ClassMethods
15
+
16
+ attr_reader :page_param_name, :items_per_page
17
+ def field_list
18
+ @fields ||= []
19
+ end
20
+ def fields(list)
21
+ field_list.concat(list)
22
+ end
23
+
24
+ def page(page_param_name, opts={})
25
+ @page_param_name = page_param_name
26
+ @items_per_page = opts[:items_per_page] || 10
27
+ end
28
+ end
29
+
30
+ module InstanceMethods
31
+ include Omnis::Operators
32
+
33
+ def to_mongo
34
+ extracted_params = extract
35
+ OpenStruct.new({ :selector => mongo_selector(extracted_params),
36
+ :opts => mongo_opts(extracted_params)})
37
+ end
38
+
39
+ private
40
+ def page
41
+ return 0 unless @input_params.has_key? page_param_name
42
+ return @input_params[page_param_name].to_i - 1
43
+ end
44
+
45
+ def skip
46
+ page * items_per_page
47
+ end
48
+
49
+ def page_param_name
50
+ self.class.page_param_name || :page
51
+ end
52
+
53
+ def items_per_page
54
+ self.class.items_per_page || 20
55
+ end
56
+
57
+ def mongo_operator(operator)
58
+ case operator
59
+ when Equals; operator.value
60
+ when Matches; /#{operator.value}/i
61
+ when BeginsWith; /^#{operator.value}/i
62
+ when Between; { :'$gte' => operator.value.begin, :'$lt' => operator.value.end}
63
+ end
64
+ end
65
+
66
+ def mongo_selector(extracted_params)
67
+ Hash[extracted_params.map { |operator| [operator.key, mongo_operator(operator)] }]
68
+ end
69
+
70
+ def mongo_opts(extracted_params)
71
+ params_with_extra_fields = extracted_params.collect(&:opts).select {|e| e.has_key? :field}
72
+ extra_fields = params_with_extra_fields.map {|e| e[:field]}
73
+ fields = self.class.field_list.concat(extra_fields)
74
+ { :limit => items_per_page, :skip => skip, :fields => fields }
75
+ end
76
+ end
77
+ end
78
+ end
79
+
@@ -0,0 +1,13 @@
1
+ module Omnis
2
+ module MongoReport
3
+ def find
4
+ @input.find(@query.selector, @query.opts.merge(:transformer => @transformer))
5
+ end
6
+
7
+ def run
8
+ find.each do |row|
9
+ @output.save(row)
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,29 @@
1
+ require 'monadic'
2
+
3
+ module Omnis
4
+ class NestedHashExtractor
5
+ # returns a lambda which extracts a value from a nested hash
6
+ def extractor(path)
7
+ raise ArgumentError("path to extract must be a string") unless String === path
8
+ expr = "source#{from_dot_path(path)} rescue Nothing"
9
+ ->source { eval(expr) }
10
+ end
11
+
12
+ private
13
+ # convert from a path to a ruby expression (as string)
14
+ def from_dot_path(path)
15
+ return nil if path.nil?
16
+ path.split('.').map {|i| field(i) }.join
17
+ end
18
+
19
+ def field(f)
20
+ return '[' << f << ']' if is_i?(f)
21
+ return '.fetch("' << f << '", Nothing)'
22
+ end
23
+
24
+ # checks if the string is a number
25
+ def is_i?(s)
26
+ !!(s =~ /^[-+]?[0-9]+$/)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,36 @@
1
+ module Omnis
2
+ module Operators
3
+ class NullOperator
4
+ attr_reader :key, :value, :opts
5
+ def initialize(key, value, opts={})
6
+ @key, @value, @opts = key, value, opts
7
+ end
8
+
9
+ def ==(other)
10
+ return false unless other.is_a? self.class
11
+ return false unless @key == other.key
12
+ @value == other.value
13
+ end
14
+
15
+ def to_s
16
+ klas = self.class.to_s.downcase.split('::')[-1]
17
+ "#{@key.to_s} #{klas} #{@value}"
18
+ end
19
+ end
20
+
21
+ class Matches < NullOperator
22
+ end
23
+
24
+ class Equals < NullOperator
25
+ end
26
+
27
+ class Gte < NullOperator
28
+ end
29
+
30
+ class Between < NullOperator
31
+ end
32
+
33
+ class BeginsWith < NullOperator
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,67 @@
1
+ require 'active_support/core_ext/hash'
2
+
3
+ module Omnis
4
+ module Query
5
+
6
+ class Param
7
+ attr_reader :name, :operator
8
+ def initialize(name, operator, opts={}, &extractor)
9
+ # raise ArgumentError("operator must be a descendant of Omnis::Operators::NullOperator") unless operator.is_a? Omnis::Operators::NullOperator
10
+ extractor ||= ->params { params[name] }
11
+ @name, @operator, @opts, @extractor = name, operator, opts, extractor
12
+ end
13
+
14
+ def ==(other)
15
+ return false unless other.is_a? self.class
16
+ return false unless @name == other.name
17
+ @operator == other.operator
18
+ end
19
+
20
+ # extracts the value for a param, using the extractor lamba or the default value
21
+ def extract(params)
22
+ value = @extractor.(params) || default
23
+ return value if value.is_a? Omnis::Operators::NullOperator
24
+ return @operator.new(@name, value, @opts) unless value.nil?
25
+ end
26
+
27
+ def default
28
+ expr = @opts[:default]
29
+ return expr.call if expr.is_a? Proc
30
+ return expr
31
+ end
32
+ end
33
+
34
+ def self.included(base)
35
+ base.class_eval do
36
+ extend ClassMethods
37
+ include InstanceMethods
38
+ include Omnis::Operators
39
+ end
40
+ end
41
+
42
+ module ClassMethods
43
+ def params
44
+ @params ||= {}
45
+ end
46
+
47
+ def param(name, operator, opts={}, &block)
48
+ Omnis::Query::Param.new(name, operator, opts, &block).tap do |param|
49
+ params[param.name] = param
50
+ end
51
+ end
52
+ end
53
+ module InstanceMethods
54
+ def initialize(input_params)
55
+ @input_params = input_params.symbolize_keys
56
+ end
57
+
58
+ def fetch(name)
59
+ self.class.params.fetch(name).extract(@input_params)
60
+ end
61
+
62
+ def extract
63
+ self.class.params.map { |k,v| v.extract(@input_params) }.compact
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,86 @@
1
+ module Omnis
2
+ module Transformer
3
+ class Property
4
+ attr_reader :name, :expr, :opts
5
+ def initialize(name, expr, opts, extractor)
6
+ @name, @expr, @opts, @extractor = name, expr, opts, extractor
7
+ end
8
+
9
+ def default
10
+ opts[:default]
11
+ end
12
+
13
+ def format
14
+ opts[:format]
15
+ end
16
+
17
+ def extract(source)
18
+ @extractor.call(source)
19
+ end
20
+ end
21
+
22
+ def self.included(base)
23
+ base.class_eval do
24
+ extend ClassMethods
25
+ include InstanceMethods
26
+ end
27
+ end
28
+
29
+ module ClassMethods
30
+ def properties
31
+ @properties ||= {}
32
+ end
33
+
34
+ # if an expr is provided it will be passed to the configured extractor,
35
+ # otherwise a block is required
36
+ def property(name, expr=nil, opts={}, &block)
37
+ raise ArgumentError if (expr.nil? && block.nil?)
38
+
39
+ xtr = case expr
40
+ when String; @extractor.extractor(expr)
41
+ when nil ; block
42
+ end
43
+
44
+ Omnis::Transformer::Property.new(name, expr, opts, xtr).tap do |prop|
45
+ properties[prop.name] = prop
46
+ end
47
+ end
48
+
49
+ def extractor(obj)
50
+ @extractor = obj
51
+ end
52
+
53
+ def extract(source, expr)
54
+ @extractor.extractor(expr).call(source)
55
+ end
56
+ end
57
+
58
+ module InstanceMethods
59
+ def __extract(property, source)
60
+ value = property_value(property, source)
61
+ if property.format
62
+ property.format.call(value)
63
+ else
64
+ value
65
+ end
66
+ end
67
+
68
+ def property_value(property, source)
69
+ value = property.extract(source)
70
+ return property.default if property.default && (value == Nothing || value.nil?)
71
+ return value
72
+ end
73
+
74
+ def transform(source)
75
+ result = Hash[self.class.properties.map do |k, v| [k, __extract(v, source)] end]
76
+ respond_to?(:to_object) ? to_object(result) : result
77
+ end
78
+
79
+ # provides a Proc to the transform method, for use e.g. with Mongo documents
80
+ # If you want to cache a transformer for reuse, you can cache just this Proc
81
+ def to_proc
82
+ method(:transform).to_proc
83
+ end
84
+ end
85
+ end
86
+ end
@@ -1,3 +1,3 @@
1
1
  module Omnis
2
- VERSION = "0.0.1"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -8,7 +8,7 @@ Gem::Specification.new do |gem|
8
8
  gem.version = Omnis::VERSION
9
9
  gem.authors = ["Piotr Zolnierek"]
10
10
  gem.email = ["pz@anixe.pl"]
11
- gem.description = %q{Helps a read-only ORM kind-of}
11
+ gem.description = %q{Helps with a read-only ORM kind-of, more useful than the description}
12
12
  gem.summary = %q{see above}
13
13
  gem.homepage = "http://github.com/pzol/omnis"
14
14
 
@@ -16,11 +16,15 @@ Gem::Specification.new do |gem|
16
16
  gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
17
17
  gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
18
18
  gem.require_paths = ["lib"]
19
+ gem.add_dependency 'activesupport'
20
+ gem.add_dependency 'bson_ext', '>=1.7.0'
21
+ gem.add_dependency 'monadic'
22
+ gem.add_dependency 'mongo', '>=1.7.0'
19
23
  gem.add_development_dependency 'rspec', '>=2.9.0'
20
24
  gem.add_development_dependency 'guard'
21
25
  gem.add_development_dependency 'guard-rspec'
22
26
  gem.add_development_dependency 'guard-bundler'
23
27
  gem.add_development_dependency 'growl'
24
- gem.add_development_dependency 'activesupport'
25
28
  gem.add_development_dependency 'rake'
29
+ gem.add_development_dependency 'rb-fsevent', '~> 0.9.1'
26
30
  end
@@ -0,0 +1,109 @@
1
+ require 'spec_helper'
2
+ require 'omnis/operators'
3
+ require 'omnis/query'
4
+ require 'omnis/mongo_query'
5
+ require 'active_support/core_ext/date/calculations'
6
+ require 'active_support/core_ext/time/calculations'
7
+
8
+ describe Omnis::MongoQuery do
9
+ class TestIntegrationQuery
10
+ include Omnis::MongoQuery
11
+
12
+ # collection Mongo::Connection.new['bms']['bookings']
13
+ def self.parse_date(params, name)
14
+ param = params[name]
15
+ return nil if param.nil?
16
+ time = Time.parse(param)
17
+ Between.new(name, time.beginning_of_day..time.end_of_day)
18
+ end
19
+
20
+ param :ref_anixe, Equals
21
+ param(:date, Between) {|source| parse_date(source, :date)}
22
+ param :contract, Matches
23
+ param :description, Matches
24
+ param :status, Matches
25
+ param :product, BeginsWith
26
+ param :agency, Equals
27
+
28
+ page :page, :items_per_page => 20
29
+
30
+ # those fields are always fetched
31
+ fields %w[ref_anixe contract description status product agency passengers date_status_modified services]
32
+ end
33
+
34
+ it "works altogether" do
35
+ t = TestIntegrationQuery.new("ref_anixe" => "1abc", "contract" => "test", "product" => "HOT", "page" => "2", "date" => "2012-10-12")
36
+ t.to_mongo.selector.should == { :ref_anixe => "1abc",
37
+ :contract => /test/i,
38
+ :product => /^HOT/i,
39
+ :date => { :'$gte' => Time.local(2012, 10, 12, 0, 0, 0), :'$lt' => Time.local(2012, 10, 12, 23, 59, 59, 999999.999)}
40
+ }
41
+
42
+ fields = %w[ref_anixe contract description status product agency passengers date_status_modified services]
43
+ t.to_mongo.opts.should == { :limit => 20, :skip => 20, :fields => fields}
44
+ end
45
+
46
+ context 'fields' do
47
+ class TestFieldsQuery
48
+ include Omnis::MongoQuery
49
+
50
+ fields %w[ref_anixe status]
51
+ # if this param is in the query, fetch the field "ref_customer"
52
+ param :ref_customer, Matches, :field => "ref_customer"
53
+ end
54
+
55
+ it 'extra field not requested when param not present' do
56
+ t = TestFieldsQuery.new({})
57
+ t.to_mongo.opts.should == { :limit => 20, :skip => 0, :fields => ['ref_anixe', 'status']}
58
+ end
59
+
60
+ it 'extra field is requested when param is in request' do
61
+ t = TestFieldsQuery.new({"ref_customer" => "123"})
62
+ t.to_mongo.opts.should == { :limit => 20, :skip => 0, :fields => ['ref_anixe', 'status', 'ref_customer']}
63
+ end
64
+ end
65
+
66
+ context 'paging' do
67
+ class TestPageDefaultQuery
68
+ include Omnis::MongoQuery
69
+ end
70
+
71
+ it "page default no page number given" do
72
+ t = TestPageDefaultQuery.new({})
73
+ t.to_mongo.opts.should == { :limit => 20, :skip => 0, :fields => []}
74
+ end
75
+ it "page defaults and page given" do
76
+ t = TestPageDefaultQuery.new({"page" => 2})
77
+ t.to_mongo.opts.should == { :limit => 20, :skip => 20, :fields => []}
78
+ end
79
+ end
80
+
81
+ context 'practical use case with predefined params' do
82
+ class TestHotelsWithDepartureTomorrowQuery
83
+ include Omnis::MongoQuery
84
+ def self.tomorrow
85
+ Time.new(2012, 10, 12, 22, 54, 38)
86
+ end
87
+
88
+ param :date_from, Between, :default => Between.new("services.date_from", tomorrow.beginning_of_day..tomorrow.end_of_day)
89
+ param :contract, Matches, :default => "^wotra."
90
+ param :product, Equals, :default => "PACKAGE"
91
+ param :status, Equals, :default => "book_confirmed"
92
+
93
+ page :page, :items_per_page => 9999
94
+ fields %w[ref_anixe ref_customer status passengers date_status_modified description contract agency services]
95
+ end
96
+
97
+ it "should fill params with default" do
98
+ t = TestHotelsWithDepartureTomorrowQuery.new({})
99
+ m = p t.to_mongo
100
+ m.selector[:contract].should == /^wotra./i
101
+ m.selector[:product].should == "PACKAGE"
102
+ m.selector[:status].should == "book_confirmed"
103
+ m.selector['services.date_from'].should == {:'$gte' => Time.new(2012, 10, 12), :'$lt' => Time.local(2012, 10, 12, 23, 59, 59, 999999.999)}
104
+ m.opts[:limit].should == 9999
105
+ m.opts[:skip].should == 0
106
+ m.opts[:fields].should == ["ref_anixe", "ref_customer", "status", "passengers", "date_status_modified", "description", "contract", "agency", "services"]
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+ require 'omnis/nested_hash_extractor'
3
+
4
+ describe Omnis::NestedHashExtractor do
5
+ let(:xtr) { Omnis::NestedHashExtractor.new }
6
+ let(:doc) {
7
+ { "ref_anixe" => "1abc",
8
+ "contract" => "test",
9
+ "agency" => nil,
10
+ "services" => [ { "date_from" => "2012-10-10"}]}
11
+ }
12
+
13
+ it "extracts values using a path" do
14
+ xtr.extractor("ref_anixe").call(doc).should == "1abc"
15
+ xtr.extractor("services.0.date_from").call(doc).should == "2012-10-10"
16
+ xtr.extractor("agency").call(doc).should be_nil
17
+ end
18
+
19
+ it "returns Nothing if an expression points to a non-existant key" do
20
+ xtr.extractor("ref_anixe").call({}).should == Nothing
21
+ end
22
+
23
+ it "returns Nothing for a nested path, if an exception would be raised" do
24
+ xtr.extractor("a.b.c").call({}).should == Nothing
25
+ xtr.extractor("a.(1]").call({}).should == Nothing
26
+ end
27
+
28
+ it "returns nil if the underlying value of a key is nil" do
29
+ xtr.extractor("agency").call(doc).should be_nil
30
+ end
31
+
32
+ end
@@ -0,0 +1,23 @@
1
+ require 'spec_helper'
2
+ require 'omnis/operators'
3
+
4
+ describe Omnis::Operators do
5
+ it 'supports equality' do
6
+ this = Omnis::Operators::NullOperator.new(:key, :value)
7
+ that = Omnis::Operators::NullOperator.new(:key, :value)
8
+ (this == that).should be_true
9
+
10
+ other = Omnis::Operators::NullOperator.new(:other, :value)
11
+ (other == this).should be_false
12
+
13
+ another = Omnis::Operators::NullOperator.new(:key, :another)
14
+ (another == this).should be_false
15
+ end
16
+
17
+ it 'should carry additional options' do
18
+ o = Omnis::Operators::NullOperator.new(:key, :value, {:k => 'v'})
19
+ expect(o.key).to eq(:key)
20
+ expect(o.value).to eq(:value)
21
+ expect(o.opts).to eq({:k => 'v'})
22
+ end
23
+ end
@@ -0,0 +1,65 @@
1
+ require 'spec_helper'
2
+ require 'omnis/operators'
3
+ require 'omnis/query'
4
+ require 'active_support/core_ext/hash'
5
+
6
+ describe Omnis::Query::Param do
7
+ it "supports equality" do
8
+ Omnis::Query::Param.new(:param, Omnis::Operators::Matches).should == Omnis::Query::Param.new(:param, Omnis::Operators::Matches)
9
+ Omnis::Query::Param.new(:param, Omnis::Operators::Between).should_not == Omnis::Query::Param.new(:param, Omnis::Operators::Matches)
10
+ end
11
+ end
12
+
13
+ describe Omnis::Query do
14
+ class TestBookingParams
15
+ include Omnis::Query
16
+
17
+ def self.parse_date(params, name)
18
+ param = params[name]
19
+ return nil if param.nil?
20
+ time = Time.parse(param)
21
+ Between.new(name, time.getlocal.beginning_of_day..time.getlocal.end_of_day)
22
+ end
23
+
24
+ param :ref_anixe, Matches
25
+ param :passenger, Equals
26
+ param(:date, Between) {|params| parse_date(params, :date) }
27
+ end
28
+
29
+ it "allows to fetch a single param" do
30
+ t = TestBookingParams.new({"ref_anixe" => "1abc"})
31
+ t.fetch(:ref_anixe).should == Omnis::Operators::Matches.new(:ref_anixe, "1abc")
32
+ end
33
+
34
+ it "allows to extract all at once" do
35
+ t = TestBookingParams.new({"ref_anixe" => "1abc"})
36
+ t.extract.should == [Omnis::Operators::Matches.new(:ref_anixe, "1abc")]
37
+ end
38
+
39
+ it "allows using blocks for extracing params" do
40
+ t = TestBookingParams.new({"date" => "2012-10-02"})
41
+ value = t.fetch(:date).value
42
+ value.begin.should be_eql Time.local(2012, 10, 02, 0, 0, 0)
43
+ value.end.should == Time.local(2012, 10, 02, 23, 59, 59, 999999.999)
44
+ end
45
+
46
+ it "returns default values even if not in the params" do
47
+ class TestDefaultParams
48
+ include Omnis::Query
49
+ param :contract, Matches, :default => "test"
50
+ end
51
+
52
+ t = TestDefaultParams.new({})
53
+ t.fetch(:contract).value.should == "test"
54
+ end
55
+
56
+ it "should accept a lambda as default" do
57
+ class TestDefaultsWithLambdaParams
58
+ include Omnis::Query
59
+ param :contract, Equals, :default => -> { :angry_nerds }
60
+ end
61
+
62
+ t = TestDefaultsWithLambdaParams.new({})
63
+ t.fetch(:contract).value.should == :angry_nerds
64
+ end
65
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+ require 'ostruct'
3
+ require 'monadic'
4
+ require 'omnis/nested_hash_extractor'
5
+ require 'omnis/transformer'
6
+
7
+ describe Omnis::Transformer do
8
+ class TestTransformer
9
+ include Omnis::Transformer
10
+ extractor Omnis::NestedHashExtractor.new
11
+
12
+ property :ref, "ref_anixe"
13
+ property(:date_from) {|source| Maybe(extract(source, "services.0.date_from")).or(Time.at(0)).fetch }
14
+ property :date_to, "services.0.date_to", :default => Time.at(0), :format => ->v { v.strftime("%Y-%m-%d") }
15
+ property :agency, "agency", :default => "000000"
16
+ end
17
+
18
+ let(:doc) {
19
+ { "ref_anixe" => "1abc",
20
+ "contract" => "test",
21
+ "agency" => nil,
22
+ "services" => [ { "date_from" => "2012-10-10"}]}
23
+ }
24
+
25
+ it "should read values from the doc" do
26
+ t = TestTransformer.new
27
+ t.transform(doc).should == { :ref => "1abc", :date_from => "2012-10-10", :date_to => "1970-01-01", :agency => "000000" }
28
+ end
29
+
30
+ it "should support blocks in properties, without a defined extractor" do
31
+ class TestTransformerWithBlock
32
+ include Omnis::Transformer
33
+ property(:ref) {|src| Maybe(src)["ref_anixe"].fetch }
34
+ end
35
+
36
+ t = TestTransformerWithBlock.new
37
+ t.transform(doc).should == { :ref => "1abc" }
38
+ end
39
+
40
+ it "should raise an ArgumentError if no block and no expression provided" do
41
+ expect {
42
+ class TestTransformerInvalid
43
+ include Omnis::Transformer
44
+ property :ref
45
+ end
46
+ }.to raise_error ArgumentError
47
+ end
48
+
49
+ it "uses a #to_object method if provided to convert the resulting Hash into an Object" do
50
+ class TestTransformerWithToObject
51
+ include Omnis::Transformer
52
+ property(:ref) {|src| src["ref_anixe"]}
53
+ def to_object(hash)
54
+ OpenStruct.new(hash)
55
+ end
56
+ end
57
+ t = TestTransformerWithToObject.new
58
+ t.transform(doc).should == OpenStruct.new(ref: "1abc")
59
+ end
60
+
61
+ it 'provides a tranformer lambda' do
62
+ class TestXformer
63
+ include Omnis::Transformer
64
+ property(:ref) {|src| src['ref_anixe']}
65
+ end
66
+ t = TestXformer.new
67
+ xformer = t.to_proc
68
+ xformer.should be_a Proc
69
+ xformer.({"ref_anixe" => "2two"}).should == {:ref => "2two"}
70
+ end
71
+ end
@@ -7,8 +7,8 @@ SPEC_SUITES = [
7
7
 
8
8
  { :id => :acceptance,
9
9
  :title => 'acceptance',
10
- :files => %w(spec_acceptance/acceptance/**/*_spec.rb),
11
- :opts => '-I spec_acceptance' },
10
+ :files => %w(spec/acceptance/**/*_spec.rb),
11
+ :opts => '-I spec/acceptance' },
12
12
  ]
13
13
 
14
14
 
@@ -1,3 +1,5 @@
1
1
  require 'bundler/setup'
2
2
  Bundler.require :test
3
+
4
+ ENV['TZ'] = "CET" # make sure all tests are in the same timezone
3
5
  $LOAD_PATH << File.expand_path('../../lib', __FILE__)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: omnis
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 0.1.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,8 +9,72 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-10-04 00:00:00.000000000 Z
12
+ date: 2012-10-11 00:00:00.000000000 Z
13
13
  dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '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: '0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: bson_ext
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ! '>='
36
+ - !ruby/object:Gem::Version
37
+ version: 1.7.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: 1.7.0
46
+ - !ruby/object:Gem::Dependency
47
+ name: monadic
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ! '>='
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ type: :runtime
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ! '>='
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: mongo
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 1.7.0
70
+ type: :runtime
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ! '>='
76
+ - !ruby/object:Gem::Version
77
+ version: 1.7.0
14
78
  - !ruby/object:Gem::Dependency
15
79
  name: rspec
16
80
  requirement: !ruby/object:Gem::Requirement
@@ -92,7 +156,7 @@ dependencies:
92
156
  - !ruby/object:Gem::Version
93
157
  version: '0'
94
158
  - !ruby/object:Gem::Dependency
95
- name: activesupport
159
+ name: rake
96
160
  requirement: !ruby/object:Gem::Requirement
97
161
  none: false
98
162
  requirements:
@@ -108,22 +172,22 @@ dependencies:
108
172
  - !ruby/object:Gem::Version
109
173
  version: '0'
110
174
  - !ruby/object:Gem::Dependency
111
- name: rake
175
+ name: rb-fsevent
112
176
  requirement: !ruby/object:Gem::Requirement
113
177
  none: false
114
178
  requirements:
115
- - - ! '>='
179
+ - - ~>
116
180
  - !ruby/object:Gem::Version
117
- version: '0'
181
+ version: 0.9.1
118
182
  type: :development
119
183
  prerelease: false
120
184
  version_requirements: !ruby/object:Gem::Requirement
121
185
  none: false
122
186
  requirements:
123
- - - ! '>='
187
+ - - ~>
124
188
  - !ruby/object:Gem::Version
125
- version: '0'
126
- description: Helps a read-only ORM kind-of
189
+ version: 0.9.1
190
+ description: Helps with a read-only ORM kind-of, more useful than the description
127
191
  email:
128
192
  - pz@anixe.pl
129
193
  executables: []
@@ -132,13 +196,24 @@ extra_rdoc_files: []
132
196
  files:
133
197
  - .gitignore
134
198
  - Gemfile
199
+ - Guardfile
135
200
  - LICENSE.txt
136
201
  - README.md
137
202
  - Rakefile
138
203
  - lib/omnis.rb
204
+ - lib/omnis/mongo_query.rb
205
+ - lib/omnis/mongo_report.rb
206
+ - lib/omnis/nested_hash_extractor.rb
207
+ - lib/omnis/operators.rb
208
+ - lib/omnis/query.rb
209
+ - lib/omnis/transformer.rb
139
210
  - lib/omnis/version.rb
140
211
  - omnis.gemspec
141
- - readme.rb
212
+ - spec/lib/omnis/mongo_query_spec.rb
213
+ - spec/lib/omnis/nested_hash_extractor_spec.rb
214
+ - spec/lib/omnis/operators_spec.rb
215
+ - spec/lib/omnis/query_spec.rb
216
+ - spec/lib/omnis/transformer_spec.rb
142
217
  - spec/spec.rake
143
218
  - spec/spec_helper.rb
144
219
  homepage: http://github.com/pzol/omnis
@@ -153,12 +228,18 @@ required_ruby_version: !ruby/object:Gem::Requirement
153
228
  - - ! '>='
154
229
  - !ruby/object:Gem::Version
155
230
  version: '0'
231
+ segments:
232
+ - 0
233
+ hash: -3696760382807505073
156
234
  required_rubygems_version: !ruby/object:Gem::Requirement
157
235
  none: false
158
236
  requirements:
159
237
  - - ! '>='
160
238
  - !ruby/object:Gem::Version
161
239
  version: '0'
240
+ segments:
241
+ - 0
242
+ hash: -3696760382807505073
162
243
  requirements: []
163
244
  rubyforge_project:
164
245
  rubygems_version: 1.8.23
@@ -166,5 +247,10 @@ signing_key:
166
247
  specification_version: 3
167
248
  summary: see above
168
249
  test_files:
250
+ - spec/lib/omnis/mongo_query_spec.rb
251
+ - spec/lib/omnis/nested_hash_extractor_spec.rb
252
+ - spec/lib/omnis/operators_spec.rb
253
+ - spec/lib/omnis/query_spec.rb
254
+ - spec/lib/omnis/transformer_spec.rb
169
255
  - spec/spec.rake
170
256
  - spec/spec_helper.rb
data/readme.rb DELETED
@@ -1,49 +0,0 @@
1
- class BookingQuery
2
- include Omnis::MongoQuery
3
-
4
- collection Mongo::Connection.new['bms']['bookings']
5
-
6
- def parse_date(value)
7
- case result = Time.parse(value) rescue Chronic.parse(value, :guess => false)
8
- when Time; Between.new(m, result.getlocal.beginning_of_day..result.getlocal.end_of_day)
9
- when Chronic::Span; Between.new(m, result)
10
- else nil
11
- end
12
- end
13
-
14
- param :contract, Matches
15
- param :date_from {|value| parse_date(value) }
16
- param :date_to {|value| parse_date(value) }
17
-
18
- fields %w[ref_anixe ref_customer status passengers date_status_modified date_from date_to description product contract agency services]
19
- end
20
-
21
- class BookingTransformer
22
- include Omnis::DataTransformer
23
-
24
- def extract_passenger(doc)
25
- ->doc { Maybe(doc)['passengers'].map {|v| v.first.values.slice(1..2).join(' ') }.or('Unknown').fetch.to_s }
26
- end
27
-
28
- property :ref_anixe, "ref_anixe"
29
- property :ref_customer, "ref_customer"
30
- property :status, "status"
31
- property :passenger, extract_passenger(doc)
32
- property :date "date_status_modified", :default => Time.at(0), :format => ->v { v.to_s(:date) }
33
- property :description, "description"
34
- property :product, "product"
35
- property :contract, "contract"
36
- property :agency, "agency"
37
- property :date_from, "services.0.date_from", :default => "n/a", :format => ->v { v.to_s(:date) }
38
- property :date_to, "services.0.date_to", :default => "n/a", :format => ->v { v.to_s(:date) }
39
-
40
- def to_object(doc)
41
- OpenStruct.new(doc)
42
- end
43
- end
44
-
45
- present BookingQuery.new(params), Foo.new(NestedHashExtractor.new)
46
-
47
- def present(query, transformer)
48
- query.call(transformer)
49
- end