influxer 0.0.1 → 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.
- checksums.yaml +4 -4
- data/.gitignore +1 -2
- data/.travis.yml +3 -2
- data/Changelog.md +19 -0
- data/Gemfile +0 -13
- data/MIT-LICENSE +1 -1
- data/README.md +51 -6
- data/gemfiles/rails42.gemfile +7 -0
- data/influxer.gemspec +7 -5
- data/lib/influxer/client.rb +46 -0
- data/lib/influxer/config.rb +10 -0
- data/lib/influxer/metrics/fanout.rb +53 -0
- data/lib/influxer/metrics/metrics.rb +33 -5
- data/lib/influxer/metrics/relation/fanout_query.rb +33 -0
- data/lib/influxer/metrics/relation/time_query.rb +73 -0
- data/lib/influxer/metrics/relation.rb +177 -39
- data/lib/influxer/metrics/scoping/default.rb +28 -0
- data/lib/influxer/metrics/scoping/named.rb +18 -0
- data/lib/influxer/metrics/scoping.rb +56 -0
- data/lib/influxer/model.rb +3 -1
- data/lib/influxer/version.rb +1 -1
- data/spec/client_spec.rb +34 -0
- data/spec/dummy/app/metrics/testo_metrics.rb +1 -1
- data/spec/dummy/app/models/testo.rb +2 -0
- data/spec/dummy/config/application.rb +1 -0
- data/spec/dummy/config/environments/test.rb +0 -1
- data/spec/metrics/fanout_spec.rb +46 -0
- data/spec/metrics/metrics_spec.rb +42 -7
- data/spec/metrics/relation_spec.rb +199 -51
- data/spec/metrics/scoping_spec.rb +68 -0
- data/spec/model/testo_spec.rb +12 -3
- data/spec/spec_helper.rb +1 -1
- metadata +78 -26
| @@ -1,12 +1,72 @@ | |
| 1 | 
            +
            require 'influxer/metrics/relation/time_query'
         | 
| 2 | 
            +
            require 'influxer/metrics/relation/fanout_query'
         | 
| 3 | 
            +
             | 
| 1 4 | 
             
            module Influxer
         | 
| 2 5 | 
             
              class Relation
         | 
| 6 | 
            +
                attr_reader :values
         | 
| 7 | 
            +
             | 
| 8 | 
            +
                include Influxer::TimeQuery
         | 
| 9 | 
            +
                include Influxer::FanoutQuery
         | 
| 3 10 |  | 
| 11 | 
            +
                MULTI_VALUE_METHODS = [:select, :where, :group]
         | 
| 12 | 
            +
             | 
| 13 | 
            +
                MULTI_KEY_METHODS = [:fanout]
         | 
| 14 | 
            +
             | 
| 15 | 
            +
                SINGLE_VALUE_METHODS = [:fill, :limit, :merge, :time]
         | 
| 16 | 
            +
             | 
| 17 | 
            +
                MULTI_VALUE_SIMPLE_METHODS = [:select, :group]
         | 
| 18 | 
            +
             | 
| 19 | 
            +
                SINGLE_VALUE_SIMPLE_METHODS = [:fill, :limit, :merge]
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                MULTI_VALUE_METHODS.each do |name|
         | 
| 22 | 
            +
                  class_eval <<-CODE, __FILE__, __LINE__ + 1
         | 
| 23 | 
            +
                    def #{name}_values                          # def select_values
         | 
