brainstem 0.2.6.1 → 1.0.0.pre.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (56) hide show
  1. checksums.yaml +5 -13
  2. data/CHANGELOG.md +16 -2
  3. data/Gemfile.lock +51 -36
  4. data/README.md +531 -110
  5. data/brainstem.gemspec +6 -2
  6. data/lib/brainstem.rb +25 -9
  7. data/lib/brainstem/concerns/controller_param_management.rb +22 -0
  8. data/lib/brainstem/concerns/error_presentation.rb +58 -0
  9. data/lib/brainstem/concerns/inheritable_configuration.rb +29 -0
  10. data/lib/brainstem/concerns/lookup.rb +30 -0
  11. data/lib/brainstem/concerns/presenter_dsl.rb +111 -0
  12. data/lib/brainstem/controller_methods.rb +17 -8
  13. data/lib/brainstem/dsl/association.rb +55 -0
  14. data/lib/brainstem/dsl/associations_block.rb +12 -0
  15. data/lib/brainstem/dsl/base_block.rb +31 -0
  16. data/lib/brainstem/dsl/conditional.rb +25 -0
  17. data/lib/brainstem/dsl/conditionals_block.rb +15 -0
  18. data/lib/brainstem/dsl/configuration.rb +112 -0
  19. data/lib/brainstem/dsl/field.rb +68 -0
  20. data/lib/brainstem/dsl/fields_block.rb +25 -0
  21. data/lib/brainstem/preloader.rb +98 -0
  22. data/lib/brainstem/presenter.rb +325 -134
  23. data/lib/brainstem/presenter_collection.rb +82 -286
  24. data/lib/brainstem/presenter_validator.rb +96 -0
  25. data/lib/brainstem/query_strategies/README.md +107 -0
  26. data/lib/brainstem/query_strategies/base_strategy.rb +62 -0
  27. data/lib/brainstem/query_strategies/filter_and_search.rb +50 -0
  28. data/lib/brainstem/query_strategies/filter_or_search.rb +103 -0
  29. data/lib/brainstem/test_helpers.rb +5 -1
  30. data/lib/brainstem/version.rb +1 -1
  31. data/spec/brainstem/concerns/controller_param_management_spec.rb +42 -0
  32. data/spec/brainstem/concerns/error_presentation_spec.rb +113 -0
  33. data/spec/brainstem/concerns/inheritable_configuration_spec.rb +210 -0
  34. data/spec/brainstem/concerns/presenter_dsl_spec.rb +412 -0
  35. data/spec/brainstem/controller_methods_spec.rb +15 -27
  36. data/spec/brainstem/dsl/association_spec.rb +123 -0
  37. data/spec/brainstem/dsl/conditional_spec.rb +93 -0
  38. data/spec/brainstem/dsl/configuration_spec.rb +1 -0
  39. data/spec/brainstem/dsl/field_spec.rb +212 -0
  40. data/spec/brainstem/preloader_spec.rb +137 -0
  41. data/spec/brainstem/presenter_collection_spec.rb +565 -244
  42. data/spec/brainstem/presenter_spec.rb +726 -167
  43. data/spec/brainstem/presenter_validator_spec.rb +209 -0
  44. data/spec/brainstem/query_strategies/filter_and_search_spec.rb +46 -0
  45. data/spec/brainstem/query_strategies/filter_or_search_spec.rb +45 -0
  46. data/spec/spec_helper.rb +11 -3
  47. data/spec/spec_helpers/db.rb +32 -65
  48. data/spec/spec_helpers/presenters.rb +124 -29
  49. data/spec/spec_helpers/rr.rb +11 -0
  50. data/spec/spec_helpers/schema.rb +115 -0
  51. metadata +126 -30
  52. data/lib/brainstem/association_field.rb +0 -53
  53. data/lib/brainstem/engine.rb +0 -4
  54. data/pkg/brainstem-0.2.5.gem +0 -0
  55. data/pkg/brainstem-0.2.6.gem +0 -0
  56. data/spec/spec_helpers/cleanup.rb +0 -23
