brainstem 0.2.2 → 0.2.4

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.
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ + **0.2.4** - _01/9/2014_
4
+
5
+ + `Brainstem::ControllerMethods#present_object` now simulates an only request (by providing the `only` parameter to Brainstem) when attempting to present a single model.
6
+
7
+ + **0.2.3** - _11/21/2013_
8
+
9
+ + `Brainstem::ControllerMethods#present_object` now runs the default filters that are defined in the presenter.
10
+
11
+ + `Brainstem.presenter_collection` now takes two optional options:
12
+ + `raise_on_empty` - Boolean that defaults to false and when set to true will raise an exception (default: `ActiveRecord::RecordNotFound`) when the result set is empty.
13
+ + `empty_error_class` - Exception class to raise when `raise_on_empty` is true.
data/Gemfile.lock CHANGED
@@ -1,8 +1,8 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- brainstem (0.2.2)
5
- activerecord (~> 3.0)
4
+ brainstem (0.2.4)
5
+ activerecord (>= 3.0)
6
6
 
7
7
  GEM
8
8
  remote: https://rubygems.org/
data/README.md CHANGED
@@ -6,7 +6,7 @@ Brainstem is designed to power rich APIs in Rails. The Brainstem gem provides a
6
6
 
7
7
  ## Why Brainstem?
8
8
 
9
- * Seperate business and presentation logic with Presenters.
9
+ * Separate business and presentation logic with Presenters.
10
10
  * Version your Presenters for consistency as your API evolves.
11
11
  * Expose end-user selectable filters and sorts.
12
12
  * Whitelist your existing scopes to act as API filters for your users.
data/brainstem.gemspec CHANGED
@@ -17,7 +17,7 @@ Gem::Specification.new do |gem|
17
17
  gem.require_paths = ["lib"]
18
18
  gem.version = Brainstem::VERSION
19
19
 
20
- gem.add_dependency "activerecord", "~> 3.0"
20
+ gem.add_dependency "activerecord", ">= 3.0"
21
21
 
22
22
  gem.add_development_dependency "rake"
23
23
  gem.add_development_dependency "redcarpet" # for markdown in yard
@@ -6,10 +6,14 @@ module Brainstem
6
6
  # @return [String] The name of the method that is being proxied.
7
7
  attr_reader :method_name
8
8
 
9
- # @!attribute [r] json_name
9
+ # @!attribute [rw] json_name
10
10
  # @return [String] The name of the top-level JSON key for objects provided by this association.
11
11
  attr_accessor :json_name
12
12
 
13
+ # @!attribute [rw] restrict_to_only
14
+ # @return [Boolean] Option for this association to be restricted to only queries.
15
+ attr_accessor :restrict_to_only
16
+
13
17
  # @!attribute [r] block
14
18
  # @return [Proc] The block to be called when fetching models instead of calling a method on the model
15
19
  attr_reader :block
@@ -21,6 +25,7 @@ module Brainstem
21
25
  options = args.last.is_a?(Hash) ? args.pop : {}
22
26
  method_name = args.first.to_sym if args.first.is_a?(String) || args.first.is_a?(Symbol)
23
27
  @json_name = options[:json_name]
28
+ @restrict_to_only = options[:restrict_to_only] || false
24
29
  if block_given?
25
30
  raise ArgumentError, "options[:json_name] is required when using a block" unless options[:json_name]
26
31
  raise ArgumentError, "Method name is invalid with a block" if method_name
@@ -27,7 +27,8 @@ module Brainstem
27
27
  # only required if the name cannot be inferred.
28
28
  # @return (see PresenterCollection#presenting)
29
29
  def present_object(objects, options = {})
30
- options.reverse_merge(:params => params)
30
+ options.merge!(:params => params)
31
+
31
32
  if objects.is_a?(ActiveRecord::Relation) || objects.is_a?(Array)
32
33
  raise ActiveRecord::RecordNotFound if objects.empty?
