brainstem 0.2.6.1 → 1.0.0.pre.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.
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