@@ -0,0 +1,209 @@
1
+ require 'spec_helper'
2
+ require 'brainstem/presenter_validator'
3
+
4
+ describe Brainstem::PresenterValidator do
5
+ let(:presenter_class) do
6
+ Class.new(Brainstem::Presenter) do
7
+ presents Workspace
8
+
9
+ preload :lead_user
10
+
11
+ conditionals do
12
+ model :title_is_hello, lambda { |model| model.title == 'hello' }, 'visible when the title is hello'
13
+ request :user_is_bob, lambda { current_user == 'bob' }, 'visible only to bob'
14
+ end
15
+
16
+ fields do
17
+ field :title, :string
18
+ field :description, :string
19
+ field :updated_at, :datetime
20
+ field :secret, :string, 'a secret, via secret_info',
21
+ via: :secret_info,
22
+ if: [:user_is_bob, :title_is_hello]
23
+
24
+ with_options if: :user_is_bob do
25
+ field :bob_title, :string, 'another name for the title, only for Bob',
26
+ via: :title
27
+ end
28
+ end
29
+
30
+ associations do
31
+ association :tasks, Task, 'The Tasks in this Workspace',
32
+ restrict_to_only: true
33
+ association :lead_user, User, 'The user who runs this Workspace'
34
+ association :subtasks, Task, 'Only Tasks in this Workspace that are subtasks',
35
+ dynamic: lambda { |workspace| workspace.tasks.where('parent_id IS NOT NULL') }
36
+ end
37
+ end
38
+ end
39
+
40
+ let(:validator) { Brainstem::PresenterValidator.new(presenter_class) }
41
+
42
+ it 'should be valid' do
43
+ expect(presenter_class.configuration[:preloads]).not_to be_empty
44
+ expect(presenter_class.configuration[:fields]).not_to be_empty
45
+ expect(presenter_class.configuration[:associations]).not_to be_empty
46
+ expect(validator).to be_valid
47
+ end
48
+
49
+ describe 'validating preload' do
50
+ it 'adds an error if the requested preload does not exist on the presented class' do
51
+ presenter_class.preload :foo
52
+ expect(validator).not_to be_valid
53
+ expect(validator.errors[:preload]).to eq ["not all presented classes respond to 'foo'"]
54
+ end
55
+
56
+ it 'adds an error if the requested preload does not exist on only one of the presented classes' do
57
+ presenter_class.presents User
58
+ expect(validator).not_to be_valid
59
+ expect(validator.errors[:preload]).to eq ["not all presented classes respond to 'lead_user'"]
60
+ end
61
+
62
+ it 'supports nested preloads' do
63
+ presenter_class.preload foo: :bar
64
+ expect(validator).not_to be_valid
65
+ expect(validator.errors[:preload]).to eq ["not all presented classes respond to 'foo'"]
66
+ end
67
+ end
68
+
69
+ describe 'validating associations' do
70
+ it 'adds an error for any field that is not on all presented classes' do
71
+ presenter_class.associations do
72
+ association :foo, Workspace
73
+ association :bar, Workspace
74
+ end
75
+
76
+ expect(validator).not_to be_valid
77
+ expect(validator.errors[:associations]).to eq ["'foo' is not valid because not all presented classes respond to 'foo'",
78
+ "'bar' is not valid because not all presented classes respond to 'bar'"]
79
+ end
80
+
81
+ it 'adds an error for any association that does not have a known presenter' do
82
+ class NewConcept; end
83
+
84
+ presenter_class.associations do
85
+ association :tasks, NewConcept
86
+ end
87
+
88
+ expect(validator).not_to be_valid
89
+ expect(validator.errors[:associations]).to eq ["'tasks' is not valid because no presenter could be found for the NewConcept class"]
90
+ end
91
+ end
92
+
93
+ describe 'validating fields' do
94
+ it 'adds an error for any field that is not on all presented classes' do
95
+ presenter_class.fields do
96
+ field :foo, :string
97
+ field :bar, :integer
98
+ end
99
+
100
+ expect(validator).not_to be_valid
101
+ expect(validator.errors[:fields]).to eq ["'foo' is not valid because not all presented classes respond to 'foo'",
102
+ "'bar' is not valid because not all presented classes respond to 'bar'"]
103
+ end
104
+
105
+ it 'errors when one of the presented classes is missing a field' do
106
+ presenter_class.presents User
107
+ expect(validator).not_to be_valid
108
+ expect(validator.errors[:fields]).to be_present
109
+ end
110
+
111
+ it 'supports :via' do
112
+ presenter_class.fields do
113
+ field :foo, :string, via: :title
114
+ end
115
+
116
+ expect(validator).to be_valid
117
+ end
118
+
119
+ it 'supports dynamic fields' do
120
+ presenter_class.fields do
121
+ field :foo, :string, dynamic: lambda { 2 }
122
+ end
123
+
124
+ expect(validator).to be_valid
125
+ end
126
+
127
+ it 'supports nested fields' do
128
+ presenter_class.fields do
129
+ fields :permissions do
130
+ field :title, :string
131
+ end
132
+ end
133
+
134
+ expect(validator).to be_valid
135
+
136
+ presenter_class.fields do
137
+ fields :permissions do
138
+ field :missing, :string
139
+ end
140
+ end
141
+
142
+ expect(validator).not_to be_valid
143
+ expect(validator.errors[:fields]).to eq ["'missing' is not valid because not all presented classes respond to 'missing'"]
144
+ end
145
+
146
+ it "checks that any 'if' option has a matching conditional(s)" do
147
+ expect(presenter_class.configuration[:conditionals][:title_is_hello]).to be_present
148
+
149
+ presenter_class.fields do
150
+ field :title, :string, if: :title_is_hello
151
+ end
152
+
153
+ expect(validator).to be_valid
154
+
155
+ presenter_class.fields do
156
+ field :title, :string, if: [:title_is_hello, :wat]
157
+ end
158
+
159
+ expect(validator).not_to be_valid
160
+ expect(validator.errors[:fields]).to eq ["'title' is not valid because one or more of the specified conditionals does not exist"]
161
+ end
162
+
163
+ it "checks conditionals on nested fields too" do
164
+ presenter_class.fields do
165
+ fields :permissions do
166
+ field :title, :string, if: [:title_is_hello, :wat]
167
+ end
168
+ end
169
+
170
+ expect(validator).not_to be_valid
171
+ expect(validator.errors[:fields]).to eq ["'title' is not valid because one or more of the specified conditionals does not exist"]
172
+ end
173
+ end
174
+
175
+ describe 'validating sort_orders' do
176
+ it 'checks that a default is selected if any sort_orders are defined' do
177
+ presenter_class.sort_order :alphabetical, "workspaces.title"
178
+ expect(validator).not_to be_valid
179
+ expect(validator.errors[:default_sort_order]).to eq ["A default_sort_order is highly recommended if any sort_orders are declared"]
180
+
181
+ presenter_class.default_sort_order "alphabetical:desc"
182
+ expect(validator).to be_valid
183
+ end
184
+
185
+ it 'checks that a default matches an existing sort_order' do
186
+ presenter_class.default_sort_order "alphabetical:desc"
187
+
188
+ expect(validator).not_to be_valid
189
+ expect(validator.errors[:default_sort_order]).to eq ["The declared default_sort_order ('alphabetical') does not match an existing sort_order"]
190
+
191
+ presenter_class.sort_order :alphabetical, "workspaces.title"
192
+ expect(validator).to be_valid
193
+ end
194
+ end
195
+
196
+ describe 'validating presence of brainstem_key' do
197
+ it 'requires a brainstem_key to be defined when more than one object is being presented' do
198
+ presenter_class.presents Task
199
+ expect(validator).not_to be_valid
200
+ expect(validator.errors[:brainstem_key]).to eq ["a brainstem_key must be provided when multiple classes are presented."]
201
+ end
202
+ end
203
+
204
+ specify 'all spec presenters should be valid' do
205
+ Brainstem.presenter_collection.presenters.each do |name, klass|
206
+ expect(Brainstem::PresenterValidator.new(klass)).to be_valid
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,46 @@
1
+ require 'spec_helper'
2
+
3
+ describe Brainstem::QueryStrategies::FilterAndSearch do
4
+ let(:bob) { User.find_by_username('bob') }
5
+
6
+ let(:params) { {
7
+ "owned_by" => bob.id.to_s,
8
+ per_page: 7,
9
+ page: 1,
10
+ search: 'toot, otto, toot',
11
+ } }
12
+
13
+ let(:options) { {
14
+ primary_presenter: CheesePresenter.new,
15
+ table_name: 'cheeses',
16
+ default_per_page: 20,
17
+ default_max_per_page: 200,
18
+ default_max_filter_and_search_page: 500,
19
+ params: params
20
+ } }
21
+
22
+ describe '#execute' do
23
+ before do
24
+ CheesePresenter.search do |string, options|
25
+ [[2,3,4,5,6,8,9,10,11,12], 11]
26
+ end
27
+
28
+ CheesePresenter.filter(:owned_by) { |scope, user_id| scope.owned_by(user_id.to_i) }
29
+ CheesePresenter.sort_order(:id) { |scope, direction| scope.order("cheeses.id #{direction}") }
30
+ end
31
+
32
+ it 'takes the intersection of the search and filter results' do
33
+ results, count = described_class.new(options).execute(Cheese.unscoped)
34
+ expect(count).to eq(8)
35
+ expect(results.map(&:id)).to eq([2,3,4,5,8,10,11])
36
+ end
37
+
38
+ it "applies ordering to the scope" do
39
+ options[:params]["order"] = 'id:desc'
40
+ proxy.instance_of(Brainstem::Presenter).apply_ordering_to_scope(anything, anything).times(1)
41
+ results, count = described_class.new(options).execute(Cheese.unscoped)
42
+ expect(count).to eq(8)
43
+ expect(results.map(&:id)).to eq([12,11,10,8,5,4,3])
44
+ end
45
+ end
46
+ end
@@ -0,0 +1,45 @@
1
+ require 'spec_helper'
2
+
3
+ # The functionality of this is mainly tested in more integration-y tests in presenter_collection_spec.rb
4
+
5
+ describe Brainstem::QueryStrategies::FilterOrSearch do
6
+ describe '#execute' do
7
+ context 'we are searching' do
8
+ let(:subject) { described_class.new(@options) }
9
+
10
+ before do
11
+ @options = { primary_presenter: WorkspacePresenter.new,
12
+ table_name: 'workspaces',
13
+ default_per_page: 20,
14
+ default_max_per_page: 200,
15
+ params: { search: "cheese" } }
16
+
17
+ @results = [Workspace.first, Workspace.second]
18
+
19
+ WorkspacePresenter.search do |string|
20
+ [[1,2], 2]
21
+ end
22
+ end
23
+
24
+ it 'returns the primary models and count' do
25
+ expect(subject.execute(Workspace.unscoped)).to eq([@results, 2])
26
+ end
27
+ end
28
+
29
+ context 'we are not searching' do
30
+ let(:subject) { described_class.new(@options) }
31
+
32
+ before do
33
+ @options = { primary_presenter: WorkspacePresenter.new,
34
+ table_name: 'workspaces',
35
+ default_per_page: 20,
36
+ default_max_per_page: 200,
37
+ params: { } }
38
+ end
39
+
40
+ it 'returns the primary models and count' do
41
+ expect(subject.execute(Workspace.unscoped)).to eq([Workspace.unscoped.to_a, Workspace.count])
42
+ end
43
+ end
44
+ end
45
+ end
data/spec/spec_helper.rb CHANGED
@@ -3,20 +3,28 @@ require 'logger'
3
3
  require 'rr'
