flex 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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