deli 0.5.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,54 @@
1
+ module Deli
2
+ module Adapters
3
+ # @todo
4
+ # IceCream.find(:all, :condition => {:flavour => 'chocolate'}, :sort => {:name => :asc})
5
+ # http://neo4j.rubyforge.org/guides/rails3.html
6
+ module Neo4j
7
+ class Query < ::Deli::Query
8
+ def render(params)
9
+
10
+ end
11
+ end
12
+
13
+ class Param < ::Deli::Param
14
+ def render(value)
15
+
16
+ end
17
+
18
+ def render_value(value, operator = nil)
19
+ value
20
+ end
21
+ end
22
+
23
+ class Time < Param
24
+ include Deli::Param::Time
25
+ end
26
+
27
+ class Date < Time
28
+ include Deli::Param::Date
29
+ end
30
+
31
+ class String < Param
32
+ include Deli::Param::String
33
+ end
34
+
35
+ class Number < Param
36
+ include Deli::Param::Number
37
+ end
38
+
39
+ class Limit < Param
40
+ include Deli::Param::Limit
41
+ end
42
+
43
+ class Order < Param
44
+ include Deli::Param::Order
45
+
46
+ def render(value)
47
+ parse(value).map do |item|
48
+ [render_name(item[:namespace], item[:key]), item[:operators][0] == "+" ? :asc : :desc]
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,39 @@
1
+ module Deli
2
+ module Adapters
3
+ module Simple
4
+ class Query < ::Deli::Query
5
+ end
6
+
7
+ class Param < ::Deli::Param
8
+ end
9
+
10
+ class Time < Param
11
+ include Deli::Param::Time
12
+ end
13
+
14
+ class Date < Time
15
+ include Deli::Param::Date
16
+ end
17
+
18
+ class String < Param
19
+ include Deli::Param::String
20
+ end
21
+
22
+ class Number < Param
23
+ include Deli::Param::Number
24
+ end
25
+
26
+ class Limit < Param
27
+ include Deli::Param::Limit
28
+ end
29
+
30
+ class Order < Param
31
+ include Deli::Param::Order
32
+ end
33
+
34
+ class Offset < Param
35
+ include Deli::Param::Offset
36
+ end
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,47 @@
1
+ module Deli
2
+ class << self
3
+ def configuration
4
+ @configuration ||= Deli::Configuration.new
5
+ end
6
+
7
+ def configure(&block)
8
+ yield configuration
9
+ end
10
+ end
11
+
12
+ class Configuration
13
+ attr_accessor :per_page, :sort_direction, :sort_key, :limit_key, :page_key, :default_adapter, :query_operators
14
+
15
+ def initialize
16
+ @per_page = 20
17
+ @sort_direction = "ASC"
18
+ @sort_key = "sort" # or "order", etc.
19
+ @limit_key = "limit" # or "per_page", etc.
20
+ @page_key = "page"
21
+ @separator = "_" # or "-"
22
+
23
+ # this is not used, just thinking...
24
+ # nested relationships as user[location][city]=san+diego
25
+ @operators = {
26
+ :gte => ":value..t",
27
+ :gt => ":value...t",
28
+ :lte => "t..:value",
29
+ :lte => "t...:value",
30
+ :range_inclusive => ":i..:f", # count=0..4
31
+ :range_exclusive => ":i...:f", # date=2011-08-10...2011-10-03
32
+ :in => [",", "+OR+"], # tags=ruby,javascript and tags=ruby+OR+javascript
33
+ :nin => "-", # tags=-ruby,-javascript and tags=ruby+OR+javascript
34
+ :all => "[:value]", # tags=[ruby,javascript] and tags=ruby+AND+javascript
35
+ :nil => "[-]", # tags=[-]
36
+ :not_nil => "[+]", # tags=ruby,[+]
37
+ :asc => ["+", ""],
38
+ :desc => "-",
39
+ :geo => ":lat,:lng,:radius" # geo=20,-50,7
40
+ }
41
+ end
42
+
43
+ def default_adapter
44
+ @default_adapter ||= :active_record
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,75 @@
1
+ module Deli
2
+ class Controller
3
+ attr_accessor :params, :model_name
4
+ # not clean at all, couples the classes, but just added quickly to accomplish it. needs refactoring.
5
+
6
+ def initialize(*args, &block)
7
+ self.model_name = args.shift
8
+ options = args.extract_options!
9
+ @params = []
10
+ @params << param!(:page, :type => :offset) unless options[:page] == false
11
+ @params << param!(:sort, :type => :order, :default => options[:sort]) unless options[:sort] == false
12
+ @params << param!(:limit, :type => :limit, :default => options[:limit] || config.per_page) unless options[:limit] == false
13
+ instance_eval(&block)
14
+ self
15
+ end
16
+
17
+ def config
18
+ Deli.configuration
19
+ end
20
+
21
+ # handles the following:
22
+ #
23
+ # match :created_at # figures out attribute from table definition
24
+ # match :created_by # figures out that it's on the 'users' table, and to add the join
25
+ # match :name, :to => {:vendor => :name} # maps to 'vendors.name' column.
26
+ # if 'vendor' is polymorphic, it will figure it out from the association reflection.
27
+ # can only handle one level deep for now, no {:vendor => {:role => :name}}
28
+ # match :name, :to => :by_vendor_name # maps to named scope
29
+ # you can also write your own join:
30
+ #
31
+ # match :name, :joins => "INNER JOIN products ON products.id = bookmarks.id"
32
+ def match(key, options = {})
33
+ @params << param!(key, options)
34
+ end
35
+
36
+ def render(params)
37
+ query.render(params)
38
+ end
39
+
40
+ def find(key)
41
+ query.find(key)
42
+ end
43
+
44
+ def query
45
+ @query ||= query_class.new(self)
46
+ end
47
+
48
+ def keys
49
+ params.map(&:key)
50
+ end
51
+
52
+ def find_type(key, options = {})
53
+ model.model_type(key, options)
54
+ end
55
+
56
+ def model
57
+ @model ||= "::Deli::Adapters::#{config.default_adapter.to_s.camelize}::Model".constantize.new(model_name)
58
+ end
59
+
60
+ def query_class
61
+ @query_class ||= "::Deli::Adapters::#{config.default_adapter.to_s.camelize}::Query".constantize
62
+ end
63
+
64
+ def find_adapter(options = {})
65
+ (options[:adapter] || config.default_adapter).to_s.camelize
66
+ end
67
+
68
+ def param!(key, options = {})
69
+ "::Deli::Adapters::#{find_adapter(options)}::#{find_type(key.to_s, options).to_s.camelize}".constantize.new(
70
+ key,
71
+ options.reverse_merge(:model_name => self.model_name, :parser => self)
72
+ )
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,61 @@
1
+ module Deli
2
+ module Helper
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ class_attribute :query_controller
7
+ end
8
+
9
+ module ClassMethods
10
+ def queries(*args, &block)
11
+ options = args.extract_options!
12
+ name = args.shift || self.class.name.underscore.gsub("_controller", "").singularize
13
+ self.query_controller = ::Deli::Controller.new(name, options, &block)
14
+
15
+ self.send :before_filter, :deli, :only => :index
16
+ end
17
+ end
18
+
19
+ module InstanceMethods
20
+ def deli_controller
21
+ @deli_controller ||= self.is_a?(::ActionController::Base) ? self.class.query_controller : self.controller.class.query_controller
22
+ end
23
+
24
+ # The query hash you want to use to paginate.
25
+ def deli
26
+ @deli ||= deli_controller.render(query_params) if deli_controller.present?
27
+ @deli
28
+ end
29
+
30
+ # Simple method to give you the query parameters we're interested in.
31
+ def query_params
32
+ @query_params ||= ::Deli::Query.parse_query(request.query_string)
33
+ end
34
+
35
+ # with_params(admin_user_membership_path(@user, @membership), :sort => "name")
36
+ # merges url with existing params
37
+ def with_params(path, new_params = {})
38
+ params = query_params.merge(new_params.stringify_keys)
39
+ return path if params.blank?
40
+ query_string = ::Deli::Query.build_query(params)
41
+ "#{path}?#{query_string}"
42
+ end
43
+
44
+ def param_operators(key)
45
+ deli_controller.find(key).operators
46
+ end
47
+
48
+ def deli_keys
49
+ deli_controller.keys
50
+ end
51
+
52
+ def queryable_keys
53
+ deli_controller.keys - [:sort, :page, :limit]
54
+ end
55
+
56
+ def queryable_options
57
+ queryable_keys.map {|i| [i.titleize, i.to_s]}
58
+ end
59
+ end
60
+ end
61
+ end
data/lib/deli/model.rb ADDED
@@ -0,0 +1,13 @@
1
+ module Deli
2
+ class Model
3
+ attr_reader :name
4
+
5
+ def initialize(name)
6
+ @name = name
7
+ end
8
+
9
+ def model_class
10
+ @model_class ||= name.to_s.camelize.constantize
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,117 @@
1
+ module Deli
2
+ module Pagination
3
+ def paginate(options)
4
+ options = options.dup
5
+ options[:page] = ((options[:offset] || 0) / options[:limit]).ceil if !options[:page] && options[:limit]
6
+ options[:per_page] ||= options[:limit]
7
+
8
+ pagenum = options.fetch(:page) { raise ArgumentError, ":page parameter required" }
9
+ per_page = options.delete(:per_page) || self.per_page
10
+ total = options.delete(:total_entries)
11
+
12
+ count_options = options.delete(:count)
13
+ options.delete(:page)
14
+
15
+ rel = limit(per_page.to_i).page(pagenum)
16
+
17
+ rel = rel.apply_finder_options(options) if options.any?
18
+ rel.total_entries = total.to_i unless total.blank?
19
+ rel
20
+ end
21
+
22
+ def page(num)
23
+ rel = scoped.extending(::Deli::Pagination::Collection)
24
+ pagenum = [num.nil? ? 1 : num, 1].max
25
+ per_page = rel.limit_value || ::Deli.configuration.per_page
26
+ rel = rel.offset((pagenum.to_i - 1) * per_page.to_i)
27
+ rel = rel.limit(per_page) unless rel.limit_value
28
+ rel
29
+ end
30
+
31
+ module Collection
32
+ def total_count
33
+ @total_count ||= proxy_scope.count(proxy_options.except(:limit, :offset, :order))
34
+ end
35
+
36
+ def has_next?
37
+ last_page > current_page
38
+ end
39
+
40
+ def has_prev?
41
+ current_page > first_page
42
+ end
43
+
44
+ def start_count
45
+ @start_count ||= (((current_page || 1) - 1) * page_size)
46
+ end
47
+
48
+ def end_count
49
+ @end_count ||= start_count + size
50
+ end
51
+
52
+ def current_page
53
+ unless @current_page
54
+ if offset_value.blank? || offset_value.zero?
55
+ @current_page = 1
56
+ else
57
+ @current_page = (offset_value.to_f / limit_value).to_i + 1
58
+ end
59
+ end
60
+
61
+ @current_page
62
+ end
63
+
64
+ def next_page
65
+ unless @next_page
66
+ @next_page = current_page + 1 > last_page ? last_page : current_page + 1
67
+ end
68
+
69
+ @next_page
70
+ end
71
+
72
+ def prev_page
73
+ unless @prev_page
74
+ @prev_page = current_page - 1 < first_page ? first_page : current_page - 1
75
+ end
76
+
77
+ @prev_page
78
+ end
79
+
80
+ def first_page
81
+ 1
82
+ end
83
+
84
+ def last_page
85
+ page_count
86
+ end
87
+
88
+ def page_size
89
+ @page_size ||= limit_value
90
+ end
91
+
92
+ def page_count
93
+ @page_count ||= (total_count.to_f / page_size).ceil
94
+ end
95
+
96
+ # a workaround for AR 3.0.x that returns 0 for #count when page > 1
97
+ # if +limit_value+ is specified, load all the records and count them
98
+ if defined?(::ActiveRecord) && ::ActiveRecord::VERSION::STRING < '3.1'
99
+ def count #:nodoc:
100
+ limit_value ? length : super
101
+ end
102
+ end
103
+
104
+ def total_count #:nodoc:
105
+ # #count overrides the #select which could include generated columns referenced in #order, so skip #order here, where it's irrelevant to the result anyway
106
+ c = except(:offset, :limit, :order)
107
+ # a workaround for 3.1.beta1 bug. see: https://github.com/rails/rails/issues/406
108
+ c = c.reorder nil
109
+ # Remove includes only if they are irrelevant
110
+ c = c.except(:includes) unless references_eager_loaded_tables?
111
+ # .group returns an OrderdHash that responds to #count
112
+ c = c.count
113
+ c.respond_to?(:count) ? c.count : c
114
+ end
115
+ end
116
+ end
117
+ end
data/lib/deli/param.rb ADDED
@@ -0,0 +1,189 @@
1
+ module Deli
2
+ class Param
3
+ module Time
4
+ def parse(value, as = :time)
5
+ values = []
6
+
7
+ value.to_s.split(/[\s,\+]/).each do |string|
8
+ if string =~ /([^\.]+)?(\.\.)([^\.]+)?/
9
+ starts_on, operator, ends_on = $1, $2, $3
10
+ range = []
11
+ range << parse_value(starts_on, [">="]) if !!(starts_on.present? && starts_on =~ /^\d/)
12
+ range << parse_value(ends_on, ["<="]) if !!(ends_on.present? && ends_on =~ /^\d/)
13
+ values << range
14
+ else
15
+ values << [parse_value(string, ["="])]
16
+ end
17
+ end
18
+
19
+ values
20
+ end
21
+
22
+ def parse_value(value, operators)
23
+ super(::Time.zone.parse(value), operators)
24
+ end
25
+ end
26
+
27
+ module Date
28
+ include Time
29
+
30
+ def parse(value)
31
+ super(value, :date)
32
+ end
33
+ end
34
+
35
+ module String
36
+ def parse(value)
37
+ arrays = value.split(/(?:[\s|\+]OR[\s|\+]|\||,)/).map do |node|
38
+ values = []
39
+
40
+ # ([\+\-\^]?[\w@\-_\s\d\.\$]+|-?\'[\w@-_\s\d\+\.\$]+\')
41
+ node.scan(/([\+\-\^]?[\w@_\s\d\.\$]+|-?\'[\w@-_\s\d\+\.\$]+\')/).flatten.each do |token|
42
+ token.gsub!(/^\+?-+/, "")
43
+ negation = $& && $&.length > 0
44
+ token.gsub!(/^\'(.+)\'$/, "\\1")
45
+ exact = $& && $&.length > 0
46
+
47
+ if negation
48
+ operators = [exact ? "!=" : "!~"]
49
+ else
50
+ operators = [exact ? "=" : "=~"]
51
+ end
52
+
53
+ operators << "^" if token =~ /^\+?\-?\^/
54
+ operators << "$" if token =~ /\$$/
55
+
56
+ values << parse_value(clean(token), operators)
57
+ end
58
+
59
+ values
60
+ end
61
+ end
62
+ end
63
+
64
+ module Number
65
+ def parse(value)
66
+ values = []
67
+
68
+ value.to_s.split(/[,\|]/).each do |string|
69
+ if string =~ /([^\.]+)?(\.{2})([^\.]+)?/
70
+ starts_on, operator, ends_on = $1, $2, $3
71
+ range = []
72
+ range << parse_value(starts_on, [">="]) if starts_on.present? && starts_on =~ /^\d/
73
+ range << parse_value(ends_on, ["<="]) if ends_on.present? && ends_on =~ /^\d/
74
+ values << range
75
+ else
76
+ values << [parse_value(string, ["="])]
77
+ end
78
+ end
79
+
80
+ values
81
+ end
82
+
83
+ def parse_value(value, operators)
84
+ super(value.to_i, operators) # or to_f ?
85
+ end
86
+ end
87
+
88
+ module Limit
89
+ def parse(value)
90
+ result = value.to_s.scan(/(\d+)/).flatten[0]
91
+ result.present? ? result.to_i : self.default
92
+ end
93
+
94
+ def render(value)
95
+ parse(value)
96
+ end
97
+ end
98
+
99
+ module Order
100
+ def parse(value)
101
+ value.split(",").map do |string|
102
+ string.scan(/([\w-]+[^\-\+])([\+\-])?/).map do |token, operator|
103
+ operator = operator == "-" ? "-" : "+"
104
+ token = clean(token)
105
+
106
+ if controller.present?
107
+ param = controller.find(token)
108
+ token = param.table_key
109
+ end
110
+
111
+ {:namespace => namespace, :key => token, :operators => [operator]}
112
+ end
113
+ end.flatten
114
+ end
115
+
116
+ def order_hash(value)
117
+ value.split(",").inject(ActiveSupport::OrderedHash.new) do |hash, string|
118
+ string.scan(/([\w-]+[^\-\+])([\+\-])?/).each do |token, operator|
119
+ hash[clean(token)] = operator == "-" ? "-" : "+"
120
+ end
121
+ hash
122
+ end
123
+ end
124
+ end
125
+
126
+ module Offset
127
+ def parse(value)
128
+ result = value.to_s.scan(/(\d+)/).flatten[0]
129
+ result.present? ? result.to_i : self.default
130
+ end
131
+
132
+ def render(value)
133
+ parse(value)
134
+ end
135
+ end
136
+
137
+ # Todo
138
+ module Geo
139
+ def parse(value)
140
+ value.to_s.split(/,\s*/).map(&:to_f) # [41.31419, -88.1847]
141
+ end
142
+
143
+ def render(value)
144
+ parse(value)
145
+ end
146
+ end
147
+
148
+ attr_accessor :controller, :key, :model_name, :namespace, :exact, :default
149
+
150
+ def initialize(key, options = {})
151
+ self.controller = options[:controller]
152
+ self.key = key.to_s
153
+ self.model_name = options[:model_name]
154
+ self.namespace = self.model_name.to_s.pluralize.to_sym if model_name.present?
155
+ self.exact = options[:exact] || false
156
+ self.default = options[:default]
157
+ end
158
+
159
+ def parse(value)
160
+ value
161
+ end
162
+
163
+ def render(value)
164
+ value
165
+ end
166
+
167
+ def parse_value(value, operators)
168
+ {
169
+ :namespace => namespace,
170
+ :key => key,
171
+ :operators => operators,
172
+ :value => value
173
+ }
174
+ end
175
+
176
+ def inspect
177
+ "#<#{self.class.name} @key=#{key.inspect} @model_name=#{model_name.inspect}>"
178
+ end
179
+
180
+ protected
181
+ def clean(string)
182
+ string.gsub(/^-/, "").gsub(/^\+-/, "").gsub(/^'|'$/, "").gsub("+", " ").gsub(/^\^/, "").gsub(/\$$/, "").strip
183
+ end
184
+
185
+ def model_class
186
+ @model_class ||= model_name.to_s.camelize.constantize
187
+ end
188
+ end
189
+ end