brainstem 0.2.2 → 0.2.4

Sign up to get free protection for your applications and to get access to all the features.
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