flex 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +20 -0
- data/README.md +20 -0
- data/VERSION +1 -0
- data/flex.gemspec +43 -0
- data/lib/flex.rb +418 -0
- data/lib/flex/api_methods.yml +108 -0
- data/lib/flex/class_proxy.rb +12 -0
- data/lib/flex/configuration.rb +57 -0
- data/lib/flex/errors.rb +42 -0
- data/lib/flex/http_clients/patron.rb +27 -0
- data/lib/flex/http_clients/rest_client.rb +38 -0
- data/lib/flex/loader.rb +116 -0
- data/lib/flex/logger.rb +16 -0
- data/lib/flex/model.rb +24 -0
- data/lib/flex/model/class_proxy.rb +45 -0
- data/lib/flex/model/instance_proxy.rb +101 -0
- data/lib/flex/model/manager.rb +67 -0
- data/lib/flex/rails.rb +12 -0
- data/lib/flex/rails/engine.rb +23 -0
- data/lib/flex/rails/helper.rb +16 -0
- data/lib/flex/related_model.rb +16 -0
- data/lib/flex/related_model/class_proxy.rb +23 -0
- data/lib/flex/related_model/class_sync.rb +23 -0
- data/lib/flex/related_model/instance_proxy.rb +28 -0
- data/lib/flex/result.rb +18 -0
- data/lib/flex/result/bulk.rb +20 -0
- data/lib/flex/result/collection.rb +51 -0
- data/lib/flex/result/document.rb +38 -0
- data/lib/flex/result/indifferent_access.rb +11 -0
- data/lib/flex/result/search.rb +51 -0
- data/lib/flex/result/source_document.rb +63 -0
- data/lib/flex/result/source_search.rb +32 -0
- data/lib/flex/structure/indifferent_access.rb +44 -0
- data/lib/flex/structure/mergeable.rb +21 -0
- data/lib/flex/tasks.rb +141 -0
- data/lib/flex/template.rb +187 -0
- data/lib/flex/template/base.rb +29 -0
- data/lib/flex/template/info.rb +50 -0
- data/lib/flex/template/partial.rb +31 -0
- data/lib/flex/template/search.rb +30 -0
- data/lib/flex/template/slim_search.rb +13 -0
- data/lib/flex/template/tags.rb +46 -0
- data/lib/flex/utility_methods.rb +140 -0
- data/lib/flex/utils.rb +59 -0
- data/lib/flex/variables.rb +11 -0
- data/lib/generators/flex/setup/setup_generator.rb +51 -0
- data/lib/generators/flex/setup/templates/flex_config.yml +16 -0
- data/lib/generators/flex/setup/templates/flex_dir/es.rb.erb +18 -0
- data/lib/generators/flex/setup/templates/flex_dir/es.yml.erb +19 -0
- data/lib/generators/flex/setup/templates/flex_dir/es_extender.rb.erb +17 -0
- data/lib/generators/flex/setup/templates/flex_initializer.rb.erb +44 -0
- data/lib/tasks/index.rake +23 -0
- data/test/flex.irt +143 -0
- data/test/flex/configuration.irt +53 -0
- data/test/irt_helper.rb +12 -0
- 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
|
data/lib/flex/result.rb
ADDED
@@ -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
|