33
34
  klass = objects.first.class
@@ -35,9 +36,11 @@ module Brainstem
35
36
  else
36
37
  klass = objects.class
37
38
  ids = objects.id
39
+ options[:params][:only] = ids.to_s
38
40
  end
39
- json_key = (options[:key_map] || {})[klass.to_s] || klass.table_name
40
- present(klass, :as => json_key, :apply_default_filters => false){ klass.where(:id => ids) }
41
+
42
+ options[:as] = (options[:key_map] || {})[klass.to_s] || klass.table_name
43
+ present(klass, options) { klass.where(:id => ids) }
41
44
  end
42
45
  alias_method :present_objects, :present_object
43
46
  end
@@ -47,7 +47,7 @@ module Brainstem
47
47
  # key these models will use in the struct that is output
48
48
  options[:as] = (options[:as] || name.to_s.tableize).to_sym
49
49
 
50
- allowed_includes = calculate_allowed_includes options[:presenter], presented_class
50
+ allowed_includes = calculate_allowed_includes options[:presenter], presented_class, options[:params][:only].present?
51
51
  includes_hash = filter_includes options[:params][:include], allowed_includes
52
52
 
53
53
  if searching? options
@@ -74,6 +74,12 @@ module Brainstem
74
74
 
75
75
  # Load Includes
76
76
  records = scope.to_a
77
+
78
+ # Determine if an exception should be raised on an empty result set.
79
+ if options[:raise_on_empty] && records.empty?
80
+ raise options[:empty_error_class] || ActiveRecord::RecordNotFound
81
+ end
82
+
77
83
  records = order_for_search(records, ordered_search_ids) if searching? options
78
84
  model = records.first
79
85
 
@@ -132,10 +138,15 @@ module Brainstem
132
138
  private
133
139
 
134
140
  def paginate(scope, options)
135
- per_page = calculate_per_page(options)
136
- page = calculate_page(options)
141
+ if options[:params][:limit].present? && options[:params][:offset].present?
142
+ limit = calculate_limit(options)
143
+ offset = calculate_offset(options)
144
+ else
145
+ limit = calculate_per_page(options)
146
+ offset = limit * (calculate_page(options) - 1)
147
+ end
137
148
 
138
- [scope.limit(per_page).offset(per_page * (page - 1)).uniq, scope.select("distinct `#{options[:table_name]}`.id").count] # as of Rails 3.2.5, uniq.count generates the wrong SQL.
149
+ [scope.limit(limit).offset(offset).uniq, scope.select("distinct #{scope.connection.quote_table_name options[:table_name]}.id").count] # as of Rails 3.2.5, uniq.count generates the wrong SQL.
139
150
  end
140
151
 
141
152
  def calculate_per_page(options)
@@ -148,13 +159,23 @@ module Brainstem
148
159
  [(options[:params][:page] || 1).to_i, 1].max
149
160
  end
150
161
 
162
+ def calculate_limit(options)
163
+ [[options[:params][:limit].to_i, 1].max, default_max_per_page].min
164
+ end
165
+
166
+ def calculate_offset(options)
167
+ [options[:params][:offset].to_i, 0].max
168
+ end
169
+
151
170
  # Gather allowed includes by inspecting the presented hash. For now, this requires that a new instance of the
152
171
  # presented class always be presentable.
153
- def calculate_allowed_includes(presenter, presented_class)
172
+ def calculate_allowed_includes(presenter, presented_class, is_only_query)
154
173
  allowed_includes = {}
155
174
  model = presented_class.new
156
175
  presenter.present(model).each do |k, v|
157
176
  next unless v.is_a?(AssociationField)
177
+ next if v.restrict_to_only && !is_only_query
178
+
158
179
  if v.json_name
159
180
  v.json_name = v.json_name.tableize.to_sym
160
181
  else
