deli 0.5.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.
@@ -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