flex 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.
Files changed (56) hide show
  1. data/LICENSE +20 -0
  2. data/README.md +20 -0
  3. data/VERSION +1 -0
  4. data/flex.gemspec +43 -0
  5. data/lib/flex.rb +418 -0
  6. data/lib/flex/api_methods.yml +108 -0
  7. data/lib/flex/class_proxy.rb +12 -0
  8. data/lib/flex/configuration.rb +57 -0
  9. data/lib/flex/errors.rb +42 -0
  10. data/lib/flex/http_clients/patron.rb +27 -0
  11. data/lib/flex/http_clients/rest_client.rb +38 -0
  12. data/lib/flex/loader.rb +116 -0
  13. data/lib/flex/logger.rb +16 -0
  14. data/lib/flex/model.rb +24 -0
  15. data/lib/flex/model/class_proxy.rb +45 -0
  16. data/lib/flex/model/instance_proxy.rb +101 -0
  17. data/lib/flex/model/manager.rb +67 -0
  18. data/lib/flex/rails.rb +12 -0
  19. data/lib/flex/rails/engine.rb +23 -0
  20. data/lib/flex/rails/helper.rb +16 -0
  21. data/lib/flex/related_model.rb +16 -0
  22. data/lib/flex/related_model/class_proxy.rb +23 -0
  23. data/lib/flex/related_model/class_sync.rb +23 -0
  24. data/lib/flex/related_model/instance_proxy.rb +28 -0
  25. data/lib/flex/result.rb +18 -0
  26. data/lib/flex/result/bulk.rb +20 -0
  27. data/lib/flex/result/collection.rb +51 -0
  28. data/lib/flex/result/document.rb +38 -0
  29. data/lib/flex/result/indifferent_access.rb +11 -0
  30. data/lib/flex/result/search.rb +51 -0
  31. data/lib/flex/result/source_document.rb +63 -0
  32. data/lib/flex/result/source_search.rb +32 -0
  33. data/lib/flex/structure/indifferent_access.rb +44 -0
  34. data/lib/flex/structure/mergeable.rb +21 -0
  35. data/lib/flex/tasks.rb +141 -0
  36. data/lib/flex/template.rb +187 -0
  37. data/lib/flex/template/base.rb +29 -0
  38. data/lib/flex/template/info.rb +50 -0
  39. data/lib/flex/template/partial.rb +31 -0
  40. data/lib/flex/template/search.rb +30 -0
  41. data/lib/flex/template/slim_search.rb +13 -0
  42. data/lib/flex/template/tags.rb +46 -0
  43. data/lib/flex/utility_methods.rb +140 -0
  44. data/lib/flex/utils.rb +59 -0
  45. data/lib/flex/variables.rb +11 -0
  46. data/lib/generators/flex/setup/setup_generator.rb +51 -0
  47. data/lib/generators/flex/setup/templates/flex_config.yml +16 -0
  48. data/lib/generators/flex/setup/templates/flex_dir/es.rb.erb +18 -0
  49. data/lib/generators/flex/setup/templates/flex_dir/es.yml.erb +19 -0
  50. data/lib/generators/flex/setup/templates/flex_dir/es_extender.rb.erb +17 -0
  51. data/lib/generators/flex/setup/templates/flex_initializer.rb.erb +44 -0
  52. data/lib/tasks/index.rake +23 -0
  53. data/test/flex.irt +143 -0
  54. data/test/flex/configuration.irt +53 -0
  55. data/test/irt_helper.rb +12 -0
  56. metadata +211 -0