@@ -229,10 +250,16 @@ module Brainstem
229
250
  search_options = HashWithIndifferentAccess.new(
230
251
  :include => includes,
231
252
  :order => { :sort_order => sort_name, :direction => direction },
232
- :per_page => calculate_per_page(options),
233
- :page => calculate_page(options)
234
253
  )
235
254
 
255
+ if options[:params][:limit].present? && options[:params][:offset].present?
256
+ search_options[:limit] = calculate_limit(options)
257
+ search_options[:offset] = calculate_offset(options)
258
+ else
259
+ search_options[:per_page] = calculate_per_page(options)
260
+ search_options[:page] = calculate_page(options)
261
+ end
262
+
236
263
  search_options.reverse_merge!(extract_filters(options))
237
264
 
238
265
  result_ids, count = options[:presenter].search_block.call(options[:params][:search], search_options)
@@ -1,3 +1,3 @@
1
1
  module Brainstem
2
- VERSION = "0.2.2"
2
+ VERSION = "0.2.4"
3
3
  end
Binary file
@@ -59,9 +59,22 @@ describe Brainstem::ControllerMethods do
59
59
  @controller.call_results[:block_result].pluck(:id).should == [1]
60
60
  end
61
61
 
62
- it "passes :apply_default_filters => false to the PresenterCollection so that filters are not applied by default" do
62
+ it "passes through the controller params" do
63
+ @controller.present_object(Workspace.find(1), :key_map => { "Workspace" => "your_workspaces" })
64
+ @controller.call_results[:options][:params].should == @controller.params.merge(:only => '1')
65
+ end
66
+
67
+ it "passes through supplied options" do
68
+ @controller.present_object(Workspace.find(1), :foo => :bar)
69
+ @controller.call_results[:options][:foo].should == :bar
70
+ end
71
+
72
+ it "adds an only param if there is only one object to present" do
63
73
  @controller.present_object(Workspace.find(1))
64
- @controller.call_results[:options][:apply_default_filters].should == false
74
+ @controller.call_results[:options][:params][:only].should == "1"
75
+
76
+ @controller.present_object(Workspace.all)
77
+ @controller.call_results[:options][:params][:only].should be_nil
65
78
  end
66
79
  end
67
80
  end
@@ -44,20 +44,98 @@ describe Brainstem::PresenterCollection do
44
44
  end
45
45
 
46
46
  describe "limits and offsets" do
47
- it "honors the user's requested page size and page and returns counts" do
48
- result = @presenter_collection.presenting("workspaces", :params => { :per_page => 1, :page => 2 }) { Workspace.order('id desc') }[:results]
49
- result.length.should == 1
50
- result.first[:id].should == Workspace.order('id desc')[1].id.to_s
51
-
52
- result = @presenter_collection.presenting("workspaces", :params => { :per_page => 2, :page => 2 }) { Workspace.order('id desc') }[:results]
53
- result.length.should == 2
54
- result.map { |m| m[:id] }.should == Workspace.order('id desc')[2..3].map(&:id).map(&:to_s)
47
+ context "when only per_page and page are present" do
48
+ it "honors the user's requested page size and page and returns counts" do
49
+ result = @presenter_collection.presenting("workspaces", :params => { :per_page => 1, :page => 2 }) { Workspace.order('id desc') }[:results]
50
+ result.length.should == 1
51
+ result.first[:id].should == Workspace.order('id desc')[1].id.to_s
52
+
53
+ result = @presenter_collection.presenting("workspaces", :params => { :per_page => 2, :page => 2 }) { Workspace.order('id desc') }[:results]
54
+ result.length.should == 2
55
+ result.map { |m| m[:id] }.should == Workspace.order('id desc')[2..3].map(&:id).map(&:to_s)
56
+ end
57
+
58
+ it "defaults to 1 if the page number is less than 1" do
59
+ result = @presenter_collection.presenting("workspaces", :params => { :per_page => 1, :page => 0 }) { Workspace.order('id desc') }[:results]
60
+ result.length.should == 1
61
+ result.first[:id].should == Workspace.order('id desc')[0].id.to_s
62
+ end
55
63
  end