4
4
  require 'rspec'
5
5
  require 'sqlite3'
6
+ require 'database_cleaner'
7
+ require 'pry'
8
+ require 'pry-nav'
6
9
 
7
10
  require 'brainstem'
11
+ require_relative 'spec_helpers/schema'
8
12
  require_relative 'spec_helpers/db'
9
- require_relative 'spec_helpers/cleanup'
13
+ require_relative 'spec_helpers/rr'
14
+
15
+ DatabaseCleaner.strategy = :transaction
10
16
 
11
17
  RSpec.configure do |config|
12
18
  config.mock_with :rr
13
19
 
14
20
  config.before(:each) do
15
21
  Brainstem.logger = Logger.new(StringIO.new)
22
+ Brainstem.reset!
23
+ load File.join(File.dirname(__FILE__), 'spec_helpers', 'presenters.rb')
24
+ DatabaseCleaner.start
16
25
  end
17
26
 
18
27
  config.after(:each) do
19
- Brainstem.clear_collections!
20
- Brainstem.default_namespace = nil
28
+ DatabaseCleaner.clean
21
29
  end
22
30
  end
@@ -1,65 +1,3 @@
1
- ActiveRecord::Base.establish_connection(:adapter => 'sqlite3', :database => ':memory:')
2
- ActiveRecord::Schema.define do
3
- self.verbose = false
4
-
5
- create_table :users, :force => true do |t|
6
- t.string :username
7
- t.timestamps null: true
8
- end
9
-
10
- create_table :workspaces, :force => true do |t|
11
- t.string :title
12
- t.string :description
13
- t.belongs_to :user
14
- t.timestamps null: true
15
- end
16
-
17
- create_table :tasks, :force => true do |t|
18
- t.string :name
19
- t.integer :parent_id
20
- t.belongs_to :workspace
21
- t.timestamps null: true
22
- end
23
-
24
- create_table :posts, :force => true do |t|
25
- t.string :body
26
- t.integer :subject_id
27
- t.string :subject_type
28
- t.timestamps null: true
29
- end
30
- end
31
-
32
- class User < ActiveRecord::Base
33
- has_many :workspaces
34
- end
35
-
36
- class Task < ActiveRecord::Base
37
- belongs_to :workspace
38
- has_many :sub_tasks, :foreign_key => :parent_id, :class_name => "Task"
39
- has_many :posts
40
-
41
- def tags
42
- %w[some tags]
43
- end
44
- end
45
-
46
- class Workspace < ActiveRecord::Base
47
- belongs_to :user
48
- has_many :tasks
49
- has_many :posts
50
-
51
- scope :owned_by, -> id { where(:user_id => id) }
52
- scope :numeric_description, -> description { where(:description => ["1", "2", "3"]) }
53
-
54
- def lead_user
55
- user
56
- end
57
- end
58
-
59
- class Post < ActiveRecord::Base
60
- belongs_to :subject, :polymorphic => true
61
- end
62
-
63
1
  User.create!(:id => 1, :username => "bob")
