yodatra 0.3.0 → 0.3.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: ed95b4a06145c24432bfb2bdaf1e2a5a4547e40a
4
- data.tar.gz: 05feecbc8562dcefd7eaccba2e8feb3392e8da3b
3
+ metadata.gz: 2f1a54f5054c7cd360df54de7fc0ef1a5bcdd361
4
+ data.tar.gz: 169102147cefde0172ba4194b60fe7db2c8b5a55
5
5
  SHA512:
6
- metadata.gz: 8ccc7aed599ecac8101a0e51685ff1c3bdfa495635810cba1e00732c5d5322e7e55c032840689bbb22f6f2c5c307e2a2bc402e296a3d0fe082a574da124dbd54
7
- data.tar.gz: 645dbd4386668dcb74ff5abdb09444d6e9faebfe403ae02a51233e86caeacdec4395379d7ee1a208634ad12f13de24bb14ac0e7bf759e2673f28fcb160848c3f
6
+ metadata.gz: c4db8ae05cbc09f9f844d983f8729ef95e4c85c67149ec83e2a1e5aea6e10112598532c6c5c88536cd37e2ac432c16d58f19ac067a9590ded6b1add8265c44fc
7
+ data.tar.gz: 6c1946756ba824933066c0babe246d220e602ad914d0f7abbfc09b10ecc5818f9676892d4679d2acf04cd29bb654333de724a14ee090f398ac89c5ad6a074609
data/Rakefile CHANGED
@@ -10,4 +10,4 @@ require 'coveralls/rake/task'
10
10
  Coveralls::RakeTask.new
11
11
  task :test_with_coveralls => [:spec, :features, 'coveralls:push']
12
12
 
13
- task :default => :spec
13
+ task :default => :spec
@@ -23,6 +23,12 @@ module Yodatra
23
23
  # If your model is referenced by another model, nested routes are also created for you. And you don't need to worry about the references/joins, they are done automaticly!
24
24
  # For example, imagine a <b>Team</b> model that has many <b>User</b>s, the following routes will be exposed:
25
25
  # GET /team/:team_id/users, GET /team/:team_id/users/:id, POST /team/:team_id/users, PUT /team/:team_id/users/:id and DESTROY /team/:team_id/users/:id
26
+ #
27
+ # _Note_: You can disable any of these five actions by using the `#disable` class method
28
+ # and giving in parameters the list of actions you want to disable
29
+ # e.g. `disable :read, :read_all, :create, :update, :delete`
30
+ #
31
+ # _Note2_: You can enable a special "search" action by using the `#enable_search_on` class method
26
32
  class ModelsController < Sinatra::Base
27
33
 
28
34
  before do
@@ -37,6 +43,10 @@ module Yodatra
37
43
  ALL_ROUTE =
38
44
  %r{\A/([\w]+?)(?:/([0-9]+)/([\w]+?)){0,1}\Z}
39
45
 
46
+ # Search route
47
+ SEARCH_ROUTE =
48
+ %r{\A/([\w]+?)(?:/([0-9]+)/([\w]+?)){0,1}/search\Z}
49
+
40
50
  READ_ALL = :read_all
41
51
  get ALL_ROUTE do
42
52
  retrieve_resources READ_ALL do |resource|
@@ -118,6 +128,10 @@ module Yodatra
118
128
  self.name.split('::').last.gsub(/sController/, '')
119
129
  end
120
130
 
131
+ def model
132
+ model_name.constantize
133
+ end
134
+
121
135
  # This helper gives the ability to disable default root by specifying
122
136
  # a list of routes to disable.
123
137
  # @param *opts list of routes to disable (e.g. :create, :destroy)
@@ -128,6 +142,30 @@ module Yodatra
128
142
  define_method method, Proc.new {|| true}
129
143
  end
130
144
  end
145
+
146
+ def enable_search_on(*attributes)
147
+ self.instance_eval do
148
+ get SEARCH_ROUTE do
149
+ retrieve_resources '' do |resource|
150
+
151
+ pass if !involved? || params[:q].blank? || params[:q].size > 100
152
+
153
+ terms = params[:q].split(/[\+ ]/)
154
+ search_terms = []
155
+
156
+ # Seperate terms to match
157
+ terms.each do |term|
158
+ attributes.each do |attr|
159
+ search_terms << resource.arel_table[attr.to_sym].matches("%#{term}%")
160
+ end
161
+ end
162
+
163
+ resource.where(search_terms.reduce(:or)).limit(100).
164
+ flatten.as_json(read_scope).to_json
165
+ end
166
+ end
167
+ end
168
+ end
131
169
  end