56
64
 
57
- it "defaults to 1 if the page number is less than 1" do
58
- result = @presenter_collection.presenting("workspaces", :params => { :per_page => 1, :page => 0 }) { Workspace.order('id desc') }[:results]
59
- result.length.should == 1
60
- result.first[:id].should == Workspace.order('id desc')[0].id.to_s
65
+ context "when only limit and offset are present" do
66
+ it "honors the user's requested limit and offset and returns counts" do
67
+ result = @presenter_collection.presenting("workspaces", :params => { :limit => 1, :offset => 2 }) { Workspace.order('id desc') }[:results]
68
+ result.length.should == 1
69
+ result.first[:id].should == Workspace.order('id desc')[2].id.to_s
70
+
71
+ result = @presenter_collection.presenting("workspaces", :params => { :limit => 2, :offset => 2 }) { Workspace.order('id desc') }[:results]
72
+ result.length.should == 2
73
+ result.map { |m| m[:id] }.should == Workspace.order('id desc')[2..3].map(&:id).map(&:to_s)
74
+ end
75
+
76
+ it "defaults to offset 0 if the passed offset is less than 0 and limit to 1 if the passed limit is less than 1" do
77
+ stub.proxy(@presenter_collection).calculate_offset(anything).times(1)
78
+ stub.proxy(@presenter_collection).calculate_limit(anything).times(1)
79
+ result = @presenter_collection.presenting("workspaces", :params => { :limit => -1, :offset => -1 }) { Workspace.order('id desc') }[:results]
80
+ result.length.should == 1
81
+ result.first[:id].should == Workspace.order('id desc')[0].id.to_s
82
+ end
83
+ end
84
+
85
+ context "when both sets of params are present" do
86
+ it "prefers limit and offset over per_page and page" do
87
+ result = @presenter_collection.presenting("workspaces", :params => { :limit => 1, :offset => 0, :per_page => 2, :page => 2 }) { Workspace.order('id desc') }[:results]
88
+ result.length.should == 1
89
+ result.first[:id].should == Workspace.order('id desc')[0].id.to_s
90
+ end
91
+
92
+ it "uses per_page and page if limit and offset are not complete" do
93
+ result = @presenter_collection.presenting("workspaces", :params => { :limit => 5, :per_page => 1, :page => 0 }) { Workspace.order('id desc') }[:results]
94
+ result.length.should == 1
95
+ result.first[:id].should == Workspace.order('id desc')[0].id.to_s
96
+
97
+ result = @presenter_collection.presenting("workspaces", :params => { :offset => 5, :per_page => 1, :page => 0 }) { Workspace.order('id desc') }[:results]
98
+ result.length.should == 1
99
+ result.first[:id].should == Workspace.order('id desc')[0].id.to_s
100
+ end
101
+ end
102
+ end
103
+
104
+ describe "raise_on_empty option" do
105
+ context "raise_on_empty is true" do
106
+ context "results are empty" do
107
+ it "should raise the provided error class when the empty_error_class option is provided" do
108
+ class MyException < Exception; end
109
+
110
+ lambda {
111
+ @presenter_collection.presenting("workspaces", :raise_on_empty => true, :empty_error_class => MyException) { Workspace.where(:id => nil) }
112
+ }.should raise_error(MyException)
113
+ end
114
+
115
+ it "should raise ActiveRecord::RecordNotFound when the empty_error_class option is not provided" do
116
+ lambda {
117
+ @presenter_collection.presenting("workspaces", :raise_on_empty => true) { Workspace.where(:id => nil) }
118
+ }.should raise_error(ActiveRecord::RecordNotFound)
119
+ end
120
+ end
121
+
122
+ context "results are not empty" do
123
+ it "should not raise an exception" do
124
+ Workspace.count.should > 0
125
+
126
+ lambda {
127
+ @presenter_collection.presenting("workspaces", :raise_on_empty => true) { Workspace.order('id desc') }
128
+ }.should_not raise_error
129
+ end
130
+ end
131
+ end
132
+
133
+ context "raise_on_empty is false" do
134
+ it "should not raise an exception when the results are empty" do
135
+ lambda {
136
+ @presenter_collection.presenting("workspaces") { Workspace.where(:id => nil) }
137
+ }.should_not raise_error
138
+ end
61
139
  end
