hario 0.2.0 → 0.3.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: 879fa179f9fab01c486396fc9afd19c19b98c674
4
- data.tar.gz: 180dd6a66f7b4fd8a63403a70a955a8f9a5da1da
3
+ metadata.gz: 51eaa13608caa623aa57c2e1c2b275aab5419e0a
4
+ data.tar.gz: 0bdab870d96a09ccdaf3357f0c45107dc366a50e
5
5
  SHA512:
6
- metadata.gz: 4dd7b5574c72a7157316dd8a59f5f8f809c8c12d896a563627eddf8eed6e76fc8feafb7766edd45137a7b8a468dd314649592ef2eedb3d369cfbdd41a26cbad6
7
- data.tar.gz: e972fde3bfb04f2b42e7ff87d2958ac4d6d442da443c61aaa7907d4452712bb748a8efe0116c6583e15f4de8f266d58c59fda32f82a913c1ec3b951f63178f1d
6
+ metadata.gz: 2b0236ad432571151b232b5d2f869cc46048a60bbe058ee550347fa7940664728952c1a84f5f413bbc166aacd15eae613af47528d19645c8e451577fca862bb3
7
+ data.tar.gz: 7c598b57e7310d36620783a0bcd3dd17878e79a4dbe0b65221495fd2e7f52c221f7ea7ba23e5188f560483944af7fbc06d99150fe29c3e663326d2910188f43b
data/README.md CHANGED
@@ -20,7 +20,7 @@ Or install it yourself as:
20
20
 
21
21
  ### Setup
22
22
 