132
170
 
133
171
  private
@@ -181,6 +219,10 @@ module Yodatra
181
219
  self.class.model_name
182
220
  end
183
221
 
222
+ def model
223
+ self.class.model
224
+ end
225
+
184
226
  def disabled? key
185
227
  method = ((nested? ? 'nested_' : '')+"#{key}_disabled?").to_sym
186
228
  self.class.method_defined?(method) && self.send(method)
@@ -1,3 +1,3 @@
1
1
  module Yodatra
2
- VERSION = '0.3.0'
2
+ VERSION = '0.3.1'
3
3
  end
@@ -0,0 +1,43 @@
1
+ require "active_record"
2
+
3
+ module ActiveRecord
4
+ class Base
5
+ def self.fake_connection(config)
6
+ ConnectionAdapters::FakeAdapter.new nil, logger
7
+ end
8
+ end
9
+
10
+ module ConnectionAdapters
11
+ class FakeAdapter < AbstractAdapter
12
+ attr_accessor :tables, :primary_keys
13
+
14
+ @columns = Hash.new { |h,k| h[k] = [] }
15
+ class << self
16
+ attr_reader :columns
17
+ end
18
+
19
+ def initialize(connection, logger)
20
+ super
21
+ @tables = []
22
+ @primary_keys = {}
23
+ @columns = self.class.columns
24
+ end
25
+
26
+ def primary_key(table)
27
+ @primary_keys[table]
28
+ end
29
+
30
+ def merge_column(table_name, name, sql_type = nil, options = {})
31
+ @columns[table_name] << ActiveRecord::ConnectionAdapters::Column.new(
32
+ name.to_s,
33
+ options[:default],
34
+ sql_type.to_s,
35
+ options[:null])
36
+ end
37
+
38
+ def columns(table_name, message)
39
+ @columns[table_name]
40
+ end
41
+ end
42
+ end
43
+ end
@@ -0,0 +1,7 @@
1
+ # Mock model constructed for the tests
2
+ class ArModel < ActiveRecord::Base
3
+ end
4
+
5
+ ActiveRecord::Base.establish_connection(:adapter => 'fake')
6
+ ActiveRecord::Base.connection.merge_column('ar_models', :email, :string)
7
+ ActiveRecord::Base.connection.merge_column('ar_models', :name, :string)
@@ -0,0 +1,2 @@
1
+ class ArModelsController < Yodatra::ModelsController
2
+ end
data/spec/data/model.rb CHANGED
@@ -47,4 +47,4 @@ class Model
47
47
  Model
48
48
  end
49
49
  def errors; []; end
50
- end
50
+ end
data/spec/spec_helper.rb CHANGED
@@ -8,6 +8,7 @@ require 'rspec'
8
8
 
9
9
  require File.expand_path '../../lib/yodatra.rb', __FILE__
10
10
  require File.expand_path '../../lib/yodatra/models_controller.rb', __FILE__
11
+ require File.expand_path '../data/ar_models_controller.rb', __FILE__
11
12
 
12
13
  module RSpecMixin
13
14
  include Rack::Test::Methods
@@ -15,6 +16,7 @@ module RSpecMixin
15
16
  Sinatra.new {
16
17
  use Yodatra::Base
17
18
  use Yodatra::ModelsController
19
+ use ArModelsController
18
20
  }
19
21
  end
20
22
  end
@@ -1,5 +1,7 @@
1
1
  require File.expand_path '../../spec_helper.rb', __FILE__
2
+ require File.expand_path '../../active_record/connection_adapters/fake_adapter.rb', __FILE__
2
3
  require File.expand_path '../../data/model.rb', __FILE__
4
+ require File.expand_path '../../data/ar_model.rb', __FILE__
3
5
 
4
6
  describe 'Model controller' do
5
7
 
@@ -13,7 +15,7 @@ describe 'Model controller' do
13
15
  it 'should have a GET all route' do
14
16
  get '/models'
15
17
 