62
140
  end
63
141
 
@@ -198,6 +276,23 @@ describe Brainstem::PresenterCollection do
198
276
  result[:users][Workspace.first.lead_user.id.to_s].should be_present
199
277
  end
200
278
 
279
+ describe "restricted associations" do
280
+ it "does apply includes that are restricted to only queries in an only query" do
281
+ t = Task.first
282
+ result = @presenter_collection.presenting("tasks", :params => { :include => "restricted", :only => t.id.to_s }, :max_per_page => 2) { Task.where(:id => t.id) }
283
+ result[:tasks][t.id.to_s].keys.should include(:restricted_id)
284
+ result.keys.should include(:restricted_associations)
285
+ end
286
+
287
+ it "does not apply includes that are restricted to only queries in a non-only query" do
288
+ t = Task.first
289
+ result = @presenter_collection.presenting("tasks", :params => { :include => "restricted" }, :max_per_page => 2) { Task.where(:id => t.id) }
290
+
291
+ result[:tasks][t.id.to_s].keys.should_not include(:restricted_id)
292
+ result.keys.should_not include(:restricted_associations)
293
+ end
294
+ end
295
+
201
296
  describe "polymorphic associations" do
202
297
  it "works with polymorphic associations" do
203
298
  result = @presenter_collection.presenting("posts", :params => { :include => "subject" }) { Post.order('id desc') }
@@ -269,21 +364,21 @@ describe Brainstem::PresenterCollection do
269
364
 
270
365
  it "converts boolean parameters from strings to booleans" do
271
366
  WorkspacePresenter.filter(:owned_by_bob) { |scope, boolean| boolean ? scope.where(:user_id => bob.id) : scope.where(:user_id => jane.id) }
272
- result = @presenter_collection.presenting("workspaces", :params => { :owned_by_bob => "false" }) { Workspace.scoped }
367
+ result = @presenter_collection.presenting("workspaces", :params => { :owned_by_bob => "false" }) { Workspace.where(nil) }
273
368
  result[:workspaces].values.find { |workspace| workspace[:title].include?("jane") }.should be
274
369
  result[:workspaces].values.find { |workspace| workspace[:title].include?("bob") }.should_not be
275
370
  end
276
371
 
277
372
  it "ensures arguments are strings" do
278
373
  WorkspacePresenter.filter(:owned_by_bob) { |scope, string| string.should be_a(String); scope }
279
- result = @presenter_collection.presenting("workspaces", :params => { :owned_by_bob => [1, 2] }) { Workspace.scoped }
374
+ result = @presenter_collection.presenting("workspaces", :params => { :owned_by_bob => [1, 2] }) { Workspace.where(nil) }
280
375
  end
281
376
 
282
377
  it "allows filters to be called with false as an argument" do
283
378
  WorkspacePresenter.filter(:nothing) { |scope, bool| bool ? scope.where(:id => nil) : scope }
284
- result = @presenter_collection.presenting("workspaces", :params => { :nothing => "true" }) { Workspace.scoped }
379
+ result = @presenter_collection.presenting("workspaces", :params => { :nothing => "true" }) { Workspace.where(nil) }
285
380
  result[:workspaces].length.should eq(0)
286
- result = @presenter_collection.presenting("workspaces", :params => { :nothing => "false" }) { Workspace.scoped }
381
+ result = @presenter_collection.presenting("workspaces", :params => { :nothing => "false" }) { Workspace.where(nil) }
287
382
  result[:workspaces].length.should_not eq(0)