| 24 | 
            +
                      @values[:#{name}] ||= []                  #   @values[:select] || []
         | 
| 25 | 
            +
                    end                                         # end
         | 
| 26 | 
            +
                  CODE
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                MULTI_KEY_METHODS.each do |name|
         | 
| 30 | 
            +
                  class_eval <<-CODE, __FILE__, __LINE__ + 1
         | 
| 31 | 
            +
                    def #{name}_values                          # def fanout_values
         | 
| 32 | 
            +
                      @values[:#{name}] ||= {}                  #   @values[:fanout] || {}
         | 
| 33 | 
            +
                    end                                         # end
         | 
| 34 | 
            +
                  CODE
         | 
| 35 | 
            +
                end
         | 
| 36 | 
            +
             | 
| 37 | 
            +
                SINGLE_VALUE_METHODS.each do |name|
         | 
| 38 | 
            +
                  class_eval <<-CODE, __FILE__, __LINE__ + 1
         | 
| 39 | 
            +
                    def #{name}_value                           # def limit_value
         | 
| 40 | 
            +
                      @values[:#{name}]                         #   @values[:limit]
         | 
| 41 | 
            +
                    end                                         # end
         | 
| 42 | 
            +
                  CODE
         | 
| 43 | 
            +
                end
         | 
| 44 | 
            +
             | 
| 45 | 
            +
                SINGLE_VALUE_SIMPLE_METHODS.each do |name|
         | 
| 46 | 
            +
                  class_eval <<-CODE, __FILE__, __LINE__ + 1
         | 
| 47 | 
            +
                    def #{name}(val)                            # def limit(val)
         | 
| 48 | 
            +
                      @values[:#{name}] = val                   #   @value[:limit] = val
         | 
| 49 | 
            +
                      self                                      #   self
         | 
| 50 | 
            +
                    end                                         # end
         | 
| 51 | 
            +
                  CODE
         | 
| 52 | 
            +
                end
         | 
| 53 | 
            +
             | 
| 54 | 
            +
                MULTI_VALUE_SIMPLE_METHODS.each do |name|
         | 
| 55 | 
            +
                  class_eval <<-CODE, __FILE__, __LINE__ + 1
         | 
| 56 | 
            +
                    def #{name}(*args)                          # def select(*args)
         | 
| 57 | 
            +
                      #{name}_values.concat args.map(&:to_s)    #  select_values.concat args.map(&:to_s)
         | 
| 58 | 
            +
                      self                                      #  self
         | 
| 59 | 
            +
                    end                                         # end
         | 
| 60 | 
            +
                  CODE
         | 
| 61 | 
            +
                end
         | 
| 62 | 
            +
             | 
| 4 63 | 
             
                # Initialize new Relation for 'klass' (Class) metrics.
         | 
| 5 64 | 
             
                # 
         | 
| 6 65 | 
             
                # Available params:
         | 
| 7 66 | 
             
                #  :attributes - hash of attributes to be included to new Metrics object and where clause of Relation
         | 
| 8 67 | 
             
                # 
         | 
| 9 68 | 
             
                def initialize(klass, params = {})
         | 
| 69 | 
            +
                  @klass = klass
         | 
| 10 70 | 
             
                  @instance = klass.new params[:attributes]
         | 
| 11 71 | 
             
                  self.reset
         | 
| 12 72 | 
             
                  self.where(params[:attributes]) if params[:attributes].present?
         | 
| @@ -26,59 +86,46 @@ module Influxer | |
| 26 86 | 
             
                  @instance
         | 
| 27 87 | 
             
                end
         | 
| 28 88 |  | 
| 29 | 
            -
                # accepts strings and symbols only
         | 
| 30 | 
            -
                def select(*args)
         | 
| 31 | 
            -
                  return self if args.empty?
         | 
| 32 | 
            -
                  @select_values.concat args
         | 
| 33 | 
            -
                  self
         | 
| 34 | 
            -
                end
         | 
| 35 | 
            -
             | 
| 36 89 | 
             
                # accepts hash or strings conditions
         | 
| 37 | 
            -
                # TODO: add sanitization and array support
         | 
| 38 | 
            -
             | 
| 39 90 | 
             
                def where(*args,**hargs)
         | 
| 40 | 
            -
                   | 
| 41 | 
            -
                  
         | 
| 42 | 
            -
                  unless hargs.empty?
         | 
| 43 | 
            -
                    hargs.each do |key, val|
         | 
| 44 | 
            -
                      @where_values << "(#{key}=#{quoted(val)})"
         | 
| 45 | 
            -
                    end
         | 
| 46 | 
            -
                  end
         | 
| 47 | 
            -
                  self
         | 
| 48 | 
            -
                end
         | 
| 49 | 
            -
             | 
| 50 | 
            -
                def group(*args)
         | 
| 51 | 
            -
                  return self if args.empty?
         | 
| 52 | 
            -
                  @group_values.concat args
         | 
| 91 | 
            +
                  build_where(args, hargs, false)
         | 
| 53 92 | 
             
                  self
         | 
| 54 93 | 
             
                end
         | 
| 55 94 |  | 
| 56 | 
            -
                def  | 
| 57 | 
            -
                   | 
| 95 | 
            +
                def not(*args, **hargs)
         | 
| 96 | 
            +
                  build_where(args, hargs, true)
         | 
| 58 97 | 
             
                  self
         | 
| 59 98 | 
             
                end
         | 
| 60 99 |  | 
| 61 100 | 
             
                def to_sql
         | 
| 62 101 | 
             
                  sql = ["select"]
         | 
| 63 102 |  | 
| 64 | 
            -
                  if  | 
| 103 | 
            +
                  if select_values.empty?
         | 
| 65 104 | 
             
                    sql << "*"
         | 
| 66 105 | 
             
                  else
         | 
| 67 | 
            -
                    sql <<  | 
| 106 | 
            +
                    sql << select_values.uniq.join(",")
         | 
| 68 107 | 
             
                  end 
         | 
| 69 108 |  | 
| 70 | 
            -
                  sql << "from #{ | 
| 109 | 
            +
                  sql << "from #{ build_series_name }"
         | 
| 110 | 
            +
             | 
| 111 | 
            +
                  unless merge_value.nil?
         | 
| 112 | 
            +
                    sql << "merge #{ @instance.quote_series(merge_value) }"
         | 
| 113 | 
            +
                  end
         | 
| 114 | 
            +
             | 
| 115 | 
            +
                  unless group_values.empty? and time_value.nil?
         | 
| 116 | 
            +
                    sql << "group by #{ (time_value.nil? ? [] : ['time('+@values[:time]+')']).concat(group_values).uniq.join(",") }"
         | 
| 117 | 
            +
                  end
         | 
| 71 118 |  | 
| 72 | 
            -
                  unless  | 
| 73 | 
            -
                    sql << " | 
| 119 | 
            +
                  unless fill_value.nil?
         | 
| 120 | 
            +
                    sql << "fill(#{ fill_value })"
         | 
| 74 121 | 
             
                  end
         | 
| 75 122 |  | 
| 76 | 
            -
                  unless  | 
| 77 | 
            -
                    sql << "where #{ | 
| 123 | 
            +
                  unless where_values.empty?
         | 
| 124 | 
            +
                    sql << "where #{ where_values.join(" and ") }"
         | 
| 78 125 | 
             
                  end
         | 
| 79 126 |  | 
| 80 | 
            -
                  unless  | 
| 81 | 
            -
                    sql << "limit #{ | 
| 127 | 
            +
                  unless limit_value.nil?
         | 
| 128 | 
            +
                    sql << "limit #{ limit_value }"
         | 
| 82 129 | 
             
                  end
         | 
| 83 130 | 
             
                  sql.join " "
         | 
| 84 131 | 
             
                end
         | 
| @@ -101,12 +148,96 @@ module Influxer | |
| 101 148 | 
             
                end
         | 
| 102 149 |  | 
| 103 150 | 
             
                def delete_all
         | 
| 151 | 
            +
                  sql = ["delete"]
         | 
| 152 | 
            +
             | 
| 153 | 
            +
                  sql << "from #{@instance.series}"
         | 
| 154 | 
            +
             | 
| 155 | 
            +
                  unless where_values.empty?
         | 
| 156 | 
            +
                    sql << "where #{where_values.join(" and ")}"
         | 
| 157 | 
            +
                  end
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                  sql = sql.join " "
         | 
| 160 | 
            +
             | 
| 161 | 
            +
                  @instance.client.query sql
         | 
| 162 | 
            +
                end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                def scoping
         | 
| 165 | 
            +
                  previous, @klass.current_scope = @klass.current_scope, self
         | 
| 166 | 
            +
                  yield
         | 
| 167 | 
            +
                ensure
         | 
| 168 | 
            +
                  @klass.current_scope = previous
         | 
| 169 | 
            +
                end
         | 
| 170 | 
            +
             | 
| 171 | 
            +
                def merge!(rel)
         | 
| 172 | 
            +
                  return self if rel.nil?
         | 
| 173 | 
            +
                  MULTI_VALUE_METHODS.each do |method|
         | 
| 174 | 
            +
                    (@values[method]||=[]).concat(rel.values[method]).uniq! unless rel.values[method].nil?
         | 
| 175 | 
            +
                  end
         | 
| 176 | 
            +
             | 
| 177 | 
            +
                  MULTI_KEY_METHODS.each do |method|
         | 
| 178 | 
            +
                    (@values[method]||={}).merge!(rel.values[method]) unless rel.values[method].nil?
         | 
| 179 | 
            +
                  end
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                  SINGLE_VALUE_METHODS.each do |method|
         | 
| 182 | 
            +
                    @values[method] = rel.values[method] unless rel.values[method].nil?
         | 
| 183 | 
            +
                  end
         | 
| 104 184 |  | 
| 185 | 
            +
                  self
         | 
| 105 186 | 
             
                end
         | 
| 106 187 |  | 
| 107 188 | 
             
                protected
         | 
| 189 | 
            +
                  def build_where(args, hargs, negate)
         | 
| 190 | 
            +
                    case
         | 
| 191 | 
            +
                    when (args.present? and args[0].is_a?(String))
         | 
| 192 | 
            +
                      where_values.concat args.map{|str| "(#{str})"}
         | 
| 193 | 
            +
                    when hargs.present?
         | 
| 194 | 
            +
                      build_hash_where(hargs, negate)
         | 
| 195 | 
            +
                    else
         | 
| 196 | 
            +
                      false
         | 
| 197 | 
            +
                    end
         | 
| 198 | 
            +
                  end
         | 
| 199 | 
            +
             | 
| 200 | 
            +
                  def build_hash_where(hargs, negate = false)
         | 
| 201 | 
            +
                    hargs.each do |key, val|
         | 
| 202 | 
            +
                      if @klass.fanout?(key)
         | 
| 203 | 
            +
                        build_fanout(key,val)
         | 
| 204 | 
            +
                      else
         | 
| 205 | 
            +
                        where_values << "(#{ build_eql(key,val,negate) })"
         | 
| 206 | 
            +
                      end
         | 
| 207 | 
            +
                    end
         | 
| 208 | 
            +
                  end
         | 
| 209 | 
            +
             | 
| 210 | 
            +
                  def build_eql(key,val,negate)
         | 
| 211 | 
            +
                    case val
         | 
| 212 | 
            +
                    when Regexp
         | 
| 213 | 
            +
                      "#{key}#{ negate ? '!~' : '=~'}#{val.inspect}"
         | 
| 214 | 
            +
                    when Array
         | 
| 215 | 
            +
                      build_in(key,val,negate)
         | 
| 216 | 
            +
                    when Range
         | 
| 217 | 
            +
                      build_range(key,val,negate)
         | 
| 218 | 
            +
                    else
         | 
| 219 | 
            +
                      "#{key}#{ negate ? '<>' : '='}#{quoted(val)}"
         | 
| 220 | 
            +
                    end  
         | 
| 221 | 
            +
                  end
         | 
| 222 | 
            +
             | 
| 223 | 
            +
                  def build_in(key, arr, negate)
         | 
| 224 | 
            +
                    buf = []
         | 
| 225 | 
            +
                    arr.each do |val|
         | 
| 226 | 
            +
                      buf << build_eql(key,val,negate)
         | 
| 227 | 
            +
                    end
         | 
| 228 | 
            +
                    "#{ buf.join( negate ? ' and ' : ' or ') }"
         | 
| 229 | 
            +
                  end
         | 
| 230 | 
            +
             | 
| 231 | 
            +
                  def build_range(key,val,negate)
         | 
| 232 | 
            +
                    unless negate
         | 
| 233 | 
            +
                      "#{key}>#{quoted(val.begin)} and #{key}<#{quoted(val.end)}"
         | 
| 234 | 
            +
                    else
         | 
| 235 | 
            +
                      "#{key}<#{quoted(val.begin)} and #{key}>#{quoted(val.end)}"
         | 
| 236 | 
            +
                    end  
         | 
| 237 | 
            +
                  end
         | 
| 238 | 
            +
             | 
| 108 239 | 
             
                  def load
         | 
| 109 | 
            -
                    @records = @instance.client. | 
| 240 | 
            +
                    @records = get_points(@instance.client.cached_query(to_sql))
         | 
| 110 241 | 
             
                    @loaded = true
         | 
| 111 242 | 
             
                  end
         | 
| 112 243 |  | 
| @@ -115,10 +246,7 @@ module Influxer | |
| 115 246 | 
             
                  end
         | 
| 116 247 |  | 
| 117 248 | 
             
                  def reset
         | 
| 118 | 
            -
                    @ | 
| 119 | 
            -
                    @select_values = []
         | 
| 120 | 
            -
                    @group_values = []
         | 
| 121 | 
            -
                    @where_values = []
         | 
| 249 | 
            +
                    @values = {}
         | 
| 122 250 | 
             
                    @records = nil
         | 
| 123 251 | 
             
                    @loaded = false
         | 
| 124 252 | 
             
                    self
         | 
| @@ -131,7 +259,7 @@ module Influxer | |
| 131 259 | 
             
                  end
         | 
| 132 260 |  | 
| 133 261 | 
             
                  def quoted(val)
         | 
| 134 | 
            -
                    if val.is_a?(String)
         | 
| 262 | 
            +
                    if val.is_a?(String) or val.is_a?(Symbol)
         | 
| 135 263 | 
             
                      "'#{val}'"
         | 
| 136 264 | 
             
                    elsif val.kind_of?(Time) or val.kind_of?(DateTime)
         | 
| 137 265 | 
             
                      "#{val.to_i}s"
         | 
| @@ -139,5 +267,15 @@ module Influxer | |
| 139 267 | 
             
                      val.to_s
         | 
| 140 268 | 
             
                    end
         | 
| 141 269 | 
             
                  end
         | 
| 270 | 
            +
             | 
| 271 | 
            +
                  def method_missing(method, *args, &block)
         | 
| 272 | 
            +
                    if @klass.respond_to?(method)    
         | 
| 273 | 
            +
                      merge!(scoping { @klass.public_send(method, *args, &block) })
         | 
| 274 | 
            +
                    end
         | 
| 275 | 
            +
                  end
         | 
| 276 | 
            +
             | 
| 277 | 
            +
                  def get_points(hash)
         | 
| 278 | 
            +
                    hash.values.reduce([],:+)
         | 
| 279 | 
            +
                  end
         | 
| 142 280 | 
             
              end
         | 
| 143 281 | 
             
            end 
         | 
| @@ -0,0 +1,28 @@ | |
| 1 | 
            +
            module Influxer
         | 
| 2 | 
            +
              module Scoping
         | 
| 3 | 
            +
                module Default
         | 
| 4 | 
            +
                  extend ActiveSupport::Concern
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  included do
         | 
| 7 | 
            +
                    class_attribute :default_scopes
         | 
| 8 | 
            +
                    self.default_scopes = []
         | 
| 9 | 
            +
                  end
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                  module ClassMethods
         | 
| 12 | 
            +
                    def default_scope(scope)
         | 
| 13 | 
            +
                      self.default_scopes += [scope] unless scope.nil?
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                    def unscoped
         | 
| 17 | 
            +
                      Relation.new self
         | 
| 18 | 
            +
                    end
         | 
| 19 | 
            +
             | 
| 20 | 
            +
                    def default_scoped
         | 
| 21 | 
            +
                      self.default_scopes.inject(Relation.new(self)) do |rel, scope| 
         | 
| 22 | 
            +
                        rel.merge!(rel.scoping{ scope.call })
         | 
| 23 | 
            +
                      end
         | 
| 24 | 
            +
                    end
         | 
| 25 | 
            +
                  end
         | 
| 26 | 
            +
                end
         | 
| 27 | 
            +
              end
         | 
| 28 | 
            +
            end
         | 
| @@ -0,0 +1,18 @@ | |
| 1 | 
            +
            module Influxer
         | 
| 2 | 
            +
              module Scoping
         | 
| 3 | 
            +
                module Named
         | 
| 4 | 
            +
                  extend ActiveSupport::Concern
         | 
| 5 | 
            +
             | 
| 6 | 
            +
                  module ClassMethods
         | 
| 7 | 
            +
                    def scope(name, scope)
         | 
| 8 | 
            +
                      raise Error.new("Scope not defined: #{name}") if scope.nil? or !scope.respond_to?(:call)
         | 
| 9 | 
            +
                      singleton_class.send(:define_method, name) do |*args|
         | 
| 10 | 
            +
                        rel = all
         | 
| 11 | 
            +
                        rel.merge!(rel.scoping { scope.call(*args) })
         | 
| 12 | 
            +
                        rel
         | 
| 13 | 
            +
                      end
         | 
| 14 | 
            +
                    end
         | 
| 15 | 
            +
                  end
         | 
| 16 | 
            +
                end
         | 
| 17 | 
            +
              end
         | 
| 18 | 
            +
            end
         | 
| @@ -0,0 +1,56 @@ | |
| 1 | 
            +
            require 'active_support/per_thread_registry'
         | 
| 2 | 
            +
            require 'influxer/metrics/scoping/default'
         | 
| 3 | 
            +
            require 'influxer/metrics/scoping/named'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            module Influxer
         | 
| 6 | 
            +
              module Scoping
         | 
| 7 | 
            +
                extend ActiveSupport::Concern
         | 
| 8 | 
            +
             | 
| 9 | 
            +
                class Error < StandardError; end;
         | 
| 10 | 
            +
             | 
| 11 | 
            +
                included do
         | 
| 12 | 
            +
                  include Default
         | 
| 13 | 
            +
                  include Named
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
             | 
| 16 | 
            +
                module ClassMethods
         | 
| 17 | 
            +
                  def current_scope #:nodoc:
         | 
| 18 | 
            +
                    ScopeRegistry.value_for(:current_scope, name)
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
                  def current_scope=(scope) #:nodoc:
         | 
| 22 | 
            +
                    ScopeRegistry.set_value_for(:current_scope, name, scope)
         | 
| 23 | 
            +
                  end
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
             | 
| 26 | 
            +
                class ScopeRegistry # :nodoc:
         | 
| 27 | 
            +
                  extend ActiveSupport::PerThreadRegistry
         | 
| 28 | 
            +
             | 
| 29 | 
            +
                  VALID_SCOPE_TYPES = [:current_scope]
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                  def initialize
         | 
| 32 | 
            +
                    @registry = Hash.new { |hash, key| hash[key] = {} }
         | 
| 33 | 
            +
                  end
         | 
| 34 | 
            +
             | 
| 35 | 
            +
                  # Obtains the value for a given +scope_name+ and +variable_name+.
         | 
| 36 | 
            +
                  def value_for(scope_type, variable_name)
         | 
| 37 | 
            +
                    raise_invalid_scope_type!(scope_type)
         | 
| 38 | 
            +
                    @registry[scope_type][variable_name]
         | 
| 39 | 
            +
                  end
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  # Sets the +value+ for a given +scope_type+ and +variable_name+.
         | 
| 42 | 
            +
                  def set_value_for(scope_type, variable_name, value)
         | 
| 43 | 
            +
                    raise_invalid_scope_type!(scope_type)
         | 
| 44 | 
            +
                    @registry[scope_type][variable_name] = value
         | 
| 45 | 
            +
                  end
         | 
| 46 | 
            +
             | 
| 47 | 
            +
                  private
         | 
| 48 | 
            +
             | 
| 49 | 
            +
                  def raise_invalid_scope_type!(scope_type)
         | 
| 50 | 
            +
                    if !VALID_SCOPE_TYPES.include?(scope_type)
         | 
| 51 | 
            +
                      raise ArgumentError, "Invalid scope type '#{scope_type}' sent to the registry. Scope types must be included in VALID_SCOPE_TYPES"
         | 
| 52 | 
            +
                    end
         | 
| 53 | 
            +
                  end
         | 
| 54 | 
            +
                end
         | 
| 55 | 
            +
              end
         | 
| 56 | 
            +
            end
         | 
    
        data/lib/influxer/model.rb
    CHANGED
    
    | @@ -17,8 +17,10 @@ module Influxer | |
| 17 17 | 
             
                      attrs = params[:inherits]
         | 
| 18 18 | 
             
                    end
         | 
| 19 19 |  | 
| 20 | 
            +
                    _foreign_key = params.key?(:foreign_key) ? params[:foreign_key] : self.to_s.foreign_key
         | 
| 21 | 
            +
             | 
| 20 22 | 
             
                    define_method(metrics_name) do
         | 
| 21 | 
            -
                      rel_attrs = { | 
| 23 | 
            +
                      rel_attrs = _foreign_key ? {_foreign_key => self.id} : {}
         | 
| 22 24 |  | 
| 23 25 | 
             
                      unless attrs.nil?
         | 
| 24 26 | 
             
                        attrs.each do |key|
         | 
    
        data/lib/influxer/version.rb
    CHANGED
    
    
    
        data/spec/client_spec.rb
    CHANGED
    
    | @@ -2,6 +2,10 @@ require 'spec_helper' | |
| 2 2 |  | 
| 3 3 | 
             
            describe Influxer::Client do
         | 
| 4 4 |  | 
| 5 | 
            +
              after(:each) do
         | 
| 6 | 
            +
                Rails.cache.clear
         | 
| 7 | 
            +
              end
         | 
| 8 | 
            +
             | 
| 5 9 | 
             
              let(:conf) { Influxer.config }
         | 
| 6 10 | 
             
              let(:client) { Influxer.client }
         | 
| 7 11 |  | 
| @@ -11,4 +15,34 @@ describe Influxer::Client do | |
| 11 15 | 
             
                expect(client.database).to eq conf.database
         | 
| 12 16 | 
             
              end
         | 
| 13 17 |  | 
| 18 | 
            +
              describe "cache" do
         | 
| 19 | 
            +
                before do
         | 
| 20 | 
            +
                  allow_any_instance_of(Influxer::Client).to receive(:query) do |_, sql|   
         | 
| 21 | 
            +
                    sql
         | 
| 22 | 
            +
                  end
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
             | 
| 25 | 
            +
                let(:q) { "list series" }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
                after(:each) do
         | 
| 28 | 
            +
                  conf.cache = false
         | 
| 29 | 
            +
                end
         | 
| 30 | 
            +
             | 
| 31 | 
            +
                it "should write data to cache" do
         | 
| 32 | 
            +
                  conf.cache = {}
         | 
| 33 | 
            +
             | 
| 34 | 
            +
                  client.cached_query(q)
         | 
| 35 | 
            +
                  expect(Rails.cache.exist?("influxer:listseries")).to be_truthy
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                it "should write data to cache with expiration" do
         | 
| 39 | 
            +
                  conf.cache = {expires_in: 1}
         | 
| 40 | 
            +
             | 
| 41 | 
            +
                  client.cached_query(q)
         | 
| 42 | 
            +
                  expect(Rails.cache.exist?("influxer:listseries")).to be_truthy
         | 
| 43 | 
            +
                  
         | 
| 44 | 
            +
                  sleep 2
         | 
| 45 | 
            +
                  expect(Rails.cache.exist?("influxer:listseries")).to be_falsey
         | 
| 46 | 
            +
                end
         | 
| 47 | 
            +
              end
         | 
| 14 48 | 
             
            end
         | 
| @@ -1,4 +1,6 @@ | |
| 1 1 | 
             
            class Testo < ActiveRecord::Base
         | 
| 2 2 | 
             
              has_metrics
         | 
| 3 3 | 
             
              has_metrics :testo_metrics, class_name: "TestoMetrics", inherits: [:receipt_id]
         | 
| 4 | 
            +
              has_metrics :testo2_metrics, class_name: "TestoMetrics", foreign_key: :testo
         | 
| 5 | 
            +
              has_metrics :custom_metrics, class_name: "TestoMetrics", foreign_key: nil
         | 
| 4 6 | 
             
            end
         | 
| @@ -7,6 +7,7 @@ require "influxer" | |
| 7 7 |  | 
| 8 8 | 
             
            module Dummy
         | 
| 9 9 | 
             
              class Application < Rails::Application
         | 
| 10 | 
            +
                config.cache_store = :memory_store
         | 
| 10 11 | 
             
                # Settings in config/environments/* take precedence over those specified here.
         | 
| 11 12 | 
             
                # Application configuration should go into files in config/initializers
         | 
| 12 13 | 
             
                # -- all .rb files in that directory are automatically loaded.
         | 
| @@ -1,6 +1,5 @@ | |
| 1 1 | 
             
            Dummy::Application.configure do
         | 
| 2 2 | 
             
              # Settings specified here will take precedence over those in config/application.rb.
         | 
| 3 | 
            -
             | 
| 4 3 | 
             
              # The test environment is used exclusively to run your application's
         | 
| 5 4 | 
             
              # test suite. You never need to work with it otherwise. Remember that
         | 
| 6 5 | 
             
              # your test database is "scratch space" for the test suite and is wiped
         | 
| @@ -0,0 +1,46 @@ | |
| 1 | 
            +
            require 'spec_helper'
         | 
| 2 | 
            +
             | 
| 3 | 
            +
            describe Influxer::Metrics do
         | 
| 4 | 
            +
              before do
         | 
| 5 | 
            +
                allow_any_instance_of(Influxer::Client).to receive(:query) do |_, sql|    
         | 
| 6 | 
            +
                  sql
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
              end
         | 
| 9 | 
            +
             | 
| 10 | 
            +
              let(:dummy) do
         | 
| 11 | 
            +
                Class.new(Influxer::Metrics) do
         | 
| 12 | 
            +
                  set_series 'dummy'
         | 
| 13 | 
            +
                  default_scope -> { time(:hour) }
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
              end
         | 
| 16 | 
            +
             | 
| 17 | 
            +
              let(:doomy) do
         | 
| 18 | 
            +
                Class.new(dummy) do
         | 
| 19 | 
            +
                  scope :by_user, -> (id) { where(user: id) if id.present? }
         | 
| 20 | 
            +
                  scope :hourly, -> { where(by: :hour).time(nil) }
         | 
| 21 | 
            +
                  scope :daily, -> { where(by: :day).time(nil) }
         | 
| 22 | 
            +
             | 
| 23 | 
            +
                  fanout :by, :user, :account, delimeter: "."
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
              end
         | 
| 26 | 
            +
             | 
| 27 | 
            +
              let(:dappy) do
         | 
| 28 | 
            +
                Class.new(doomy) do
         | 
| 29 | 
            +
                  fanout :user, delimeter: "_"
         | 
| 30 | 
            +
                end
         | 
| 31 | 
            +
              end
         | 
| 32 | 
            +
             | 
| 33 | 
            +
              describe "fanouts" do
         | 
| 34 | 
            +
                it "should work with one fanout" do
         | 
| 35 | 
            +
                  expect(doomy.by_user(1).to_sql).to eq "select * from \"dummy.user.1\" group by time(1h)"
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
             | 
| 38 | 
            +
                it "should work with several fanouts" do
         | 
| 39 | 
            +
                  expect(dappy.by_user(1).hourly.to_sql).to eq "select * from \"dummy_by_hour_user_1\""
         | 
| 40 | 
            +
                end
         | 
| 41 | 
            +
             | 
| 42 | 
            +
                it "should work with regexp fanouts" do
         | 
| 43 | 
            +
                  expect(dappy.where(dummy_id: 100).by_user(/[1-3]/).daily.to_sql).to eq "select * from merge(/^dummy_by_day_user_[1-3]$/) where (dummy_id=100)"
         | 
| 44 | 
            +
                end
         | 
| 45 | 
            +
              end
         | 
| 46 | 
            +
            end
         | 
| @@ -1,6 +1,11 @@ | |
| 1 1 | 
             
            require 'spec_helper'
         | 
| 2 2 |  | 
| 3 3 | 
             
            describe Influxer::Metrics do
         | 
| 4 | 
            +
              before do
         | 
| 5 | 
            +
                allow_any_instance_of(Influxer::Client).to receive(:query) do |_, sql|    
         | 
| 6 | 
            +
                  sql
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
              end
         | 
| 4 9 |  | 
| 5 10 | 
             
              let(:metrics) { Influxer::Metrics.new }
         | 
| 6 11 | 
             
              let(:metrics_class) { Influxer::Metrics }
         | 
| @@ -16,7 +21,10 @@ describe Influxer::Metrics do | |
| 16 21 |  | 
| 17 22 | 
             
              specify { expect(metrics_class).to respond_to :all}
         | 
| 18 23 | 
             
              specify { expect(metrics_class).to respond_to :where}
         | 
| 19 | 
            -
              specify { expect(metrics_class).to respond_to : | 
| 24 | 
            +
              specify { expect(metrics_class).to respond_to :merge}
         | 
| 25 | 
            +
              specify { expect(metrics_class).to respond_to :time}
         | 
| 26 | 
            +
              specify { expect(metrics_class).to respond_to :past}
         | 
| 27 | 
            +
              specify { expect(metrics_class).to respond_to :since}
         | 
| 20 28 | 
             
              specify { expect(metrics_class).to respond_to :limit}
         | 
| 21 29 | 
             
              specify { expect(metrics_class).to respond_to :select}
         | 
| 22 30 | 
             
              specify { expect(metrics_class).to respond_to :delete_all}
         | 
| @@ -86,12 +94,30 @@ describe Influxer::Metrics do | |
| 86 94 | 
             
                  end
         | 
| 87 95 | 
             
                end
         | 
| 88 96 |  | 
| 97 | 
            +
                let(:dummy_metrics_2) do
         | 
| 98 | 
            +
                  Class.new(Influxer::Metrics) do
         | 
| 99 | 
            +
                    set_series "dummy \"A\""       
         | 
| 100 | 
            +
                  end
         | 
| 101 | 
            +
                end
         | 
| 102 | 
            +
             | 
| 103 | 
            +
                let(:dummy_metrics_3) do
         | 
| 104 | 
            +
                  Class.new(Influxer::Metrics) do
         | 
| 105 | 
            +
                    set_series /^.*$/       
         | 
| 106 | 
            +
                  end
         | 
| 107 | 
            +
                end
         | 
| 108 | 
            +
             | 
| 89 109 | 
             
                let(:dummy_with_2_series) do
         | 
| 90 110 | 
             
                  Class.new(Influxer::Metrics) do
         | 
| 91 111 | 
             
                    set_series :events, :errors       
         | 
| 92 112 | 
             
                  end
         | 
| 93 113 | 
             
                end
         | 
| 94 114 |  | 
| 115 | 
            +
                let(:dummy_with_2_series_quoted) do
         | 
| 116 | 
            +
                  Class.new(Influxer::Metrics) do
         | 
| 117 | 
            +
                    set_series "dummy \"A\"", "dummy \"B\""        
         | 
| 118 | 
            +
                  end
         | 
| 119 | 
            +
                end
         | 
| 120 | 
            +
             | 
| 95 121 | 
             
                let(:dummy_with_proc_series) do
         | 
| 96 122 | 
             
                  Class.new(Influxer::Metrics) do
         | 
| 97 123 | 
             
                    attributes :user_id, :test_id
         | 
| @@ -99,27 +125,36 @@ describe Influxer::Metrics do | |
| 99 125 | 
             
                  end
         | 
| 100 126 | 
             
                end
         | 
| 101 127 |  | 
| 102 | 
            -
             | 
| 103 | 
            -
             | 
| 104 128 | 
             
                describe "set_series" do
         | 
| 105 129 | 
             
                  it "should set series name from class name by default" do
         | 
| 106 | 
            -
                    expect(DummyMetrics.series).to eq  | 
| 130 | 
            +
                    expect(DummyMetrics.new.series).to eq "\"dummy\""
         | 
| 107 131 | 
             
                  end
         | 
| 108 132 |  | 
| 109 133 | 
             
                  it "should set series from subclass" do
         | 
| 110 | 
            -
                    expect(dummy_metrics.series).to eq  | 
| 134 | 
            +
                    expect(dummy_metrics.new.series).to eq "\"dummies\""
         | 
| 135 | 
            +
                  end
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                  it "should set series as regexp" do
         | 
| 138 | 
            +
                    expect(dummy_metrics_3.new.series).to eq '/^.*$/'
         | 
| 139 | 
            +
                  end
         | 
| 140 | 
            +
             | 
| 141 | 
            +
                  it "should set series with quotes" do
         | 
| 142 | 
            +
                    expect(dummy_metrics_2.new.series).to eq "\"dummy \\\"A\\\"\""
         | 
| 111 143 | 
             
                  end
         | 
| 112 144 |  | 
| 113 145 | 
             
                  it "should set several series" do
         | 
| 114 | 
            -
                    expect(dummy_with_2_series.series).to eq  | 
| 146 | 
            +
                    expect(dummy_with_2_series.new.series).to eq "merge(\"events\",\"errors\")"
         | 
| 115 147 | 
             
                  end
         | 
| 116 148 |  | 
| 149 | 
            +
                  it "should set several series with quotes" do
         | 
| 150 | 
            +
                    expect(dummy_with_2_series_quoted.new.series).to eq "merge(\"dummy \\\"A\\\"\",\"dummy \\\"B\\\"\")"
         | 
| 151 | 
            +
                  end
         | 
| 117 152 |  | 
| 118 153 | 
             
                  it "should set series from proc" do
         | 
| 119 154 | 
             
                    expect(dummy_with_proc_series.series).to be_an_instance_of Proc
         | 
| 120 155 |  | 
| 121 156 | 
             
                    m = dummy_with_proc_series.new user_id: 2, test_id:123
         | 
| 122 | 
            -
                    expect( | 
| 157 | 
            +
                    expect(m.series).to eq "\"test/123/user/2\""
         | 
| 123 158 | 
             
                  end
         | 
| 124 159 | 
             
                end
         | 
| 125 160 |  |