nexter 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9fe3377bb4621f5dd4334001c71bfc251c1aab7a
4
- data.tar.gz: b40d8a4a5dd7cfc932313cbceb18b538008e9bad
3
+ metadata.gz: bb5f5647cea17c363cbc0c3bdd0a0dbffaf60e4d
4
+ data.tar.gz: 05a68031d96af8db94742064b8f5efe6e831f3a3
5
5
  SHA512:
6
- metadata.gz: b19d7a48206c5276a18d6d6805c771d5d3f443047504d54b074a72ee1cfe9f77bf57743b5381f7a1738d276d72980f15f5df7af21168f7912edd13a8f33aa587
7
- data.tar.gz: 7214177fefacbade18b798b5058a4b01979199fc4837ecfe5858af830fa3012a59e6fc184eddd0d13b0ce52e304589cd211bb07c0a9f4fbe867c2fd3d97121fc
6
+ metadata.gz: 3a989deebd5e880d7710c5ca97adac906b554a184d0efd701305497d961975f2be5f632192aa23d3a28587d215c5abb96a4875b5364b5b8d5edeb666a51bfaa2
7
+ data.tar.gz: e87cbdb667b506e39116e63b6fe4c2dbf62525161471d9b92d6d2bda8ca2364a5def9230280531d1917c9c73df6bccc318cead3a06470dee575adb200e0463e1
@@ -1,7 +1,8 @@
1
1
  language: ruby
2
2
  rvm:
3
- - "1.9.3"
3
+ #- "1.9.3"
4
4
  - "2.0.0"
5
+ - "2.2.2"
5
6
  #- jruby-18mode # JRuby in 1.8 mode
6
7
  #- jruby-19mode # JRuby in 1.9 mode
7
8
  #- rbx
@@ -0,0 +1,18 @@
1
+ ## Nexter 0.2.0 (Mars 2016)
2
+
3
+ * properly do order parsing so it works with arel or plain string
4
+ * quote or not values of a column (e.g "title > 'sweet bean'" or "num < 18")
5
+ * some dirty hack on float values to make comparaison work
6
+
7
+ ## Nexter 0.1.0 (Dec 2015)
8
+
9
+ * All model values for building the query moved in wrap
10
+ * Query now only takes a hash of values to build itself
11
+ * Query relies on Section for the possible records
12
+ * Query uses Direction to look at the next bigger or smaller
13
+ * Nexter now **handles NULL** yey
14
+
15
+
16
+ ## Nexter 0.0.5
17
+
18
+ * sanitize SQL (wonky still needs work)
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  What is Nexter ? A misspelled tv show or a killer feature ? Not sure but it wraps your ActiveRecord model with an ordered scope and consistently cuts out the _next_ and _previous_ records. It also works with associations & nested columns : `Book.order("books.genre, authors.name, published_at desc")`
7
7
 
8
- ## Installation
8
+ ## Installation (RUBY 2)
9
9
 
10
10
  gem 'nexter'
11
11
  # (edge) gem 'nexter', git: 'https://github.com/charly/nexter'
@@ -25,7 +25,7 @@ nexter.next
25
25
 