288
383
  end
289
384
 
@@ -295,7 +390,7 @@ describe Brainstem::PresenterCollection do
295
390
  scope
296
391
  }
297
392
 
298
- @presenter_collection.presenting("workspaces", :params => { :between => "1:10" }) { Workspace.scoped }
393
+ @presenter_collection.presenting("workspaces", :params => { :between => "1:10" }) { Workspace.where(nil) }
299
394
  end
300
395
 
301
396
  context "with defaults" do
@@ -346,27 +441,27 @@ describe Brainstem::PresenterCollection do
346
441
  end
347
442
 
348
443
  it "calls the named scope with default arguments" do
349
- result = @presenter_collection.presenting("workspaces") { Workspace.scoped }
444
+ result = @presenter_collection.presenting("workspaces") { Workspace.where(nil) }
350
445
  result[:workspaces].keys.should eq(bob.workspaces.pluck(:id).map(&:to_s))
351
446
  end
352
447
 
353
448
  it "calls the named scope with given arguments" do
354
- result = @presenter_collection.presenting("workspaces", :params => { :owned_by => jane.id.to_s }) { Workspace.scoped }
449
+ result = @presenter_collection.presenting("workspaces", :params => { :owned_by => jane.id.to_s }) { Workspace.where(nil) }
355
450
  result[:workspaces].keys.should eq(jane.workspaces.pluck(:id).map(&:to_s))
356
451
  end
357
452
 
358
453
  it "can use filters without lambdas in the presenter or model, but behaves strangely when false is given" do
359
454
  WorkspacePresenter.filter(:numeric_description)
360
455
 
361
- result = @presenter_collection.presenting("workspaces") { Workspace.scoped }
456
+ result = @presenter_collection.presenting("workspaces") { Workspace.where(nil) }
362
457
  result[:workspaces].keys.should eq(%w[1 2 3 4])
363
458
 
364
- result = @presenter_collection.presenting("workspaces", :params => { :numeric_description => "true" }) { Workspace.scoped }
459
+ result = @presenter_collection.presenting("workspaces", :params => { :numeric_description => "true" }) { Workspace.where(nil) }
365
460
  result[:workspaces].keys.should eq(%w[2 4])
366
461
 
367
462
  # This is probably not the behavior that the developer or user intends. You should always use a one-argument lambda in your
368
463
  # model scope declaration!
369
- result = @presenter_collection.presenting("workspaces", :params => { :numeric_description => "false" }) { Workspace.scoped }
464
+ result = @presenter_collection.presenting("workspaces", :params => { :numeric_description => "false" }) { Workspace.where(nil) }
370
465
  result[:workspaces].keys.should eq(%w[2 4])
371
466
  end
372
467
  end
@@ -503,6 +598,66 @@ describe Brainstem::PresenterCollection do
503
598
  @presenter_collection.presenting("workspaces", :params => { :search => "blah", :order => "created_at:asc"}) { Workspace.order("id asc") }
504
599
  end
505
600
  end