16
- last_response.should be_ok
18
+ expect(last_response).to be_ok
17
19
  expect(last_response.body).to eq(Model::ALL.map{|e| {:data => e} }.to_json)
18
20
  end
19
21
  end
@@ -21,7 +23,7 @@ describe 'Model controller' do
21
23
  it 'should have a GET all route' do
22
24
  get '/models/1/models'
23
25
 
24
- last_response.should be_ok
26
+ expect(last_response).to be_ok
25
27
  expect(last_response.body).to eq(Model::ALL.map{|e| {:data => e} }.to_json)
26
28
  end
27
29
  end
@@ -34,7 +36,7 @@ describe 'Model controller' do
34
36
  it 'should fail with no route available' do
35
37
  get '/models'
36
38
 
37
- last_response.should_not be_ok
39
+ expect(last_response).to_not be_ok
38
40
  end
39
41
  end
40
42
  end
@@ -42,7 +44,7 @@ describe 'Model controller' do
42
44
  it 'should have a GET one route' do
43
45
  get '/models/2'
44
46
 
45
- last_response.should be_ok
47
+ expect(last_response).to be_ok
46
48
  expect(last_response.body).to eq({ :data => 'c'}.to_json)
47
49
  end
48
50
  context 'forced GET one route disabled' do
@@ -54,7 +56,7 @@ describe 'Model controller' do
54
56
  it 'should fail with no route available' do
55
57
  get '/models/1'
56
58
 
57
- last_response.should_not be_ok
59
+ expect(last_response).to_not be_ok
58
60
  end
59
61
  end
60
62
  end
@@ -65,7 +67,7 @@ describe 'Model controller' do
65
67
  post '/models', {:data => 'd'}
66
68
  }.to change(Model::ALL, :length).by(1)
67
69
 
68
- last_response.should be_ok
70
+ expect(last_response).to be_ok
69
71
  end
70
72
  end
71
73
  context 'with incorrect params' do
@@ -74,7 +76,7 @@ describe 'Model controller' do
74
76
  post '/models', {}
75
77
  }.to change(Model::ALL, :length).by(0)
76
78
 
77
- last_response.should_not be_ok
79
+ expect(last_response).to_not be_ok
78
80
  expect(last_response.body).to eq(@errors.to_json)
79
81
  end
80
82
  end
@@ -87,7 +89,7 @@ describe 'Model controller' do
87
89
  it 'should fail with no route available' do
88
90
  post '/models', {:data => 'd'}
89
91
 
90
- last_response.should_not be_ok
92
+ expect(last_response).to_not be_ok
91
93
  end
92
94
  end
93
95
  end
@@ -98,7 +100,7 @@ describe 'Model controller' do
98
100
  put '/models/21', {:data => 'e'}
99
101
  }.to change(Model::ALL, :length).by(0)
100
102
 
101
- last_response.should_not be_ok
103
+ expect(last_response).to_not be_ok
102
104
  expect(last_response.body).to eq(['record not found'].to_json)
103
105
  end
104
106
  end
@@ -109,7 +111,7 @@ describe 'Model controller' do
109
111
  put '/models/2', {:data => 'e'}
110
112
  }.to change(Model::ALL, :length).by(0)
111
113
 
112
- last_response.should be_ok
114
+ expect(last_response).to be_ok
113
115
  expect(last_response.body).to eq({ :data => 'e'}.to_json)
114
116
  expect(Model.find(2).to_json).to eq({ :data => 'e'}.to_json)
115
117
  end
@@ -120,7 +122,7 @@ describe 'Model controller' do
120
122
  put '/models/2', {:data => 321}
121
123
  }.to change(Model::ALL, :length).by(0)
122
124
 
123
- last_response.should_not be_ok
125
+ expect(last_response).to_not be_ok
124
126
  expect(last_response.body).to eq(@errors.to_json)
125
127
  end
126
128
  end
@@ -133,7 +135,7 @@ describe 'Model controller' do
133
135
  it 'should fail with no route available' do
134
136
  put '/models', {:data => 'd'}
135
137
 
136
- last_response.should_not be_ok
138
+ expect(last_response).to_not be_ok
137
139
  end
138
140
  end
139
141
  end
@@ -145,7 +147,7 @@ describe 'Model controller' do
145
147
  delete '/models/1'