23
- Add `include Hario::Filterable` to your AR model to add the `search` method, for instance (we'll use these classes as examples throughout):
23
+ Add `extend Hario::Filterable` to your AR model to add the `search` method, for instance (we'll use these classes as examples throughout):
24
24
 
25
25
  ```ruby
26
26
  def Brand < ActiveRecord::Base
@@ -46,7 +46,9 @@ class BrandsController < ApplicationController
46
46
  respond_to :json
47
47
 
48
48
  def index
49
- @brands = Brand.search(params[:filters])
49
+ @brands = Brand.search(params[:filters], params[:pluck])
50
+ # or to only use the filterable functionality
51
+ # @brands = Brand.search(params[:filters])
50
52
 
51
53
  respond_with(@brands)
52
54
  end
@@ -79,9 +81,23 @@ The available operators are:
79
81
  - like (sql like)
80
82
  - equals
81
83
 
82
- ## Todo
84
+ ### Pluck
83
85
 
84
- - Migrate our application-specific tests across to the gem
86
+ Oftentimes you might only need a particular attribute, or a few attributes, from the resource, in which case getting the whole of the resources would be inefficient.
87
+
88
+ This is where pluck comes in handy, it allows you to specify which attributes you're interested in in the request, reducing the amount of data that has to be retrieved from the database, and more importantly, sent over the wire:
89
+
90
+ ```ruby
91
+ @brands = Brand.search(nil, ["name"])
92
+ # will return an array of hashes containing only the primary key and the name attribute for each brand.
93
+ ```
94
+
95
+ Like filters, you can also pluck associated columns from other models using the dot notation, e.g.:
96
+
97
+ ```ruby
98
+ # assuming Country belongs_to :continent
99
+ @countries = Country.search(nil, ["name", "continent.name"])
100
+ ```
85
101
 
86
102
  ## Tests
87
103
 
data/hario.gemspec CHANGED
@@ -17,10 +17,10 @@ Gem::Specification.new do |spec|
17
17
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
18
18
  spec.require_paths = ["lib"]
19
19
 
20
- spec.add_runtime_dependency "activerecord", "~> 4.0"
20
+ spec.add_runtime_dependency "activerecord", ">= 4.0"
21
21
 
22
22
  spec.add_development_dependency "bundler", "~> 1.6"
23
- spec.add_development_dependency "rake", "~> 0"
23
+ spec.add_development_dependency "rake"
24
24
  spec.add_development_dependency "sqlite3", "~> 1.3.5"
25
- spec.add_development_dependency "database_rewinder", "~> 0"
25
+ spec.add_development_dependency "database_rewinder"
26
26
  end
@@ -1,57 +1,72 @@
1
1
  require "hario/behaviours/utils"
2
2
 
3
- class FilterParser
4
- include ParserUtils
3
+ module Hario
4
+ class FilterParser
5
+ include ParserUtils
5
6
 
6
- OPERATORS = { lt: '<', gt: '>', lte: '<=', gte: '>=', like: 'like', equals: '=' }
7
+ OPERATORS = { lt: '<', gt: '>', lte: '<=', gte: '>=', like: 'like', equals: '=' }
7
8
 
8
- attr_accessor :join_clause, :where_clauses
9
+ attr_accessor :join_clause, :where_clauses
9
10
 
10
- def initialize(filters, klass)
11
- @filters = filters
12
- @klass = klass
11
+ def initialize(filters, klass)
12
+ @filters = filters
13
+ @klass = klass
13
14
 
14
- parse_filters
15
- end
15
+ parse_filters
16
+ end
16
17
 
17
- private
18
- def parse_filters
19
- @join_clause, @where_clauses = @filters.inject([{}, []]) do |m, (descriptor, value)|
20
- association_chain, attribute, operator = parse_descriptor(descriptor)
21
- condition = build_condition(association_chain, attribute, operator, value)
18
+ InvalidAttributeError = Class.new(RuntimeError)
22
19
 
23
- nested_associations = (association_chain.dup << {}).reverse.inject { |v, key| { key => v } }
24
- joins = m[0].deep_merge(nested_associations)
25
- wheres = m[1] + [condition]
26
- [joins, wheres]
27
- end
28
- end
20
+ private
21
+ def parse_filters
22
+ @join_clause, @where_clauses = @filters.inject([{}, []]) do |m, (descriptor, value)|
23
+ association_chain, attribute, operator = parse_descriptor(descriptor)
24
+ condition = build_condition(association_chain, attribute, operator, value)
29
25
 
30
- def parse_descriptor(descriptor)
31
- parts = descriptor.split('.')
32
- operator = parts.pop.to_sym
33
- attribute = parts.pop
34
- association_chain = parts
26
+ nested_associations = (association_chain.dup << {}).reverse.inject { |v, key| { key => v } }
27
+ joins = m[0].deep_merge(nested_associations)
28
+ wheres = m[1] + [condition]
29
+ [joins, wheres]
30
+ end
31
+ end
35
32
 
36
- [association_chain, attribute, operator]
37
- end
33
+ def parse_descriptor(descriptor)
34
+ parts = descriptor.split('.')
35
+ operator = parts.pop.to_sym
36
+ attribute = parts.pop
37
+ association_chain = parts
38
38
 
39
- def build_condition(association_chain, attribute, operator, value)
40
- if association_chain.any?
41
- attribute_table = table_name_from_association_chain(association_chain)
39
+ [association_chain, attribute, operator]
42
40
  end
43
41
 
44
- case operator
45
- when :equals
46
- condition = { attribute => value }
47
- condition = { attribute_table => condition } if attribute_table
48
- else
49
- value = "%#{value}%" if operator == :like
50
- operator = OPERATORS[operator]
51
- condition = ["? #{operator} ?", attribute, value]
52
- condition[1].prepend("#{attribute_table}.") if attribute_table
42
+ def build_condition(association_chain, attribute, operator, value)
43
+ if association_chain.any?
44
+ end_model = end_model_from_association_chain(association_chain)
45
+ attribute_table = end_model.table_name
46
+ else
47
+ end_model = @klass
48
+ end
49
+
50
+ raise_if_invalid_attribute!(attribute, end_model)
51
+
52
+ case operator
53
+ when :equals
54
+ condition = { attribute => value }
55
+ condition = { attribute_table => condition } if attribute_table
56
+ else
57
+ operator = OPERATORS[operator]
58
+ condition = ["#{attribute} #{operator} ?", value]
59
+ condition[0].prepend("#{attribute_table || @klass.table_name}.")
60
+ end
61
+
62
+ condition
53
63
  end
54
64
 
55
- condition
56
- end
65
+ def raise_if_invalid_attribute!(attribute, end_model)
66
+ unless end_model.column_names.include?(attribute)
67
+ raise InvalidAttributeError,
68
+ "'#{attribute}' is not a valid column name for '#{end_model.table_name}'"
69
+ end
70
+ end
71
+ end
57
72
  end
@@ -1,42 +1,44 @@
1
1
  require "hario/behaviours/utils"
2
2
 
3
- class PluckParser
4
- include ParserUtils
3
+ module Hario
4
+ class PluckParser
5
+ include ParserUtils
5
6
 
6
- attr_accessor :join_clause, :pluck_clause
7
+ attr_accessor :join_clause, :pluck_clause
7
8
 
8
- def initialize(pluck, klass)
9
- @pluck = pluck
10
- @klass = klass
9
+ def initialize(pluck, klass)
10
+ @pluck = pluck
11
+ @klass = klass
11
12
 
12
- parse_pluck
13
- end
13
+ parse_pluck
14
+ end
14
15
 
15
- private
16
- def parse_pluck
17
- @join_clause = {}
18
- @pluck_clause = [[@klass.table_name, 'id'].join('.')]
16
+ private
17
+ def parse_pluck
18
+ @join_clause = {}
19
+ @pluck_clause = [[@klass.table_name, 'id'].join('.')]
19
20
 
20
- ns, no_ns = @pluck.partition{ |p| p.include?('.') }
21
+ ns, no_ns = @pluck.partition{ |p| p.include?('.') }
21
22
 
22
- no_ns.each{ |p| @pluck_clause << [@klass.table_name, p].join('.') }
23
+ no_ns.each{ |p| @pluck_clause << [@klass.table_name, p].join('.') }
23
24
 
24
- ns.each do |p|
25
- association_chain, attribute = parse_namespace(p)
25
+ ns.each do |p|
26
+ association_chain, attribute = parse_namespace(p)
26
27
 
27
- nested_associations = (association_chain.dup << {}).reverse.inject { |v, key| { key => v } }
28
- @join_clause.deep_merge!(nested_associations)
28
+ nested_associations = (association_chain.dup << {}).reverse.inject { |v, key| { key => v } }
29
+ @join_clause.deep_merge!(nested_associations)
29
30
 
30
- attribute_table = table_name_from_association_chain(association_chain)
31
- @pluck_clause << [attribute_table, attribute].join('.')
31
+ attribute_table = table_name_from_association_chain(association_chain)
32
+ @pluck_clause << [attribute_table, attribute].join('.')
33
+ end
32
34
  end
33
- end
34
35
 
35
- def parse_namespace(namespace)
36
- parts = namespace.split('.')
37
- attribute = parts.pop
38
- association_chain = parts
39
-
40
- [association_chain, attribute]
41
- end
36
+ def parse_namespace(namespace)
37
+ parts = namespace.split('.')
38
+ attribute = parts.pop
39
+ association_chain = parts
40
+
41
+ [association_chain, attribute]
42
+ end
43
+ end
42
44
  end
@@ -1,11 +1,15 @@
1
1
  module ParserUtils
2
2
  def table_name_from_association_chain(association_chain)
3
+ end_model_from_association_chain(association_chain).table_name
4
+ end
5
+
6
+ def end_model_from_association_chain(association_chain)
3
7
  head = @klass
4
8
 
5
9
  association_chain.each do |a_name|
6
10
  head = head.reflect_on_all_associations.find{ |a| a.name.to_s == a_name }.klass
7
11
  end
8
12
 
9
- head.table_name
13
+ head
10
14
  end
11
15
  end
data/lib/hario/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Hario
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
data/test/filter_test.rb CHANGED
@@ -1,6 +1,12 @@
1
1
  require_relative 'test_helper'
2
+ require 'date'
2
3
 
3
4
  class FilterTest < Hario::Test
5
+ def test_test_data_loaded_properly
6
+ assert Brand.count > 0,
7
+ "Test data not loaded"
8
+ end
9
+
4
10
  def test_simple_filter
5
11
  filters = { 'name.equals' => "Adidas" }
6
12
  brands = Brand.search(filters)
@@ -27,4 +33,19 @@ class FilterTest < Hario::Test
27
33
  assert_equal 1, brands.count
28
34
  assert brand.name == "Adidas"
29
35
  end
36
+
37
+ def test_filter_with_date_condition
38
+ filters = { 'created_at.gt' => (DateTime.now - 5).iso8601 }
39
+ products = Product.search(filters)
40
+
41
+ assert_equal 2, products.count
42
+ end
43
+
44
+ def test_invalid_attribute_raises
45
+ filters = { 'foobar.equals' => "ehehehe" }
46
+
47
+ assert_raises Hario::FilterParser::InvalidAttributeError do
48
+ Product.search(filters)
49
+ end
50
+ end
30
51
  end
data/test/fixtures.rb CHANGED
@@ -1,3 +1,5 @@
1
+ require 'date'
2
+
1
3
  @adidas = Brand.create!(name: "Adidas")
2
4
  @shoe = ProductCategory.create!(name: "Shoe")
3
5
  @tee = ProductCategory.create!(name: "T-shirt")
@@ -5,7 +7,8 @@
5
7
  Product.create!(
6
8
  brand_id: @adidas.id,
7
9
  category_id: @shoe.id,
8
- name: "Gazelle OG"
10
+ name: "Gazelle OG",
11
+ created_at: DateTime.now - 10
9
12
  )
10
13
  Product.create!(
11
14
  brand_id: @adidas.id,
data/test/schema.rb CHANGED
@@ -1,12 +1,14 @@
1
1
  ActiveRecord::Schema.define(:version => 1) do
2
2
  create_table :brands, :force => true do |t|
3
3
  t.column :name, :string
4
+ t.timestamps null: false
4
5
  end
5
6
 
6
7
  create_table :products, :force => true do |t|
7
8
  t.column :name, :string
8
9
  t.column :category_id, :integer
9
10
  t.column :brand_id, :integer
11
+ t.timestamps null: false
10
12
  end
11
13
 
12
14
  create_table :product_categories, :force => true do |t|
data/test/test_helper.rb CHANGED
@@ -8,7 +8,9 @@ require "models"
8
8
 
9
9
  ActiveRecord::Base.configurations = YAML.load_file(File.join(File.dirname(__FILE__), 'database.yml'))
10
10
  ActiveRecord::Base.establish_connection(ENV['DB'] || :sqlite3)
11
- load(File.join(File.dirname(__FILE__), "schema.rb"))
11
+ ActiveRecord::Migration.suppress_messages do
12
+ load(File.join(File.dirname(__FILE__), "schema.rb"))
13
+ end
12
14
 
13
15
  ActiveRecord::Base.logger = Logger.new(File.join(File.dirname(__FILE__), "debug.log"))
14
16
 
metadata CHANGED
@@ -1,27 +1,27 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: hario
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mike Campbell
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2015-07-15 00:00:00.000000000 Z
11
+ date: 2016-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activerecord
15
15
  requirement: !ruby/object:Gem::Requirement
16
16
  requirements:
17
- - - "~>"
17
+ - - ">="
18
18
  - !ruby/object:Gem::Version
19
19
  version: '4.0'
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
- - - "~>"
24
+ - - ">="
25
25
  - !ruby/object:Gem::Version
26
26
  version: '4.0'
27
27
  - !ruby/object:Gem::Dependency
@@ -42,14 +42,14 @@ dependencies:
42
42
  name: rake
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
- - - "~>"
45
+ - - ">="
46
46
  - !ruby/object:Gem::Version
47
47
  version: '0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
- - - "~>"
52
+ - - ">="
53
53
  - !ruby/object:Gem::Version
54
54
  version: '0'
55
55
  - !ruby/object:Gem::Dependency
@@ -70,14 +70,14 @@ dependencies:
70
70
  name: database_rewinder
71
71
  requirement: !ruby/object:Gem::Requirement
72
72
  requirements:
73
- - - "~>"
73
+ - - ">="
74
74
  - !ruby/object:Gem::Version
75
75
  version: '0'
76
76
  type: :development
77
77
  prerelease: false
78
78
  version_requirements: !ruby/object:Gem::Requirement
79
79
  requirements:
80
- - - "~>"
80
+ - - ">="
81
81
  - !ruby/object:Gem::Version
82
82
  version: '0'
83
83
  description:
@@ -126,7 +126,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
126
126
  version: '0'
127
127
  requirements: []
128
128
  rubyforge_project:
129
- rubygems_version: 2.2.2
129
+ rubygems_version: 2.4.6
130
130
  signing_key:
131
131
  specification_version: 4
132
132
  summary: Hario provides ActiveRecord filtering for Rails APIs.