601
+
602
+ describe "pagination" do
603
+ it "passes through limit and offset if they are requested" do
604
+ WorkspacePresenter.search do |string, options|
605
+ options[:limit].should == 1
606
+ options[:offset].should == 2
607
+ [[1], 1]
608
+ end
609
+
610
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah", :limit => 1, :offset => 2}) { Workspace.order("id asc") }
611
+ end
612
+
613
+ it "passes through only limit and offset if all pagination options are requested" do
614
+ WorkspacePresenter.search do |string, options|
615
+ options[:limit].should == 1
616
+ options[:offset].should == 2
617
+ options[:per_page].should == nil
618
+ options[:page].should == nil
619
+ [[1], 1]
620
+ end
621
+
622
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah", :limit => 1, :offset => 2, :per_page => 3, :page => 4}) { Workspace.order("id asc") }
623
+ end
624
+
625
+ it "passes through page and per_page when limit not present" do
626
+ WorkspacePresenter.search do |string, options|
627
+ options[:limit].should == nil
628
+ options[:offset].should == nil
629
+ options[:per_page].should == 3
630
+ options[:page].should == 4
631
+ [[1], 1]
632
+ end
633
+
634
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah", :offset => 2, :per_page => 3, :page => 4}) { Workspace.order("id asc") }
635
+ end
636
+
637
+ it "passes through page and per_page when offset not present" do
638
+ WorkspacePresenter.search do |string, options|
639
+ options[:limit].should == nil
640
+ options[:offset].should == nil
641
+ options[:per_page].should == 3
642
+ options[:page].should == 4
643
+ [[1], 1]
644
+ end
645
+
646
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah", :limit => 1, :per_page => 3, :page => 4}) { Workspace.order("id asc") }
647
+ end
648
+
649
+ it "passes through page and per_page by default" do
650
+ WorkspacePresenter.search do |string, options|
651
+ options[:limit].should == nil
652
+ options[:offset].should == nil
653
+ options[:per_page].should == 20
654
+ options[:page].should == 1
655
+ [[1], 1]
656
+ end
657
+
658
+ @presenter_collection.presenting("workspaces", :params => { :search => "blah"}) { Workspace.order("id asc") }
659
+ end
660
+ end
506
661
  end
507
662
  end
508
663
 
@@ -49,7 +49,7 @@ class Workspace < ActiveRecord::Base
49
49
  has_many :posts
50
50
 
51
51
  scope :owned_by, -> id { where(:user_id => id) }
52
- scope :numeric_description, where(:description => ["1", "2", "3"])
52
+ scope :numeric_description, -> description { where(:description => ["1", "2", "3"]) }
53
53
 
54
54
  def lead_user
55
55
  user
@@ -16,7 +16,8 @@ class TaskPresenter < Brainstem::Presenter
16
16
  :name => model.name,
17
17
  :sub_tasks => association(:sub_tasks),
18
18
  :other_tasks => association(:sub_tasks, :json_name => "other_tasks"),
19
- :workspace => association(:workspace)
19
+ :workspace => association(:workspace),
20
+ :restricted => association(:json_name => "restricted_association", :restrict_to_only => true) { |model| model }
20
21
  }
21
22
  end
22
23
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: brainstem
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.4
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,14 +9,14 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2013-05-25 00:00:00.000000000 Z
12
+ date: 2014-03-10 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: activerecord
16
16
  requirement: !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
- - - ~>
19
+ - - ! '>='
20
20
  - !ruby/object:Gem::Version
21
21
  version: '3.0'
22
22
  type: :runtime
@@ -24,7 +24,7 @@ dependencies:
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  none: false
26
26
  requirements:
27
- - - ~>
27
+ - - ! '>='
28
28
  - !ruby/object:Gem::Version
29
29
  version: '3.0'
30
30
  - !ruby/object:Gem::Dependency
@@ -132,6 +132,7 @@ extensions: []
132
132
  extra_rdoc_files: []
133
133
  files:
134
134
  - brainstem.gemspec
135
+ - CHANGELOG.md
135
136
  - Gemfile
136
137
  - Gemfile.lock
137
138
  - Guardfile
@@ -174,7 +175,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
174
175
  version: '0'
175
176
  segments:
176
177
  - 0
177
- hash: 1101488451032288843
178
+ hash: 1278718761883180932
178
179
  required_rubygems_version: !ruby/object:Gem::Requirement
179
180
  none: false
180
181
  requirements:
@@ -183,7 +184,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
183
184
  version: '0'
184
185
  segments:
185
186
  - 0
186
- hash: 1101488451032288843
187
+ hash: 1278718761883180932
187
188
  requirements: []
188
189
  rubyforge_project:
189
190
  rubygems_version: 1.8.25