@@ -0,0 +1,45 @@
1
+ module Flex
2
+ module Model
3
+ class ClassProxy < Flex::ClassProxy
4
+
5
+ include RelatedModel::ClassSync
6
+
7
+ attr_reader :parent_association, :parent_child_map
8
+
9
+ def initialize(base)
10
+ super
11
+ variables.add :index => Configuration.variables[:index],
12
+ :type => Manager.class_name_to_type(host_class.name)
13
+ end
14
+
15
+ def index
16
+ variables[:index]
17
+ end
18
+
19
+ def index=(val)
20
+ variables[:index] = val
21
+ end
22
+
23
+ def type
24
+ variables[:type]
25
+ end
26
+
27
+ def type=(val)
28
+ variables[:type] = val
29
+ end
30
+
31
+ def parent(parent_association, map)
32
+ @parent_association = parent_association
33
+ Manager.parent_types |= map.keys.map(&:to_s)
34
+ self.type = map.values.map(&:to_s)
35
+ @parent_child_map = map
36
+ @is_child = true
37
+ end
38
+
39
+ def is_child?
40
+ !!@is_child
41
+ end
42
+
43
+ end
44
+ end
45
+ end
@@ -0,0 +1,101 @@
1
+ module Flex
2
+ module Model
3
+ class InstanceProxy < RelatedModel::InstanceProxy
4
+
5
+ # indexes the document
6
+ # usually called from after_save, you can eventually call it explicitly for example from another callback
7
+ # or whenever the DB doesn't get updated by the model
8
+ # you can also pass the :data=>flex_source explicitly (useful for example to override the flex_source in the model)
9
+ def store(vars={})
10
+ if instance.flex_indexable?
11
+ Flex.store metainfo.merge(:data => instance.flex_source).merge(vars)
12
+ else
13
+ Flex.remove(metainfo.merge(vars)) if Flex.get(metainfo.merge(vars.merge(:raise => false)))
14
+ end
15
+ end
16
+
17
+ # removes the document from the index (called from after_destroy)
18
+ def remove(vars={})
19
+ return unless instance.flex_indexable?
20
+ Flex.remove metainfo.merge(vars)
21
+ end
22
+
23
+ # gets the document from ES
24
+ def get(vars={})
25
+ return unless instance.flex_indexable?
26
+ Flex.get metainfo.merge(vars)
27
+ end
28
+
29
+ def parent_instance(raise=true)
30
+ return unless is_child?
31
+ @parent_instance ||= instance.send(class_flex.parent_association) || raise && raise(MissingParentError, "missing parent instance for document #{instance.inspect}.")
32
+ end
33
+
34
+ # helper that iterates through the parent record chain
35
+ # record.flex.each_parent{|p| p.do_smething }
36
+ def each_parent
37
+ pi = parent_instance
38
+ while pi do
39
+ yield pi
40
+ pi = pi.flex.parent_instance
41
+ end
42
+ end
43
+
44
+ def type
45
+ @type ||= is_child? ? class_flex.parent_child_map[parent_instance.flex.type] : class_flex.type
46
+ end
47
+
48
+ def index
49
+ class_flex.index
50
+ end
51
+
52
+ def id
53
+ instance.id
54
+ end
55
+
56
+ def routing(raise=true)
57
+ @routing ||= case
58
+ when is_child? then parent_instance(raise).flex.routing
59
+ when is_parent? then create_routing
60
+ end
61
+ end
62
+
63
+ def is_child?
64
+ @is_child ||= class_flex.is_child?
65
+ end
66
+
67
+ def is_parent?
68
+ @is_parent ||= Manager.parent_types.include?(type)
69
+ end
70
+
71
+ def metainfo
72
+ @metainfo ||= begin
73
+ meta = { :index => index, :type => type, :id => id }
74
+ params = {}
75
+ params[:routing] = routing if routing
76
+ params[:parent] = parent_instance.id if is_child?
77
+ meta.merge!(:params => params) unless params.empty?
78
+ meta
79
+ end
80
+ end
81
+
82
+ private
83
+
84
+ BASE62_DIGITS = %w(0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J K L M N O P Q R S T U V W X Y Z a b c d e f g h i j k l m n o p q r s t u v w x y z)
85
+
86
+ def create_routing
87
+ string = [index, type, id].join
88
+ remainder = Digest::MD5.hexdigest(string).to_i(16)
89
+ result = []
90
+ max_power = ( Math.log(remainder) / Math.log(62) ).floor
91
+ max_power.downto(0) do |power|
92
+ digit, remainder = remainder.divmod(62**power)
93
+ result << digit
94
+ end
95
+ result << remainder if remainder > 0
96
+ result.map{|digit| BASE62_DIGITS[digit]}.join
97
+ end
98
+
99
+ end
100
+ end
101
+ end
@@ -0,0 +1,67 @@
1
+ module Flex
2
+ module Model
3
+ module Manager
4
+
5
+ extend self
6
+
7
+ attr_accessor :parent_types
8
+ @parent_types = []
9
+
10
+ def init
11
+ Configuration.flex_models.each {|m| eval"::#{m}" if m.is_a?(String) }
12
+ end
13
+
14
+ # arrays of all the types
15
+ def types
16
+ type_class_map.keys.map{|k| k.split('/').last}
17
+ end
18
+
19
+ # sets the default parent/child mappings and merges with the config_file
20
+ # returns the indices structure used for creating the indices
21
+ def indices(file=Configuration.config_file)
22
+ @indices ||= begin
23
+ default = {}.extend Structure::Mergeable
24
+ Configuration.flex_models.each do |m|
25
+ m = eval"::#{m}" if m.is_a?(String)
26
+ next unless m.flex.is_child?
27
+ index = m.flex.index
28
+ m.flex.parent_child_map.each do |parent, child|
29
+ default.add index => {'mappings' => {child => {'_parent' => {'type' => parent }}}}
30
+ end
31
+ end
32
+ hash = YAML.load(Utils.erb_process(file))
33
+ hash.delete('ANCHORS')
34
+ default.deep_merge(hash)
35
+ end
36
+ end
37
+
38
+ # maps all the index/types to the ruby class
39
+ def type_class_map
40
+ @type_class_map ||= begin
41
+ map = {}
42
+ Configuration.flex_models.each do |m|
43
+ m = eval("::#{m}") if m.is_a?(String)
44
+ types = m.flex.type.is_a?(Array) ? m.flex.type : [m.flex.type]
45
+ types.each do |t|
46
+ map["#{m.flex.index}/#{t}"] = m
47
+ end
48
+ end
49
+ map
50
+ end
51
+ end
52
+
53
+ def class_name_to_type(class_name)
54
+ type = class_name.tr(':', '_')
55
+ type.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
56
+ type.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
57
+ type.downcase!
58
+ type
59
+ end
60
+
61
+ def type_to_class_name(type)
62
+ type.gsub(/__(.?)/) { "::#{$1.upcase}" }.gsub(/(?:^|_)(.)/) { $1.upcase }
63
+ end
64
+
65
+ end
66
+ end
67
+ end
data/lib/flex/rails.rb ADDED
@@ -0,0 +1,12 @@
1
+ require 'flex'
2
+ require 'rails'
3
+ require 'flex/rails/helper'
4
+
5
+ if Rails.version.match /^3/
6
+ require 'flex/rails/engine'
7
+ else
8
+ Flex::Configuration.configure do |c|
9
+ c.config_file = Rails.root.join('config', 'flex.yml').to_s
10
+ c.flex_dir = Rails.root.join('app', 'flex').to_s
11
+ end
12
+ end
@@ -0,0 +1,23 @@
1
+ module Flex
2
+ module Rails
3
+ class Engine < ::Rails::Engine
4
+
5
+ ActiveSupport.on_load(:before_configuration) do
6
+ Flex::Configuration.configure do |c|
7
+ c.variables[:index] = [self.class.name.split('::').first.underscore, ::Rails.env].join('_')
8
+ c.config_file = ::Rails.root.join('config', 'flex.yml').to_s
9
+ c.flex_dir = ::Rails.root.join('app', 'flex').to_s
10
+ end
11
+ end
12
+
13
+ ActiveSupport.on_load(:after_initialize) do
14
+ Helper.after_initialize
15
+ end
16
+
17
+ config.to_prepare do
18
+ Flex.reload!
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,16 @@
1
+ module Flex
2
+ module Rails
3
+ module Helper
4
+ extend self
5
+
6
+ def after_initialize
7
+ # use the same app logger
8
+ Flex::Configuration.logger = ::Rails.logger
9
+ # we need to reload the flex API methods with the new variables
10
+ Flex.reload!
11
+ Flex::Model::Manager.init
12
+ end
13
+
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,16 @@
1
+ module Flex
2
+ module RelatedModel
3
+
4
+ def self.included(base)
5
+ base.class_eval do
6
+ class << self; attr_reader :flex end
7
+ @flex ||= Flex::RelatedModel::ClassProxy.new(base)
8
+ end
9
+ end
10
+
11
+ def flex
12
+ @flex ||= InstanceProxy.new(self)
13
+ end
14
+
15
+ end
16
+ end
@@ -0,0 +1,23 @@
1
+ module Flex
2
+ module RelatedModel
3
+ class ClassProxy
4
+
5
+ attr_reader :host_class
6
+
7
+ def initialize(host_class)
8
+ @host_class = host_class
9
+ end
10
+
11
+ include RelatedModel::ClassSync
12
+
13
+ alias_method :full_sync, :sync
14
+
15
+ def sync(*synced)
16
+ raise ArgumentError, 'You cannot flex.sync(self) a Flex::RelatedModel.' \
17
+ if synced.any?{|s| s == host_class}
18
+ full_sync
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,23 @@
1
+ module Flex
2
+ module RelatedModel
3
+ module ClassSync
4
+
5
+ def self.included(base)
6
+ base.class_eval do
7
+ attr_accessor :synced
8
+ end
9
+ end
10
+
11
+ def sync(*synced)
12
+ @synced = synced
13
+ host_class.class_eval do
14
+ raise NotImplementedError, "the class #{self} must implement :after_save and :after_destroy callbacks" \
15
+ unless respond_to?(:after_save) && respond_to?(:after_destroy)
16
+ after_save { flex.sync }
17
+ after_destroy { flex.sync }
18
+ end
19
+ end
20
+
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,28 @@
1
+ module Flex
2
+ module RelatedModel
3
+ class InstanceProxy
4
+ attr_reader :instance, :class_flex
5
+
6
+ def initialize(instance)
7
+ @instance = instance
8
+ @class_flex = instance.class.flex
9
+ end
10
+
11
+ def sync
12
+ class_flex.synced.each do |s|
13
+ case
14
+ when s == instance.class # only called for Flex::Model
15
+ instance.destroyed? ? remove : store
16
+ when s.is_a?(Symbol)
17
+ instance.send(s).flex.sync
18
+ when s.is_a?(String)
19
+ parent_instance.flex.sync if s == parent_instance.flex.type
20
+ else
21
+ raise ArgumentError, "self, string or symbol expected, got #{s.inspect}"
22
+ end
23
+ end
24
+ end
25
+
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,18 @@
1
+ module Flex
2
+ class Result < ::Hash
3
+
4
+ attr_reader :template, :variables, :response
5
+
6
+ def initialize(template, variables, response, result=nil)
7
+ @template = template
8
+ @variables = variables
9
+ @response = response
10
+ replace result || !response.body.empty? && MultiJson.decode(response.body) || return
11
+ Configuration.result_extenders.each do |ext|
12
+ next if ext.respond_to?(:should_extend?) && !ext.should_extend?(self)
13
+ extend ext
14
+ end
15
+ end
16
+
17
+ end
18
+ end
@@ -0,0 +1,20 @@
1
+ module Flex
2
+ class Result
3
+ module Bulk
4
+
5
+ # extend if result comes from a bulk url
6
+ def self.should_extend?(result)
7
+ result.response.url =~ /\b_bulk\b/
8
+ end
9
+
10
+ def failed
11
+ self['items'].reject{|i| i['index']['ok']}
12
+ end
13
+
14
+ def successful
15
+ self['items'].select{|i| i['index']['ok']}
16
+ end
17
+
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,51 @@
1
+ module Flex
2
+ class Result
3
+ module Collection
4
+
5
+ attr_reader :total_entries, :variables
6
+
7
+ def setup(total_entries, variables)
8
+ @total_entries = total_entries
9
+ @variables = variables
10
+ end
11
+
12
+ def per_page
13
+ (@variables[:per_page] || @variables[:params] && @variables[:params][:size] || @variables[:size] || 10).to_i
14
+ end
15
+
16
+ def total_pages
17
+ ( @total_entries.to_f / per_page ).ceil
18
+ end
19
+
20
+ def current_page
21
+ if @variables[:page]
22
+ @variables[:page].to_i
23
+ else
24
+ (per_page + (@variables[:from]||0).to_i) / per_page
25
+ end
26
+ end
27
+
28
+ def previous_page
29
+ current_page > 1 ? (current_page - 1) : nil
30
+ end
31
+
32
+ def next_page
33
+ current_page < total_pages ? (current_page + 1) : nil
34
+ end
35
+
36
+ def offset
37
+ per_page * (current_page - 1)
38
+ end
39
+
40
+ def out_of_bounds?
41
+ current_page > total_pages
42
+ end
43
+
44
+ alias_method :limit_value, :per_page
45
+ alias_method :total_count, :total_entries
46
+ alias_method :num_pages, :total_pages
47
+ alias_method :offset_value, :offset
48
+
49
+ end
50
+ end
51
+ end