flex-scopes 1.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.
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012-2013 by Domizio Demichelis
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # flex-scopes
2
+
3
+ Provides an easy to use ruby API to search elasticsearch with ActiveRecord-like chainable and mergeables scopes.
4
+
5
+
6
+ ## Links
7
+
8
+ * [Flex Repository](https://github.com/ddnexus/flex)
9
+ * [Flex Project (Global Documentation)](http://ddnexus.github.io/flex/doc/)
10
+ * [flex-scopes Gem (Specific Documentation)](http://ddnexus.github.io/flex/doc/3-flex-scopes)
11
+ * [Issues](https://github.com/ddnexus/flex-scopes/issues)
12
+ * [Pull Requests](https://github.com/ddnexus/flex-scopes/pulls)
13
+
14
+ ## Branches
15
+
16
+ The master branch reflects the last published gem. Then you may find a next-version branch (named after the version string), with the commits that will be merged in master just before publishing the next gem version. The next-version branch may get rebased or force pushed.
17
+
18
+ ## Credits
19
+
20
+ Special thanks for their sponsorship to [Escalate Media](http://www.escalatemedia.com) and [Barquin International](http://www.barquin.com).
21
+
22
+ ## Copyright
23
+
24
+ Copyright (c) 2012-2013 by [Domizio Demichelis](mailto://dd.nexus@gmail.com)<br>
25
+ See [LICENSE](https://github.com/ddnexus/flex-scopes/blob/master/LICENSE) for details.
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 1.0.1
@@ -0,0 +1,19 @@
1
+ require 'date'
2
+ version = File.read(File.expand_path('../VERSION', __FILE__)).strip
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'flex-scopes'
6
+ s.summary = 'ActiveRecord-like chainable scopes and finders for Flex.'
7
+ s.description = 'Provides an easy to use ruby API to search elasticsearch with ActiveRecord-like chainable and mergeables scopes.'
8
+ s.homepage = 'http://github.com/ddnexus/flex-scopes'
9
+ s.authors = ["Domizio Demichelis"]
10
+ s.email = 'dd.nexus@gmail.com'
11
+ s.extra_rdoc_files = %w[README.md]
12
+ s.files = `git ls-files -z`.split("\0")
13
+ s.version = version
14
+ s.date = Date.today.to_s
15
+ s.required_rubygems_version = ">= 1.3.6"
16
+ s.rdoc_options = %w[--charset=UTF-8]
17
+
18
+ s.add_runtime_dependency 'flex', version
19
+ end
@@ -0,0 +1,12 @@
1
+ require 'flex'
2
+ require 'flex/scope/utils'
3
+ require 'flex/scope/filter_methods'
4
+ require 'flex/scope/vars_methods'
5
+ require 'flex/scope/query_methods'
6
+ require 'flex/scope'
7
+ require 'flex/scopes'
8
+ require 'flex/result/scope'
9
+
10
+ Flex::LIB_PATHS << File.dirname(__FILE__)
11
+
12
+ Flex::Conf.result_extenders |= [Flex::Result::Scope]
@@ -0,0 +1,12 @@
1
+ module Flex
2
+ class Result
3
+ module Scope
4
+
5
+ def get_docs
6
+ return self if variables[:raw_result]
7
+ respond_to?(:collection) ? collection : self
8
+ end
9
+
10
+ end
11
+ end
12
+ end
data/lib/flex/scope.rb ADDED
@@ -0,0 +1,42 @@
1
+ module Flex
2
+ # never instantiate this class directly: it is automatically done by the scoped method
3
+ class Scope < Vars
4
+
5
+ class Error < StandardError; end
6
+
7
+ include FilterMethods
8
+ include VarsMethods
9
+ include QueryMethods
10
+
11
+ SCOPED_METHODS = FilterMethods.instance_methods + VarsMethods.instance_methods + QueryMethods.instance_methods
12
+
13
+ def inspect
14
+ "#<#{self.class.name} #{self}>"
15
+ end
16
+
17
+ def respond_to?(meth, private=false)
18
+ super || is_template?(meth) || is_scope?(meth)
19
+ end
20
+
21
+ def method_missing(meth, *args, &block)
22
+ super unless respond_to?(meth)
23
+ case
24
+ when is_scope?(meth)
25
+ deep_merge self[:context].send(meth, *args, &block)
26
+ when is_template?(meth)
27
+ self[:context].send(meth, deep_merge(*args), &block)
28
+ end
29
+ end
30
+
31
+ private
32
+
33
+ def is_template?(name)
34
+ self[:context].respond_to?(:template_methods) && self[:context].template_methods.include?(name.to_sym)
35
+ end
36
+
37
+ def is_scope?(name)
38
+ self[:context].scope_methods.include?(name.to_sym)
39
+ end
40
+
41
+ end
42
+ end
@@ -0,0 +1,81 @@
1
+ module Flex
2
+ class Scope
3
+ module FilterMethods
4
+
5
+ include Scope::Utils
6
+
7
+ # accepts also :any_term => nil for missing values
8
+ def terms(value)
9
+ terms, missing_list = {}, []
10
+ value.each { |f, v| v.nil? ? missing_list.push({ :missing => f }) : (terms[f] = v) }
11
+ terms, term = terms.partition{|k,v| v.is_a?(Array)}
12
+ term_list = []
13
+ term.each do |term, value|
14
+ term_list.push(:term => {term => value})
15
+ end
16
+ deep_merge boolean_wrapper( :terms_list => Hash[terms],
17
+ :term_list => term_list,
18
+ :_missing_list => missing_list )
19
+ end
20
+
21
+ # accepts one or an array or a list of filter structures
22
+ def filters(*value)
23
+ deep_merge boolean_wrapper( :filters => array_value(value) )
24
+ end
25
+
26
+ def missing(*fields)
27
+ missing_list = []
28
+ for field in fields
29
+ missing_list.push(:missing => field)
30
+ end
31
+ deep_merge :_missing_list => missing_list
32
+ end
33
+
34
+ # accepts a single key hash or a multiple keys hash, that will be translated in a array of single key hashes
35
+ def term(term_or_terms_hash)
36
+ term_list = []
37
+ term_or_terms_hash.each do |term, value|
38
+ term_list.push(:term => {term => value})
39
+ end
40
+ deep_merge boolean_wrapper(:term_list => term_list)
41
+ end
42
+
43
+ # accepts one hash of ranges documented at
44
+ # http://www.elasticsearch.org/guide/reference/query-dsl/range-filter/
45
+ def range(value)
46
+ deep_merge boolean_wrapper(:range => value)
47
+ end
48
+
49
+
50
+ %w[and or].each do |m|
51
+ class_eval <<-ruby, __FILE__, __LINE__
52
+ def #{m}(&block)
53
+ vars = {:_#{m} => Hash[Flex::Scope.new.instance_eval(&block).to_a]}
54
+ vars.merge!(:_boolean_wrapper => :_#{m}) if context_scope?
55
+ deep_merge vars
56
+ end
57
+ ruby
58
+ end
59
+
60
+ private
61
+
62
+ def context_scope?
63
+ has_key?(:context)
64
+ end
65
+
66
+ def boolean_wrapper(value)
67
+ if context_scope?
68
+ if has_key?(:_boolean_wrapper) && self[:_boolean_wrapper] != :_and
69
+ current_wrapper = {self[:_boolean_wrapper] => delete(self[:_boolean_wrapper])}
70
+ self.and{ current_wrapper }.and{ value }
71
+ else
72
+ self.and{value}
73
+ end
74
+ else
75
+ value
76
+ end
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,52 @@
1
+ ANCHORS:
2
+ - &filters
3
+ - <<filters= ~ >>
4
+ - <<term_list= ~ >>
5
+ - terms: <<terms_list= ~ >>
6
+ - <<_missing_list= ~ >>
7
+ - <<_and= ~ >>
8
+ - <<_or= ~ >>
9
+ - range: <<range= ~ >>
10
+
11
+ - &scope
12
+ query:
13
+ query_string:
14
+ "<<cleanable_query= {query: '*'} >>"
15
+ script_fields:
16
+ <<script_fields= ~ >>
17
+ sort: <<sort= ~ >>
18
+ facets: <<facets= ~ >>
19
+ highlight: <<highlight= ~ >>
20
+ filter: <<_boolean_wrapper= :_and >>
21
+
22
+ _and:
23
+ and: *filters
24
+
25
+ _or:
26
+ or: *filters
27
+
28
+ _missing_fields:
29
+ missing:
30
+ field: <<missing>>
31
+
32
+ get:
33
+ - GET
34
+ - /<<index>>/<<type>>/_search
35
+ - *scope
36
+
37
+
38
+
39
+ delete:
40
+ - DELETE
41
+ - /<<index>>/<<type>>/_query
42
+ - filtered:
43
+ <<: *scope
44
+
45
+
46
+
47
+ ids:
48
+ - GET
49
+ - /<<index>>/<<type>>/_search
50
+ - query:
51
+ terms:
52
+ _id: <<ids>>
@@ -0,0 +1,89 @@
1
+ module Flex
2
+ class Scope
3
+
4
+ module Query
5
+
6
+ include Templates
7
+ flex.load_source File.expand_path('../queries.yml', __FILE__)
8
+
9
+ end
10
+
11
+ module QueryMethods
12
+
13
+ # MyModel.find(ids, *vars)
14
+ # - ids can be a single id or an array of ids
15
+ #
16
+ # MyModel.find '1Momf4s0QViv-yc7wjaDCA'
17
+ # #=> #<MyModel ... color: "red", size: "small">
18
+ #
19
+ # MyModel.find ['1Momf4s0QViv-yc7wjaDCA', 'BFdIETdNQv-CuCxG_y2r8g']
20
+ # #=> [#<MyModel ... color: "red", size: "small">, #<MyModel ... color: "bue", size: "small">]
21
+ #
22
+ def find(ids, *vars)
23
+ raise ArgumentError, "Empty argument passed (got #{ids.inspect})" \
24
+ if ids.nil? || ids.respond_to?(:empty?) && ids.empty?
25
+ wrapped = ids.is_a?(::Array) ? ids : [ids]
26
+ result = Query.ids self, *vars, :ids => wrapped
27
+ docs = result.get_docs
28
+ ids.is_a?(::Array) ? docs : docs.first
29
+ end
30
+
31
+ # it limits the size of the query to the first document and returns it as a single document object
32
+ def first(*vars)
33
+ result = Query.get params(:size => 1), *vars
34
+ docs = result.get_docs
35
+ docs.is_a?(Array) ? docs.first : docs
36
+ end
37
+
38
+ # it limits the size of the query to the last document and returns it as a single document object
39
+ def last(*vars)
40
+ result = Query.get params(:from => count-1, :size => 1), *vars
41
+ docs = result.get_docs
42
+ docs.is_a?(Array) ? docs.first : docs
43
+ end
44
+
45
+ # will retrieve all documents, the results will be limited by the default :size param
46
+ # use #scan_all if you want to really retrieve all documents (in batches)
47
+ def all(*vars)
48
+ result = Query.get self, *vars
49
+ result.get_docs
50
+ end
51
+
52
+ def each(*vars, &block)
53
+ all(*vars).each &block
54
+ end
55
+
56
+ # scan_search: the block will be yielded many times with an array of batched results.
57
+ # You can pass :scroll and :size as params in order to control the action.
58
+ # See http://www.elasticsearch.org/guide/reference/api/search/scroll.html
59
+ def scan_all(*vars, &block)
60
+ Query.flex.scan_search(:get, self, *vars) do |result|
61
+ block.call result.get_docs
62
+ end
63
+ end
64
+ alias_method :each_batch, :scan_all
65
+ alias_method :find_in_batches, :scan_all
66
+
67
+ def delete(*vars)
68
+ Query.delete self, *vars
69
+ end
70
+
71
+ # performs a count search on the scope
72
+ # you can pass a template name as the first arg and
73
+ # it will be used to compute the count. For example:
74
+ # SearchClass.scoped.count(:search_template, vars)
75
+ #
76
+ def count(*vars)
77
+ result = if vars.first.is_a?(Symbol)
78
+ template = vars.shift
79
+ # preserves an eventual wrapper by calling the template method
80
+ self[:context].send(template, params(:search_type => 'count'), *vars)
81
+ else
82
+ Query.flex.count_search(:get, self, *vars)
83
+ end
84
+ result['hits']['total']
85
+ end
86
+
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,13 @@
1
+ module Flex
2
+ class Scope < Vars
3
+ module Utils
4
+
5
+ private
6
+
7
+ def array_value(value)
8
+ (value.first.is_a?(::Array) && value.size == 1) ? value.first : value
9
+ end
10
+
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,81 @@
1
+ module Flex
2
+ class Scope
3
+ module VarsMethods
4
+
5
+ include Scope::Utils
6
+
7
+ def query_string(q)
8
+ hash = q.is_a?(Hash) ? q : {:query => q}
9
+ deep_merge :cleanable_query => hash
10
+ end
11
+ alias_method :query, :query_string
12
+
13
+ # accepts one or an array or a list of sort structures documented at
14
+ # http://www.elasticsearch.org/guide/reference/api/search/sort.html
15
+ # doesn't probably support the multiple hash form, but you can pass an hash as single argument
16
+ # or an array or list of hashes
17
+ def sort(*value)
18
+ deep_merge :sort => array_value(value)
19
+ end
20
+
21
+ # the fields that you want to retrieve (limiting the size of the response)
22
+ # the returned records will be frozen (for Flex::ActiveModel objects), and the missing fields will be nil
23
+ # pass an array eg fields.([:field_one, :field_two]) or a list of fields e.g. fields(:field_one, :field_two)
24
+ def fields(*value)
25
+ deep_merge :params => {:fields => array_value(value)}
26
+ end
27
+
28
+ # limits the size of the retrieved hits
29
+ def size(value)
30
+ deep_merge :params => {:size => value}
31
+ end
32
+
33
+ # sets the :from param so it will return the nth page of size :size
34
+ def page(value)
35
+ deep_merge :page => value || 1
36
+ end
37
+
38
+ # the standard :params variable
39
+ def params(value)
40
+ deep_merge :params => value
41
+ end
42
+
43
+ # meaningful alias of deep_merge
44
+ def variables(*variables)
45
+ deep_merge *variables
46
+ end
47
+
48
+ def index(val)
49
+ deep_merge :index => val
50
+ end
51
+
52
+ def type(val)
53
+ deep_merge :type => val
54
+ end
55
+
56
+ # script_fields(:my_field => 'script ...', # simpler form
57
+ # :my_other_field => {:script => 'script ...', ...}) # ES API
58
+ def script_fields(hash)
59
+ hash.keys.each do |k|
60
+ v = hash[k]
61
+ hash[k] = {:script => v} unless v.is_a?(Hash)
62
+ hash[k][:script].gsub!(/\n+\s*/,' ')
63
+ end
64
+ deep_merge :script_fields => hash
65
+ end
66
+
67
+ def facets(hash)
68
+ deep_merge :facets => hash
69
+ end
70
+
71
+ def highlight(hash)
72
+ deep_merge :highlight => hash
73
+ end
74
+
75
+ def metrics
76
+ deep_merge :params => {:search_type => 'count'}
77
+ end
78
+
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,89 @@
1
+ module Flex
2
+ module Scopes
3
+
4
+ def self.included(context)
5
+ context.class_eval do
6
+ @flex ||= ClassProxy::Base.new(context)
7
+ def self.flex; @flex end
8
+
9
+ extend ClassMethods
10
+
11
+ @scope_methods = []
12
+ def self.scope_methods; @scope_methods end
13
+ end
14
+ end
15
+
16
+ module ClassMethods
17
+
18
+ # Scope methods. They returns a Scope object similar to AR.
19
+ # You can chain scopes, then you can call :count, :first, :all and :scan_all to get your result
20
+ # See Flex::Scope
21
+ #
22
+ # scoped = MyModel.terms(:field_one => 'something', :field_two => nil)
23
+ # .sort(:field_three => :desc)
24
+ # .filters(:range => {:created_at => {:from => 2.days.ago, :to => Time.now})
25
+ # .fields('field_one,field_two,field_three') # or [:field_one, :field_two, ...]
26
+ # .params(:any => 'param')
27
+ #
28
+ # # add another filter or other terms at any time
29
+ # scoped2 = scoped.terms(...).filters(...)
30
+ #
31
+ # scoped2.count
32
+ # scoped2.first
33
+ # scoped2.all
34
+ # scoped2.scan_all {|batch| do_something_with_results batch}
35
+ #
36
+ Utils.define_delegation :to => :scoped,
37
+ :in => self,
38
+ :by => :module_eval,
39
+ :for => Scope::SCOPED_METHODS
40
+
41
+
42
+ # You can start with a non restricted Flex::Scope object
43
+ def scoped
44
+ @scoped ||= Scope[:context => flex.context]
45
+ end
46
+
47
+
48
+ # define scopes as class methods
49
+ #
50
+ # class MyModel
51
+ # include Flex::StoredModel
52
+ # ...
53
+ # scope :red, terms(:color => 'red').sort(:supplier => :asc)
54
+ # scope :size do |size|
55
+ # terms(:size => size)
56
+ # end
57
+ #
58
+ # MyModel.size('large').first
59
+ # MyModel.red.all
60
+ # MyModel.size('small').red.all
61
+ #
62
+ def scope(name, scope=nil, &block)
63
+ raise ArgumentError, "Dangerous scope name: a :#{name} method is already defined. Please, use another one." \
64
+ if respond_to?(name)
65
+ proc = case
66
+ when block_given?
67
+ block
68
+ when scope.is_a?(Flex::Scope)
69
+ lambda {scope}
70
+ when scope.is_a?(Proc)
71
+ scope
72
+ else
73
+ raise ArgumentError, "Scope object or Proc expected (got #{scope.inspect})"
74
+ end
75
+ metaclass = class << self; self end
76
+ metaclass.send(:define_method, name) do |*args|
77
+ scope = proc.call(*args)
78
+ raise Scope::Error, "The scope :#{name} does not return a Flex::Scope object (got #{scope.inspect})" \
79
+ unless scope.is_a?(Flex::Scope)
80
+ scope
81
+ end
82
+ scope_methods << name
83
+ end
84
+
85
+ end
86
+
87
+ end
88
+ end
89
+
metadata ADDED
@@ -0,0 +1,76 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: flex-scopes
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Domizio Demichelis
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-08-12 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: flex
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - '='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.0.1
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: 1.0.1
30
+ description: Provides an easy to use ruby API to search elasticsearch with ActiveRecord-like
31
+ chainable and mergeables scopes.
32
+ email: dd.nexus@gmail.com
33
+ executables: []
34
+ extensions: []
35
+ extra_rdoc_files:
36
+ - README.md
37
+ files:
38
+ - LICENSE
39
+ - README.md
40
+ - VERSION
41
+ - flex-scopes.gemspec
42
+ - lib/flex-scopes.rb
43
+ - lib/flex/result/scope.rb
44
+ - lib/flex/scope.rb
45
+ - lib/flex/scope/filter_methods.rb
46
+ - lib/flex/scope/queries.yml
47
+ - lib/flex/scope/query_methods.rb
48
+ - lib/flex/scope/utils.rb
49
+ - lib/flex/scope/vars_methods.rb
50
+ - lib/flex/scopes.rb
51
+ homepage: http://github.com/ddnexus/flex-scopes
52
+ licenses: []
53
+ post_install_message:
54
+ rdoc_options:
55
+ - --charset=UTF-8
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ! '>='
62
+ - !ruby/object:Gem::Version
63
+ version: '0'
64
+ required_rubygems_version: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ! '>='
68
+ - !ruby/object:Gem::Version
69
+ version: 1.3.6
70
+ requirements: []
71
+ rubyforge_project:
72
+ rubygems_version: 1.8.25
73
+ signing_key:
74
+ specification_version: 3
75
+ summary: ActiveRecord-like chainable scopes and finders for Flex.
76
+ test_files: []