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.
- checksums.yaml +5 -13
- data/CHANGELOG.md +16 -2
- data/Gemfile.lock +51 -36
- data/README.md +531 -110
- data/brainstem.gemspec +6 -2
- data/lib/brainstem.rb +25 -9
- data/lib/brainstem/concerns/controller_param_management.rb +22 -0
- data/lib/brainstem/concerns/error_presentation.rb +58 -0
- data/lib/brainstem/concerns/inheritable_configuration.rb +29 -0
- data/lib/brainstem/concerns/lookup.rb +30 -0
- data/lib/brainstem/concerns/presenter_dsl.rb +111 -0
- data/lib/brainstem/controller_methods.rb +17 -8
- data/lib/brainstem/dsl/association.rb +55 -0
- data/lib/brainstem/dsl/associations_block.rb +12 -0
- data/lib/brainstem/dsl/base_block.rb +31 -0
- data/lib/brainstem/dsl/conditional.rb +25 -0
- data/lib/brainstem/dsl/conditionals_block.rb +15 -0
- data/lib/brainstem/dsl/configuration.rb +112 -0
- data/lib/brainstem/dsl/field.rb +68 -0
- data/lib/brainstem/dsl/fields_block.rb +25 -0
- data/lib/brainstem/preloader.rb +98 -0
- data/lib/brainstem/presenter.rb +325 -134
- data/lib/brainstem/presenter_collection.rb +82 -286
- data/lib/brainstem/presenter_validator.rb +96 -0
- data/lib/brainstem/query_strategies/README.md +107 -0
- data/lib/brainstem/query_strategies/base_strategy.rb +62 -0
- data/lib/brainstem/query_strategies/filter_and_search.rb +50 -0
- data/lib/brainstem/query_strategies/filter_or_search.rb +103 -0
- data/lib/brainstem/test_helpers.rb +5 -1
- data/lib/brainstem/version.rb +1 -1
- data/spec/brainstem/concerns/controller_param_management_spec.rb +42 -0
- data/spec/brainstem/concerns/error_presentation_spec.rb +113 -0
- data/spec/brainstem/concerns/inheritable_configuration_spec.rb +210 -0
- data/spec/brainstem/concerns/presenter_dsl_spec.rb +412 -0
- data/spec/brainstem/controller_methods_spec.rb +15 -27
- data/spec/brainstem/dsl/association_spec.rb +123 -0
- data/spec/brainstem/dsl/conditional_spec.rb +93 -0
- data/spec/brainstem/dsl/configuration_spec.rb +1 -0
- data/spec/brainstem/dsl/field_spec.rb +212 -0
- data/spec/brainstem/preloader_spec.rb +137 -0
- data/spec/brainstem/presenter_collection_spec.rb +565 -244
- data/spec/brainstem/presenter_spec.rb +726 -167
- data/spec/brainstem/presenter_validator_spec.rb +209 -0
- data/spec/brainstem/query_strategies/filter_and_search_spec.rb +46 -0
- data/spec/brainstem/query_strategies/filter_or_search_spec.rb +45 -0
- data/spec/spec_helper.rb +11 -3
- data/spec/spec_helpers/db.rb +32 -65
- data/spec/spec_helpers/presenters.rb +124 -29
- data/spec/spec_helpers/rr.rb +11 -0
- data/spec/spec_helpers/schema.rb +115 -0
- metadata +126 -30
- data/lib/brainstem/association_field.rb +0 -53
- data/lib/brainstem/engine.rb +0 -4
- data/pkg/brainstem-0.2.5.gem +0 -0
- data/pkg/brainstem-0.2.6.gem +0 -0
- 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/
|
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
|
-
|
20
|
-
Brainstem.default_namespace = nil
|
28
|
+
DatabaseCleaner.clean
|
21
29
|
end
|
22
30
|
end
|
data/spec/spec_helpers/db.rb
CHANGED
@@ -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
|
-
|
79
|
-
|
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
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
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
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
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
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
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
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
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
|