26
26
  It helps you cycle consistentely through each record of any filtered collection instead of helplessly hit the back button of your browser to find the next item of your search. It plays well with gem which keeps the state of an `ActiveRelation` like [siphon](https://github.com/charly/siphon), [ransack](https://github.com/activerecord-hackery/ransack) & others.
27
27
 
28
- ### New way (bleeding edge)
28
+ ### New way
29
29
 
30
30
  With the new view helper `nexter` no need to inject previous/next in the ActiveRecord model.
31
31
  However there's an assumptions : the formobject responds to `result` and returns an activerelation (like ransack does)
@@ -93,7 +93,7 @@ end
93
93
  - (docs) How it works
94
94
  - (feature) Joins ?
95
95
  - (docs) previous/next through ctrl (not preloaded)
96
- - (fix) for nil values you need a reorder with default delimiter
96
+ x (fix) for nil values you need a reorder with default delimiter
97
97
 
98
98
  ## Contributing
99
99
 
@@ -5,6 +5,9 @@ require "active_support/all"
5
5
 
6
6
  require "nexter/version"
7
7
  require "nexter/wrap"
8
+ require "nexter/model"
9
+ require "nexter/model/parse_order"
10
+
8
11
  require "nexter/query"
9
12
  require "nexter/query/section"
10
13
  require "nexter/query/direction"
@@ -0,0 +1,32 @@
1
+ module Nexter
2
+ class Model
3
+ attr_reader :model
4
+ attr_reader :order_values, :associations
5
+
6
+ def initialize(model, relation)
7
+ @model = model
8
+ @order_values = ParseOrder.parse(relation.order_values)
9
+ end
10
+
11
+ def values
12
+ @values ||= @order_values.map do |column|
13
+ { col: column[0],
14
+ val: value_of(column[0]),
15
+ dir: column[1]
16
+ }
17
+ end
18
+ end
19
+
20
+ private
21
+ def value_of(cursor)
22
+ splits = cursor.split(".")
23
+ result = if splits.first == model.class.table_name || splits.size == 1
24
+ model.send(splits.last) if model.respond_to?(splits.last)
25
+ else
26
+ asso = model.class.reflections.keys.grep(/#{splits.first.singularize}/).first
27
+ asso = model.send(asso) and asso.send(splits.last)
28
+ end
29
+ end
30
+
31
+ end
32
+ end
@@ -0,0 +1,35 @@
1
+ class Nexter::Model
2
+ class ParseOrder
3
+ attr_reader :order_values
4
+
5
+ def self.parse(order_values)
6
+ new(order_values).parse
7
+ end
8
+
9
+ def initialize(order_values)
10
+ @order_values = order_values
11
+ end
12
+
13
+
14
+ def parse
15
+ order_values.flat_map do |value|
16
+ value.is_a?(String) ? parse_string(value) : parse_arel(value)
17
+ end
18
+ end
19
+
20
+ # helper to turn mixed order attributes to a consistant array of vals
21
+ def parse_string(string)
22
+ string.split(",").map(&:strip).map do |column|
23
+ splits = column.split(" ").map(&:strip).map(&:downcase)
24
+ splits << "asc" if splits.size == 1
25
+ splits
26
+ end
27
+ end
28
+
29
+ def parse_arel(arel)
30
+ ["#{arel.value.name}", "#{arel.direction}"]
31
+ end
32
+
33
+
34
+ end
35
+ end
@@ -5,12 +5,12 @@ module Nexter
5
5
  def initialize(columns, goto)
6
6
  @columns = columns
7
7
  @compass = Compass.new(goto)
8
- @wheres = []
9
8
  @reorders = []
10
9
  iterate
11
10
  end
12
11
 
13
12
  def iterate
13
+ @wheres = []
14
14
  columns = @columns.dup
15
15
 
16
16
  while column = columns.pop do
@@ -11,7 +11,7 @@ class Nexter::Query
11
11
 
12
12
  def slice
13
13
  if column[:val].present?
14
- delimited = "#{column[:col]} #{bracket} '#{column[:val]}'"
14
+ delimited = "#{column[:col]} #{bracket} #{quote(column[:val])}"
15
15
  delimited.concat(" OR #{column[:col]} IS NULL") if @compass.sign == 1
16
16
  "(#{delimited})"
17
17
  elsif @compass.sign == -1
@@ -20,6 +20,19 @@ class Nexter::Query
20
20
  end
21
21
  alias sql slice
22
22
 
23
+ private
24
+ def quote(value)
25
+ if value.is_a?(Float)
26
+ @compass.sign == 1 ? value + 0.0001 : value - 0.0001
27
+ elsif value.is_a?(Integer)
28
+ value
29
+ else #value.is_a?(String)
30
+ "'#{value}'"
31
+ end
32
+ end
33
+
34
+
35
+
23
36
  end
24
37
  end
25
38
 
@@ -22,7 +22,7 @@ class Nexter::Query
22
22
  def iterate
23
23
  @where ||= columns.map do |column|
24
24
  if column[:val]
25
- "#{column[:col]} = '#{column[:val]}'"
25
+ "#{column[:col]} = #{quote(column[:val])}"
26
26
  else
27
27
  "#{column[:col]} IS NULL"
28
28
  end
@@ -34,5 +34,19 @@ class Nexter::Query
34
34
  iterate.blank?
35
35
  end
36
36
 
37
+ private
38
+ def quote(value)
39
+ if value.is_a?(Integer)
40
+ value
41
+ # TODO: lookat numeric precision e.g.
42
+ # round(vat::numeric, 2) = 19.66;
43
+ elsif value.is_a?(Float)
44
+ value
45
+ else #value.is_a?(String)
46
+ "'#{value}'"
47
+ end
48
+ end
49
+
50
+
37
51
  end #section
38
52
  end
@@ -1,3 +1,3 @@
1
1
  module Nexter
2
- VERSION = "0.1.0"
2
+ VERSION = "0.2.0"
3
3
  end
@@ -1,65 +1,34 @@
1
1
  module Nexter
2
2
  class Wrap
3
-
4
3
  # the current model & the scope
5
4
  attr_reader :model, :relation
6
5
 
7
- # extracted values from the relation
8
- attr_reader :order_values, :associations
9
-
10
6
  def initialize(relation, model)
11
7
  @relation = relation
12
- @model = model
13
- @order_values = parse_order( relation.order_values )
14
- @associations = relation.includes_values
8
+ @model = Model.new(model, relation)
15
9
  end
16
10
 
17
11
  # TODO : let user determine which strategy to choose:
18
- # e.g: carousel or stay there
12
+ # e.g: carousel or stay on last
19
13
  def next
20
- after.first
14
+ @next||=after.first
21
15
  end
22
16
 
23
17
  def previous
24
- before.first
18
+ @prev||=before.first
25
19
  end
26
20
 
27
21
  def after
28
- query = Query.new(map_column_values, :next)
22
+ query = Query.new(model.values, :next)
29
23
  relation.where( query.wheres.join(' OR ') )
30
24
  end
31
25
 
32
26
  def before
33
- query = Query.new(map_column_values, :previous)
27
+ query = Query.new(model.values, :previous)
34
28
  relation.where( query.wheres.join(' OR ') ).
35
29
  reorder( query.reorders.join(", ") )
36
30
  end
37
31
 
38
- def map_column_values
39
- @column_values ||= @order_values.map do |column|
40
- {col: column[0], val: value_of(column[0]), dir: column[1]}
41
- end
42
- end
43
-
44
- private
45
- def value_of(cursor)
46
- splits = cursor.split(".")
47
- result = if splits.first == model.class.table_name || splits.size == 1
48
- model.send(splits.last) if model.respond_to?(splits.last)
49
- else
50
- asso = model.class.reflections.keys.grep(/#{splits.first.singularize}/).first
51
- asso = model.send(asso) and asso.send(splits.last)
52
- end
53
- end
54
-
55
- # helper to turn mixed order attributes to a consistant
56
- def parse_order(array)
57
- array.join(",").split(",").map(&:strip).map do |column|
58
- splits = column.split(" ").map(&:strip).map(&:downcase)
59
- splits << "asc" if splits.size == 1
60
- splits
61
- end
62
- end
63
32
 
64
33
  end
65
34
  end
@@ -1,6 +1,6 @@
1
1
  require "spec_helper"
2
2
 
3
- describe Nexter::Compass, focus: true do
3
+ describe Nexter::Compass do
4
4
 
5
5
  describe "#arrwo or #bracket" do
6
6
  context "when looking for *next*" do
@@ -0,0 +1,38 @@
1
+ require "spec_helper"
2
+
3
+ describe Nexter::Model::ParseOrder do
4
+
5
+ context "String with MANY order values" do
6
+ let(:parser) {Nexter::Model::ParseOrder.new ["authors.name ASC, title DESC"]}
7
+
8
+ describe "#parse" do
9
+ it "should create an array of ALL order values" do
10
+ expect(parser.parse).to eq([["authors.name", "asc"], ["title", "desc"]])
11
+ end
12
+ end
13
+ end
14
+
15
+ context "String with ONE order value" do
16
+ let(:parser) {Nexter::Model::ParseOrder.new ["authors.name"]}
17
+
18
+ describe "#parse" do
19
+ it "should create an array of ONE order values" do
20
+ expect(parser.parse).to eq([["authors.name", "asc"]])
21
+ end
22
+ end
23
+ end
24
+
25
+
26
+
27
+
28
+ # describe "#parse" do
29
+ # let(:relation) {
30
+ # Relation.new.tap{|r| r.order_values=["authors.name ASC, title DESC"] }}
31
+
32
+ # it "should create hashes of attributes out of the order values" do
33
+ # model = Nexter::Model.new(book, relation)
34
+ # expect(model.values).to eq("fvhjk")
35
+ # expect(model.order_values).to eq("fvhjk")
36
+ # end
37
+ # end
38
+ end
@@ -0,0 +1,22 @@
1
+ require "spec_helper"
2
+
3
+ describe Nexter::Model do
4
+ let(:relation) {
5
+ Relation.new.tap{|r| r.order_values=["authors.name","genre","title"]}}
6
+
7
+ let(:book) { Book.new("novel", "nabokov", "ada") }
8
+
9
+ describe "#values" do
10
+ it "should create hashes of attributes out of the order values" do
11
+ model = Nexter::Model.new(book, relation)
12
+
13
+ expect(model.values).to eq([
14
+ {col: "authors.name", val: "nabokov", dir: "asc"},
15
+ {col: "genre", val: "novel", dir: "asc"},
16
+ {col: "title", val: "ada", dir: "asc"}
17
+ ])
18
+ end
19
+ end
20
+
21
+
22
+ end
@@ -9,7 +9,7 @@ describe Nexter::Query::Direction do
9
9
  {col: "title", val: "ada", dir: "asc"}
10
10
  ]}
11
11
 
12
- describe "#slice", focus: true do
12
+ describe "#slice" do
13
13
 
14
14
  it "should be awesome" do
15
15
  direction = Nexter::Query::Direction.new(columns.pop, compass)
@@ -1,6 +1,6 @@
1
1
  require "spec_helper"
2
2
 
3
- describe Nexter::Query::Section, focus: true do
3
+ describe Nexter::Query::Section do
4
4
 
5
5
  # let(:compass) {Nexter::Compass.new(:next)}
6
6
  let(:columns) {[
@@ -1,6 +1,6 @@
1
1
  require "spec_helper"
2
2
 
3
- describe Nexter::Query, broken: true do
3
+ describe Nexter::Query do
4
4
 
5
5
  let(:columns) {[
6
6
  {col: "authors.name", val: "nabokov", dir: "asc"},
@@ -8,16 +8,60 @@ describe Nexter::Query, broken: true do
8
8
  {col: "title", val: "ada", dir: "asc"}
9
9
  ]}
10
10
 
11
- describe "#where" do
11
+ context "on a next query" do
12
+ let(:query) { Nexter::Query.new(columns, :next) }
12
13
 
13
- it "builds the query from its ashes" do
14
+ describe "#where" do
15
+ it "builds the query from its ashes" do
16
+ expect(query.wheres).to include(
17
+ "authors.name = 'nabokov' AND (genre > 'novel' OR genre IS NULL)")
18
+ end
19
+ end
20
+
21
+ describe "#reloads" do
22
+ it "keeps the original order" do
23
+ expect(query.reorders).to eq([
24
+ "authors.name ASC", "genre ASC", "title ASC"])
25
+ end
26
+ end
27
+ end
28
+
29
+ context "on a previous query" do
30
+ let(:query) { Nexter::Query.new(columns, :previous) }
31
+
32
+ describe "#where" do
33
+ it "builds the query from its ashes" do
34
+ expect(query.wheres.size).to eq(3)
35
+ expect(query.wheres).to include(
36
+ "authors.name = 'nabokov' AND genre = 'novel' AND (title < 'ada')")
37
+ end
38
+ end
39
+
40
+ describe "#reloads" do
41
+ it "reverses the original order" do
42
+ expect(query.reorders).to eq([
43
+ "authors.name DESC", "genre DESC", "title DESC"])
44
+ end
45
+ end
46
+ end
47
+
48
+ context "when some value (title) is null (AND nulls come last!)" do
49
+
50
+ it "it skips **next** null (not possible) and moves to higher section" do
51
+ columns.last.merge!({val: nil})
14
52
  query = Nexter::Query.new(columns, :next)
15
53
 
16
- expect(query.wheres).to eq(
17
- [ "authors.name = 'nabokov' AND genre = 'novel' AND title > 'ada'",
18
- "authors.name = 'nabokov' AND genre > 'novel'",
19
- "authors.name > 'nabokov'"])
54
+ expect(query.wheres.size).to eq(2)
55
+ end
56
+
57
+ it "it looks for **previous** non null value of title" do
58
+ columns.last.merge!({val: nil})
59
+ query = Nexter::Query.new(columns, :previous)
60
+
61
+ expect(query.wheres[0]).to eq(
62
+ "authors.name = 'nabokov' AND genre = 'novel' AND title IS NOT NULL")
20
63
  end
21
64
  end
22
65
 
66
+
23
67
  end
@@ -4,102 +4,14 @@ describe Nexter::Wrap do
4
4
  let(:relation) {Relation.new.tap{|r| r.order_values=["authors.name","genre","title"]}}
5
5
  let(:book) {Book.new("novel", "nabokov", "ada")}
6
6
 
7
- describe "#map_column_values", focus: true do
7
+ describe "#after" do
8
8
 
9
- it "should create hashes of attributes out of the order values" do
9
+ it "should create a realtion" do
10
+ skip
10
11
  nexter = Nexter::Wrap.new(relation, book)
11
12
 
12
- expect(nexter.map_column_values).to eq(
13
- [ {col: "authors.name", val: "nabokov", dir: "asc"},
14
- {col: "genre", val: "novel", dir: "asc"},
15
- {col: "title", val: "ada", dir: "asc"}
16
- ])
13
+ expect(nexter.after).to eq("dfgjk")
17
14
  end
18
15
  end
19
16
 
20
- context "no missing values", broken: true do
21
- let(:relation) { Relation.new.tap {|rel| rel.order_values=["authors.name", "title"]} }
22
- let(:book) { Book.new("novel", "nabokov", "ada") }
23
-
24
- describe "#wheres" do
25
- it "has the right SQL condition" do
26
- nexter = Nexter::Wrap.new(relation, book)
27
- nexter.after
28
-
29
- expect(nexter.wheres[0]).to eq("(authors.name = 'nabokov' AND title > 'ada')")
30
- end
31
- end
32
-
33
- describe "#reorders" do
34
- it "has the right SQL condition" do
35
- nexter = Nexter::Wrap.new(relation, book)
36
- nexter.before
37
-
38
- expect(nexter.wheres[0]).to eq("(authors.name = 'nabokov' AND title < 'ada')")
39
- end
40
-
41
- it "has the right SQL order by" do
42
- nexter = Nexter::Wrap.new(relation, book)
43
- nexter.before
44
-
45
- expect(nexter.reorders[0]).to eq(" authors.name DESC")
46
- end
47
- end
48
- end
49
-
50
- context "nil values", broken: true do
51
- let(:relation) {Relation.new.tap {|r| r.order_values=["genre", "title", "id"]} }
52
-
53
- describe "#wheres with Book#genre IS NULL" do
54
- let(:book) { Book.new(nil, "nabokov", "Ada") }
55
-
56
- it "has the right SQL condition" do
57
- skip
58
- nexter = Nexter::Wrap.new(relation, book)
59
- nexter.after
60
-
61
- expect(nexter.wheres).to eq("(genre IS NULL AND title > 'Ada')")
62
- end
63
- end
64
-
65
- describe "#wheres with Book#title IS NULL" do
66
- let(:book) { Book.new("novel", "nabokov", nil) }
67
-
68
- it "has the right SQL condition for NEXT" do
69
- nexter = Nexter::Wrap.new(relation, book)
70
- nexter.after
71
-
72
- expect(nexter.wheres[0]).to \
73
- eq("(genre = 'novel' AND title IS NULL AND id > '#{book.id}')")
74
-
75
- expect(nexter.wheres[1]).to \
76
- eq("( genre > 'novel')")
77
- end
78
-
79
-
80
- it "has the right SQL condition for PREVIOUS" do
81
- nexter = Nexter::Wrap.new(relation, book)
82
- nexter.before
83
-
84
- expect(nexter.wheres[0]).to \
85
- eq("(genre = 'novel' AND title IS NULL AND id < '#{book.id}')")
86
-
87
- expect(nexter.wheres[1]).to \
88
- eq("( genre > 'novel')")
89
- end
90
- end
91
-
92
- describe "#after with Book#title IS NULL" do
93
- let(:book) { Book.new("novel", "nabokov", nil) }
94
-
95
- it "has the right SQL condition" do
96
- nexter = Nexter::Wrap.new(relation, book)
97
- nexter.after
98
-
99
- expect(nexter.wheres[0]).to \
100
- eq("(genre = 'novel' AND title IS NULL AND id > '#{book.id}')")
101
- end
102
- end
103
- end #contextr
104
-
105
17
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: nexter
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charles Sistovaris
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-12-21 00:00:00.000000000 Z
11
+ date: 2016-03-07 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
@@ -108,6 +108,7 @@ files:
108
108
  - ".rspec"
109
109
  - ".ruby-version"
110
110
  - ".travis.yml"
111
+ - CHANGELOG.md
111
112
  - Gemfile
112
113
  - LICENSE.txt
113
114
  - README.md
@@ -115,6 +116,8 @@ files:
115
116
  - lib/nexter.rb
116
117
  - lib/nexter/compass.rb
117
118
  - lib/nexter/eyecontact.rb
119
+ - lib/nexter/model.rb
120
+ - lib/nexter/model/parse_order.rb
118
121
  - lib/nexter/query.rb
119
122
  - lib/nexter/query/direction.rb
120
123
  - lib/nexter/query/section.rb
@@ -123,6 +126,8 @@ files:
123
126
  - nexter.gemspec
124
127
  - spec/book.rb
125
128
  - spec/nexter/compass_spec.rb
129
+ - spec/nexter/model/parse_order_spec.rb
130
+ - spec/nexter/model_spec.rb
126
131
  - spec/nexter/query/direction_spec.rb
127
132
  - spec/nexter/query/section_spec.rb
128
133
  - spec/nexter/query_spec.rb
@@ -156,6 +161,8 @@ summary: Wrap your model with an ordered scope and cut out the _next_ and _previ
156
161
  test_files:
157
162
  - spec/book.rb
158
163
  - spec/nexter/compass_spec.rb
164
+ - spec/nexter/model/parse_order_spec.rb
165
+ - spec/nexter/model_spec.rb
159
166
  - spec/nexter/query/direction_spec.rb
160
167
  - spec/nexter/query/section_spec.rb
161
168
  - spec/nexter/query_spec.rb