64
2
  User.create!(:id => 2, :username => "jane")
65
3
 
@@ -70,11 +8,40 @@ Workspace.create!(:id => 4, :user_id => 1, :title => "bob workspace 4", :descrip
70
8
  Workspace.create!(:id => 5, :user_id => 2, :title => "jane workspace 1", :description => "c")
71
9
  Workspace.create!(:id => 6, :user_id => 2, :title => "jane workspace 2", :description => "3")
72
10
 
11
+
12
+ Cheese.create!(id: 1, user_id: 1, flavor: 'colby jack' )
13
+ Cheese.create!(id: 2, user_id: 1, flavor: 'swiss' )
14
+ Cheese.create!(id: 3, user_id: 1, flavor: 'cheese curds' )
15
+ Cheese.create!(id: 4, user_id: 1, flavor: 'bleu' )
16
+ Cheese.create!(id: 5, user_id: 1, flavor: 'cheddar' )
17
+ Cheese.create!(id: 6, user_id: 2, flavor: 'gorgonzola' )
18
+ Cheese.create!(id: 7, user_id: 1, flavor: 'search text' )
19
+ Cheese.create!(id: 8, user_id: 1, flavor: 'search text' )
20
+ Cheese.create!(id: 9, user_id: 2, flavor: 'provologne')
21
+ Cheese.create!(id: 10, user_id: 1, flavor: 'old amsterdam' )
22
+ Cheese.create!(id: 11, user_id: 1, flavor: 'brie' )
23
+ Cheese.create!(id: 12, user_id: 1, flavor: 'wensleydale' )
24
+ # Group.create!(:id => 7, :user_id => 2, :title => "a group", :description => "this is a group")
25
+
73
26
  Task.create!(:id => 1, :workspace_id => 1, :name => "Buy milk")
74
27
  Task.create!(:id => 2, :workspace_id => 1, :name => "Buy bananas")
75
28
  Task.create!(:id => 3, :workspace_id => 1, :parent_id => 2, :name => "Green preferred")
76
29
  Task.create!(:id => 4, :workspace_id => 1, :parent_id => 2, :name => "One bunch")
30
+ Task.create!(:id => 5, :workspace_id => 6, :name => "In another Workspace")
31
+
32
+ Post.create!(:id => 1, :user_id => 1, :subject => Workspace.first, :body => "first post!")
33
+ Post.create!(:id => 2, :user_id => 1, :subject => Task.first, :body => "this is important. get on it!")
34
+ Post.create!(:id => 3, :user_id => 2, :body => "Post without subject")
35
+
36
+ Attachments::PostAttachment.create!(id: 1, subject: Post.first, filename: 'I am an attachment on a post')
37
+ Attachments::TaskAttachment.create!(id: 2, subject: Task.first, filename: 'I am an attachment on a task')
38
+
39
+ # TODO:
40
+ # user can group all of an STI structure into the brainstem_keys, of their choice
41
+ # user can put STI classes into seperate brainstem_keys
42
+ # inheriting presenters and showing that they work
43
+ # Spec presents attachments, including their subjects, shows that right presenters are used.
44
+ # Show that the base class is always used as the brainstem_key for attachments as polymorphic association targets
77
45
 
78
- Post.create!(:id => 1, :subject => Workspace.first, :body => "first post!")
79
- Post.create!(:id => 2, :subject => Task.first, :body => "this is important. get on it!")
80
- Post.create!(:id => 3, :body => "Post without subject")
46
+ # Use Group / Workspace as the polymorphic target of an association where we do not
47
+ # want the baseclass to be used as the brainstem_key (like line_items0)
@@ -1,43 +1,138 @@
1
1
  class WorkspacePresenter < Brainstem::Presenter
2
- def present(model)
3
- {
4
- :title => model.title,
5
- :description => model.description,
6
- :updated_at => model.updated_at,
7
- :tasks => association(:tasks),
8
- :lead_user => association(:lead_user, :json_name => "users")
9
- }
2
+ presents Workspace
3
+
4
+ helper do
5
+ def current_user
6
+ 'jane'
7
+ end
8
+ end
9
+
10
+ preload :lead_user
11
+
12
+ conditionals do
13
+ model :title_is_hello, lambda { |model| model.title == 'hello' }, 'visible when the title is hello'
14
+ request :user_is_bob, lambda { current_user == 'bob' }, 'visible only to bob'
15
+ end
16
+
17
+ fields do
18
+ field :title, :string
19
+ field :description, :string
20
+ field :updated_at, :datetime
21
+ field :dynamic_title, :string, dynamic: lambda { |model| "title: #{model.title}" }
22
+ field :lookup_title, :string, lookup: lambda { |models| Hash[models.map { |model| [model.id, "lookup_title: #{model.title}"] }] }
23
+ field :lookup_fetch_title, :string,
24
+ lookup: lambda { |models| Hash[models.map { |model| [model.id, model.title] }] },
25
+ lookup_fetch: lambda { |lookup, model| "lookup_fetch_title: #{lookup[model.id]}" }
26
+ field :expensive_title, :string, via: :title, optional: true
27
+ field :expensive_title2, :string, via: :title, optional: true
28
+ field :expensive_title3, :string, via: :title, optional: true
29
+ field :conditional_expensive_title, :string, via: :title, optional: true, if: :title_is_hello
30
+
31
+ fields :permissions do
32
+ field :access_level, :integer, dynamic: lambda { 2 }
33
+ end
34
+
35
+ field :hello_title, :string, 'the title, when hello',
36
+ dynamic: lambda { 'title is hello' },
37
+ if: :title_is_hello
38
+
39
+ field :secret, :string, 'a secret, via secret_info',
40
+ via: :secret_info,
41
+ if: [:user_is_bob, :title_is_hello]
42
+
43
+ with_options if: :user_is_bob do
44
+ field :bob_title, :string, 'another name for the title, only for Bob',
45
+ via: :title
46
+ end
47
+ end
48
+
49
+ associations do
50
+ association :tasks, Task, 'The Tasks in this Workspace'
51
+ association :lead_user, User, 'The user who runs this Workspace'
52
+ # association :subtasks, Task, 'Only Tasks in this Workspace that are subtasks',
53
+ # dynamic: lambda { |workspace| workspace.tasks.where('parent_id IS NOT NULL') },
54
+ # brainstem_key: 'sub_tasks'
55
+ end
56
+ end
57
+
58
+ class CheesePresenter < Brainstem::Presenter
59
+ presents Cheese
60
+
61
+ fields do
62
+ field :flavor, :string
63
+ end
64
+
65
+ associations do
66
+ association :user, User, 'The owner of the cheese'
67
+ end
68
+ end
69
+
70
+ class GroupPresenter < Brainstem::Presenter
71
+ presents Group
72
+
73
+ fields do
74
+ field :title, :string
75
+ end
76
+
77
+ associations do
78
+ association :tasks, Task, 'The Tasks in this Group'
10
79
  end
11
80
  end
12
81
 
13
82
  class TaskPresenter < Brainstem::Presenter
14
- def present(model)
15
- {
16
- :name => model.name,
17
- :sub_tasks => association(:sub_tasks),
18
- :other_tasks => association(:sub_tasks, :json_name => "other_tasks"),
19
- :workspace => association(:workspace),
20
- :restricted => association(:json_name => "restricted_association", :restrict_to_only => true) { |model| model }
21
- }
83
+ presents Task
84
+
85
+ fields do
86
+ field :name, :string
87
+ end
88
+
89
+ associations do
90
+ association :sub_tasks, Task
91
+ association :other_tasks, Task, 'another copy of the sub_tasks association',
92
+ via: :sub_tasks
93
+ association :workspace, Workspace
94
+ association :restricted, Task, 'only available on only / show requests',
95
+ dynamic: lambda { |task| Task.last },
96
+ restrict_to_only: true
22
97
  end
23
98
  end
24
99
 
25
100
  class UserPresenter < Brainstem::Presenter
26
- def present(model)
27
- {
28
- :username => model.username,
29
- :odd_workspaces => association(:json_name => "odd_workspaces") { |user|
30
- user.workspaces.select { |workspace| workspace.id % 2 == 1 }
31
- }
32
- }
101
+ presents User
102
+
103
+ fields do
104
+ field :username, :string
105
+ end
106
+
107
+ associations do
108
+ association :odd_workspaces, Workspace, 'only the odd numbered workspaces',
109
+ dynamic: lambda { |user| user.workspaces.select { |workspace| workspace.id % 2 == 1 } }
33
110
  end
34
111
  end
35
-
112
+
36
113
  class PostPresenter < Brainstem::Presenter
37
- def present(model)
38
- {
39
- :body => model.body,
40
- :subject => association(:subject)
41
- }
114
+ presents Post
115
+
116
+ fields do
117
+ field :body, :string
118
+ end
119
+
120
+ associations do
121
+ association :subject, :polymorphic
122
+ association :attachments, Attachments::PostAttachment
42
123
  end
43
124
  end
125
+
126
+ class AttachmentPresenter < Brainstem::Presenter
127
+ presents Attachments::TaskAttachment, Attachments::PostAttachment
128
+
129
+ brainstem_key :attachments
130
+
131
+ fields do
132
+ field :filename, :string
133
+ end
134
+
135
+ associations do
136
+ association :subject, :polymorphic
137
+ end
138
+ end