146
148
  }.to change(Model::ALL, :length).by(-1)
147
149
 
148
- last_response.should be_ok
150
+ expect(last_response).to be_ok
149
151
  end
150
152
  end
151
153
  context 'targeting an existing instance but deletion fails' do
@@ -157,7 +159,7 @@ describe 'Model controller' do
157
159
  delete '/models/1/models/1'
158
160
  }.to change(Model::ALL, :length).by(0)
159
161
 
160
- last_response.should_not be_ok
162
+ expect(last_response).to_not be_ok
161
163
  expect(last_response.body).to eq(@errors.to_json)
162
164
  end
163
165
  end
@@ -167,7 +169,7 @@ describe 'Model controller' do
167
169
  delete '/models/6'
168
170
  }.to change(Model::ALL, :length).by(0)
169
171
 
170
- last_response.should_not be_ok
172
+ expect(last_response).to_not be_ok
171
173
  end
172
174
  end
173
175
  context 'when the deletion route is disabled' do
@@ -179,7 +181,7 @@ describe 'Model controller' do
179
181
  it 'should fail with no route available' do
180
182
  delete '/models/2'
181
183
 
182
- last_response.should_not be_ok
184
+ expect(last_response).to_not be_ok
183
185
  end
184
186
  end
185
187
  end
@@ -196,7 +198,7 @@ describe 'Model controller' do
196
198
  it 'fails with a record not found message' do
197
199
  get '/modeels/1/models'
198
200
 
199
- last_response.should_not be_ok
201
+ expect(last_response).to_not be_ok
200
202
  expect(last_response.body).to eq(['record not found'].to_json)
201
203
  end
202
204
  end
@@ -204,7 +206,7 @@ describe 'Model controller' do
204
206
  it 'fails with a record not found message' do
205
207
  get '/models/123/models'
206
208
 
207
- last_response.should_not be_ok
209
+ expect(last_response).to_not be_ok
208
210
  expect(last_response.body).to eq(['record not found'].to_json)
209
211
  end
210
212
  end
@@ -212,4 +214,41 @@ describe 'Model controller' do
212
214
 
213
215
  end
214
216
 
215
- end
217
+ describe 'Model search' do
218
+ describe 'when the search feature is not enabled (default)' do
219
+ it 'fails with no route found' do
220
+ get '/ar_models/search?q=john4'
221
+
222
+ expect(last_response).to_not be_ok
223
+ end
224
+ end
225
+
226
+ describe 'when the search is enabled of the models controller' do
227
+ before do
228
+ class ArModelsController
229
+ enable_search_on :email
230
+ end
231
+
232
+ @search_term = 'john4'
233
+
234
+ expect_arel_matches = [ArModel.arel_table[:email].matches("%#{@search_term}%")]
235
+ expect(ArModel).to receive(:where).with(expect_arel_matches.reduce(:or)) do |arg|
236
+ ActiveRecord::Relation.new ArModel, 'ar_models'
237
+ end
238
+ allow_any_instance_of(ActiveRecord::Relation).to receive(:limit) do |arg|
239
+ FakeModel = Struct.new(:name, :email)
240
+ @fake_instance = FakeModel.new('john', 'john4@swcc.com')
241
+ [FakeModel.new('john', 'john4@swcc.com')]
242
+ end
243
+ end
244
+
245
+ it 'should search in the list of available models' do
246
+ get "/ar_models/search?q=#{@search_term}"
247
+
248
+ expect(last_response).to be_ok
249
+ expect(last_response.body).to eq([@fake_instance.as_json({})].to_json)
250
+ end
251
+ end
252
+ end
253
+
254
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: yodatra
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.3.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paul Bonaud
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-05-25 00:00:00.000000000 Z
11
+ date: 2014-08-09 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: rack
@@ -129,6 +129,9 @@ files:
129
129
  - lib/yodatra/throttling.rb
130
130
  - lib/yodatra/utils.rb
131
131
  - lib/yodatra/version.rb
132
+ - spec/active_record/connection_adapters/fake_adapter.rb
133
+ - spec/data/ar_model.rb
134
+ - spec/data/ar_models_controller.rb
132
135
  - spec/data/model.rb
133
136
  - spec/spec_helper.rb
134
137
  - spec/unit/models_controller_spec.rb