brainstem 0.0.2

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.
@@ -0,0 +1,252 @@
1
+ require 'spec_helper'
2
+
3
+ describe Brainstem::Presenter do
4
+ describe "class methods" do
5
+
6
+ describe "presents method" do
7
+ before do
8
+ @klass = Class.new(Brainstem::Presenter)
9
+ end
10
+
11
+ it "records itself as the presenter for the named class as a string" do
12
+ @klass.presents "String"
13
+ Brainstem.presenter_collection.for(String).should be_a(@klass)
14
+ end
15
+
16
+ it "records itself as the presenter for the given class" do
17
+ @klass.presents String
18
+ Brainstem.presenter_collection.for(String).should be_a(@klass)
19
+ end
20
+
21
+ it "records itself as the presenter for the named classes" do
22
+ @klass.presents String, Array
23
+ Brainstem.presenter_collection.for(String).should be_a(@klass)
24
+ Brainstem.presenter_collection.for(Array).should be_a(@klass)
25
+ end
26
+ end
27
+
28
+ describe "implicit namespacing" do
29
+ module V1
30
+ class SomePresenter < Brainstem::Presenter
31
+ end
32
+ end
33
+
34
+ it "uses the closest module name as the presenter namespace" do
35
+ V1::SomePresenter.presents String
36
+ Brainstem.presenter_collection(:v1).for(String).should be_a(V1::SomePresenter)
37
+ end
38
+
39
+ it "does not map namespaced presenters into the default namespace" do
40
+ V1::SomePresenter.presents String
41
+ Brainstem.presenter_collection.for(String).should be_nil
42
+ end
43
+ end
44
+
45
+ describe "helper method" do
46
+ before do
47
+ @klass = Class.new(Brainstem::Presenter) do
48
+ def call_helper
49
+ foo
50
+ end
51
+ end
52
+ @helper = Module.new do
53
+ def foo
54
+ "I work"
55
+ end
56
+ end
57
+ end
58
+
59
+ it "includes and extends the given module" do
60
+ lambda { @klass.new.call_helper }.should raise_error
61
+ @klass.helper @helper
62
+ @klass.new.call_helper.should == "I work"
63
+ @klass.foo.should == "I work"
64
+ end
65
+ end
66
+
67
+ describe "filter method" do
68
+ before do
69
+ @klass = Class.new(Brainstem::Presenter)
70
+ end
71
+
72
+ it "creates an entry in the filters class ivar" do
73
+ @klass.filter(:foo, :default => true) { 1 }
74
+ @klass.filters[:foo][0].should eq({:default => true})
75
+ @klass.filters[:foo][1].should be_a(Proc)
76
+ end
77
+
78
+ it "accepts names without blocks" do
79
+ @klass.filter(:foo)
80
+ @klass.filters[:foo][1].should be_nil
81
+ end
82
+ end
83
+
84
+ describe "search method" do
85
+ before do
86
+ @klass = Class.new(Brainstem::Presenter)
87
+ end
88
+
89
+ it "creates an entry in the search class ivar" do
90
+ @klass.search do end
91
+ @klass.search_block.should be_a(Proc)
92
+ end
93
+ end
94
+ end
95
+
96
+ describe "post_process hooks" do
97
+ describe "adding object ids as strings" do
98
+ before do
99
+ post_presenter = Class.new(Brainstem::Presenter) do
100
+ presents Post
101
+
102
+ def present(model)
103
+ {
104
+ :body => model.body,
105
+ }
106
+ end
107
+ end
108
+
109
+ @presenter = post_presenter.new
110
+ @post = Post.first
111
+ end
112
+
113
+ it "outputs the associated object's id and type" do
114
+ data = @presenter.present_and_post_process(@post)
115
+ data[:id].should eq(@post.id.to_s)
116
+ data[:body].should eq(@post.body)
117
+ end
118
+ end
119
+
120
+ describe "converting dates and times" do
121
+ it "should convert all Time-and-date-like objects to iso8601" do
122
+ class TimePresenter < Brainstem::Presenter
123
+ def present(model)
124
+ {
125
+ :time => Time.now,
126
+ :date => Date.new,
127
+ :recursion => {
128
+ :time => Time.now,
129
+ :something => [Time.now, :else],
130
+ :foo => :bar
131
+ }
132
+ }
133
+ end
134
+ end
135
+
136
+ iso8601_time = /\d{4}-\d{2}-\d{2}T\d{2}\:\d{2}\:\d{2}[-+]\d{2}:\d{2}/
137
+ iso8601_date = /\d{4}-\d{2}-\d{2}/
138
+
139
+ struct = TimePresenter.new.present_and_post_process("something")
140
+ struct[:time].should =~ iso8601_time
141
+ struct[:date].should =~ iso8601_date
142
+ struct[:recursion][:time].should =~ iso8601_time
143
+ struct[:recursion][:something].first.should =~ iso8601_time
144
+ struct[:recursion][:something].last.should == :else
145
+ struct[:recursion][:foo].should == :bar
146
+ end
147
+ end
148
+
149
+ describe "outputting polymorphic associations" do
150
+ before do
151
+ some_presenter = Class.new(Brainstem::Presenter) do
152
+ presents Post
153
+
154
+ def present(model)
155
+ {
156
+ :body => model.body,
157
+ :subject => association(:subject),
158
+ :another_subject => association(:subject)
159
+ }
160
+ end
161
+ end
162
+
163
+ @presenter = some_presenter.new
164
+ @post = Post.first
165
+ end
166
+
167
+ it "outputs the associated object's id and type" do
168
+ data = @presenter.present_and_post_process(@post)
169
+ data[:subject_id].should eq(@post.subject_id.to_s)
170
+ data[:subject_type].should eq(@post.subject_type)
171
+ end
172
+
173
+ it "outputs custom names for an associated object's id and type" do
174
+ data = @presenter.present_and_post_process(@post)
175
+ data[:another_subject_id].should eq(@post.subject_id.to_s)
176
+ data[:another_subject_type].should eq(@post.subject_type)
177
+ end
178
+ end
179
+
180
+ describe "outputting associations" do
181
+ before do
182
+ some_presenter = Class.new(Brainstem::Presenter) do
183
+ presents Workspace
184
+
185
+ def present(model)
186
+ {
187
+ :updated_at => model.updated_at,
188
+ :tasks => association(:tasks),
189
+ :user => association(:user),
190
+ :something => association(:user),
191
+ :lead_user => association(:lead_user),
192
+ :lead_user_with_lambda => association { model.user },
193
+ :synthetic => association(:synthetic)
194
+ }
195
+ end
196
+ end
197
+
198
+ @presenter = some_presenter.new
199
+ @workspace = Workspace.find_by_title "bob workspace 1"
200
+ end
201
+
202
+ it "should not convert or return non-included associations, but should return <association>_id for belongs_to relationships, plus all fields" do
203
+ json = @presenter.present_and_post_process(@workspace, [])
204
+ json.keys.should =~ [:id, :updated_at, :something_id, :user_id]
205
+ end
206
+
207
+ it "should convert requested has_many associations (includes) into the <association>_ids format" do
208
+ @workspace.tasks.length.should > 0
209
+ @presenter.present_and_post_process(@workspace, [:tasks])[:task_ids].should =~ @workspace.tasks.map(&:id).map(&:to_s)
210
+ end
211
+
212
+ it "should convert requested belongs_to and has_one associations into the <association>_id format when requested" do
213
+ @presenter.present_and_post_process(@workspace, [:user])[:user_id].should == @workspace.user.id.to_s
214
+ end
215
+
216
+ it "converts non-association models into <model>_id format when they are requested" do
217
+ @presenter.present_and_post_process(@workspace, [:lead_user])[:lead_user_id].should == @workspace.lead_user.id.to_s
218
+ end
219
+
220
+ it "handles associations provided with lambdas" do
221
+ @presenter.present_and_post_process(@workspace, [:lead_user_with_lambda])[:lead_user_with_lambda_id].should == @workspace.lead_user.id.to_s
222
+ end
223
+
224
+ it "should return <association>_id fields when the given association ids exist on the model whether it is requested or not" do
225
+ @presenter.present_and_post_process(@workspace, [:user])[:user_id].should == @workspace.user_id.to_s
226
+
227
+ json = @presenter.present_and_post_process(@workspace, [])
228
+ json.keys.should =~ [:user_id, :something_id, :id, :updated_at]
229
+ json[:user_id].should == @workspace.user_id.to_s
230
+ json[:something_id].should == @workspace.user_id.to_s
231
+ end
232
+
233
+ it "should return null, not empty string when ids are missing" do
234
+ @workspace.user = nil
235
+ @workspace.tasks = []
236
+ @presenter.present_and_post_process(@workspace, [:lead_user_with_lambda])[:lead_user_with_lambda_id].should == nil
237
+ @presenter.present_and_post_process(@workspace, [:user])[:user_id].should == nil
238
+ @presenter.present_and_post_process(@workspace, [:something])[:something_id].should == nil
239
+ @presenter.present_and_post_process(@workspace, [:tasks])[:task_ids].should == []
240
+ end
241
+
242
+ context "when the model has an <association>_id method but no column" do
243
+ it "does not include the <association>_id field" do
244
+ def @workspace.synthetic_id
245
+ raise "this explodes because it's not an association"
246
+ end
247
+ @presenter.present_and_post_process(@workspace, []).should_not have_key(:synthetic_id)
248
+ end
249
+ end
250
+ end
251
+ end
252
+ end
@@ -0,0 +1,25 @@
1
+ require 'spec_helper'
2
+
3
+ describe Brainstem do
4
+ describe "default_namespace attribute" do
5
+ it "can be set and read" do
6
+ Brainstem.default_namespace = "something"
7
+ Brainstem.default_namespace.should eq("something")
8
+ end
9
+
10
+ it "returns 'none' if unset" do
11
+ Brainstem.default_namespace.should eq("none")
12
+ end
13
+ end
14
+
15
+ describe "presenter collection method" do
16
+ it "returns an instance of PresenterCollection" do
17
+ Brainstem.presenter_collection.should be_a(Brainstem::PresenterCollection)
18
+ end
19
+
20
+ it "accepts a namespace" do
21
+ Brainstem.presenter_collection("v1").should be_a(Brainstem::PresenterCollection)
22
+ Brainstem.presenter_collection("v1").should_not eq(Brainstem.presenter_collection)
23
+ end
24
+ end
25
+ end
@@ -0,0 +1,22 @@
1
+ require 'active_record'
2
+ require 'logger'
3
+ require 'rr'
4
+ require 'rspec'
5
+ require 'sqlite3'
6
+
7
+ require 'brainstem'
8
+ require_relative 'spec_helpers/db'
9
+ require_relative 'spec_helpers/cleanup'
10
+
11
+ RSpec.configure do |config|
12
+ config.mock_with :rr
13
+
14
+ config.before(:each) do
15
+ Brainstem.logger = Logger.new(StringIO.new)
16
+ end
17
+
18
+ config.after(:each) do
19
+ Brainstem.clear_collections!
20
+ Brainstem.default_namespace = nil
21
+ end
22
+ end
@@ -0,0 +1,23 @@
1
+ module Brainstem
2
+
3
+ def self.clear_collections!
4
+ presenter_collection.presenters.each do |klass, presenter|
5
+ presenter.clear_options!
6
+ end
7
+ @presenter_collection = {}
8
+ end
9
+
10
+ class Presenter
11
+ def self.clear_options!
12
+ @default_sort_order = nil
13
+ @filters = nil
14
+ @sort_orders = nil
15
+ @search_block = nil
16
+ end
17
+
18
+ def clear_options!
19
+ self.class.clear_options!
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,79 @@
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
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
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
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
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, 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
+ User.create!(:id => 1, :username => "bob")
64
+ User.create!(:id => 2, :username => "jane")
65
+
66
+ Workspace.create!(:id => 1, :user_id => 1, :title => "bob workspace 1", :description => "a")
67
+ Workspace.create!(:id => 2, :user_id => 1, :title => "bob workspace 2", :description => "1")
68
+ Workspace.create!(:id => 3, :user_id => 1, :title => "bob workspace 3", :description => "b")
69
+ Workspace.create!(:id => 4, :user_id => 1, :title => "bob workspace 4", :description => "2")
70
+ Workspace.create!(:id => 5, :user_id => 2, :title => "jane workspace 1", :description => "c")
71
+ Workspace.create!(:id => 6, :user_id => 2, :title => "jane workspace 2", :description => "3")
72
+
73
+ Task.create!(:id => 1, :workspace_id => 1, :name => "Buy milk")
74
+ Task.create!(:id => 2, :workspace_id => 1, :name => "Buy bananas")
75
+ Task.create!(:id => 3, :workspace_id => 1, :parent_id => 2, :name => "Green preferred")
76
+ Task.create!(:id => 4, :workspace_id => 1, :parent_id => 2, :name => "One bunch")
77
+
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!")
@@ -0,0 +1,39 @@
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
+ }
10
+ end
11
+ end
12
+
13
+ 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
+ }
21
+ end
22
+ end
23
+
24
+ class UserPresenter < Brainstem::Presenter
25
+ def present(model)
26
+ {
27
+ :username => model.username
28
+ }
29
+ end
30
+ end
31
+
32
+ class PostPresenter < Brainstem::Presenter
33
+ def present(model)
34
+ {
35
+ :body => model.body,
36
+ :subject => association(:subject)
37
+ }
38
+ end
39
+ end
metadata ADDED
@@ -0,0 +1,201 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: brainstem
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.2
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Sufyan Adam
9
+ - André Arko
10
+ - Andrew Cantino
11
+ - Katlyn Daniluk
12
+ - Reid Gillette
13
+ autorequire:
14
+ bindir: bin
15
+ cert_chain: []
16
+ date: 2013-04-29 00:00:00.000000000 Z
17
+ dependencies:
18
+ - !ruby/object:Gem::Dependency
19
+ name: activerecord
20
+ requirement: !ruby/object:Gem::Requirement
21
+ none: false
22
+ requirements:
23
+ - - ~>
24
+ - !ruby/object:Gem::Version
25
+ version: '3.0'
26
+ type: :runtime
27
+ prerelease: false
28
+ version_requirements: !ruby/object:Gem::Requirement
29
+ none: false
30
+ requirements:
31
+ - - ~>
32
+ - !ruby/object:Gem::Version
33
+ version: '3.0'
34
+ - !ruby/object:Gem::Dependency
35
+ name: rake
36
+ requirement: !ruby/object:Gem::Requirement
37
+ none: false
38
+ requirements:
39
+ - - ! '>='
40
+ - !ruby/object:Gem::Version
41
+ version: '0'
42
+ type: :development
43
+ prerelease: false
44
+ version_requirements: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ! '>='
48
+ - !ruby/object:Gem::Version
49
+ version: '0'
50
+ - !ruby/object:Gem::Dependency
51
+ name: redcarpet
52
+ requirement: !ruby/object:Gem::Requirement
53
+ none: false
54
+ requirements:
55
+ - - ! '>='
56
+ - !ruby/object:Gem::Version
57
+ version: '0'
58
+ type: :development
59
+ prerelease: false
60
+ version_requirements: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ - !ruby/object:Gem::Dependency
67
+ name: rr
68
+ requirement: !ruby/object:Gem::Requirement
69
+ none: false
70
+ requirements:
71
+ - - ! '>='
72
+ - !ruby/object:Gem::Version
73
+ version: '0'
74
+ type: :development
75
+ prerelease: false
76
+ version_requirements: !ruby/object:Gem::Requirement
77
+ none: false
78
+ requirements:
79
+ - - ! '>='
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
82
+ - !ruby/object:Gem::Dependency
83
+ name: rspec
84
+ requirement: !ruby/object:Gem::Requirement
85
+ none: false
86
+ requirements:
87
+ - - ! '>='
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ none: false
94
+ requirements:
95
+ - - ! '>='
96
+ - !ruby/object:Gem::Version
97
+ version: '0'
98
+ - !ruby/object:Gem::Dependency
99
+ name: sqlite3
100
+ requirement: !ruby/object:Gem::Requirement
101
+ none: false
102
+ requirements:
103
+ - - ! '>='
104
+ - !ruby/object:Gem::Version
105
+ version: '0'
106
+ type: :development
107
+ prerelease: false
108
+ version_requirements: !ruby/object:Gem::Requirement
109
+ none: false
110
+ requirements:
111
+ - - ! '>='
112
+ - !ruby/object:Gem::Version
113
+ version: '0'
114
+ - !ruby/object:Gem::Dependency
115
+ name: yard
116
+ requirement: !ruby/object:Gem::Requirement
117
+ none: false
118
+ requirements:
119
+ - - ! '>='
120
+ - !ruby/object:Gem::Version
121
+ version: '0'
122
+ type: :development
123
+ prerelease: false
124
+ version_requirements: !ruby/object:Gem::Requirement
125
+ none: false
126
+ requirements:
127
+ - - ! '>='
128
+ - !ruby/object:Gem::Version
129
+ version: '0'
130
+ description: Brainstem allows you to create rich API presenters that know how to filter,
131
+ sort, and include associations.
132
+ email:
133
+ - dev@mavenlink.com
134
+ executables: []
135
+ extensions: []
136
+ extra_rdoc_files: []
137
+ files:
138
+ - brainstem.gemspec
139
+ - Gemfile
140
+ - Gemfile.lock
141
+ - Guardfile
142
+ - lib/brainstem/association_field.rb
143
+ - lib/brainstem/controller_methods.rb
144
+ - lib/brainstem/engine.rb
145
+ - lib/brainstem/presenter.rb
146
+ - lib/brainstem/presenter_collection.rb
147
+ - lib/brainstem/time_classes.rb
148
+ - lib/brainstem/version.rb
149
+ - lib/brainstem.rb
150
+ - LICENSE
151
+ - Rakefile
152
+ - README.md
153
+ - spec/brainstem/controller_methods_spec.rb
154
+ - spec/brainstem/presenter_collection_spec.rb
155
+ - spec/brainstem/presenter_spec.rb
156
+ - spec/brainstem_spec.rb
157
+ - spec/spec_helper.rb
158
+ - spec/spec_helpers/cleanup.rb
159
+ - spec/spec_helpers/db.rb
160
+ - spec/spec_helpers/presenters.rb
161
+ homepage: http://developer.mavenlink.com
162
+ licenses:
163
+ - MIT
164
+ post_install_message:
165
+ rdoc_options: []
166
+ require_paths:
167
+ - lib
168
+ required_ruby_version: !ruby/object:Gem::Requirement
169
+ none: false
170
+ requirements:
171
+ - - ! '>='
172
+ - !ruby/object:Gem::Version
173
+ version: '0'
174
+ segments:
175
+ - 0
176
+ hash: -2131973950543043950
177
+ required_rubygems_version: !ruby/object:Gem::Requirement
178
+ none: false
179
+ requirements:
180
+ - - ! '>='
181
+ - !ruby/object:Gem::Version
182
+ version: '0'
183
+ segments:
184
+ - 0
185
+ hash: -2131973950543043950
186
+ requirements: []
187
+ rubyforge_project:
188
+ rubygems_version: 1.8.25
189
+ signing_key:
190
+ specification_version: 3
191
+ summary: ActiveRecord presenters with a rich request API
192
+ test_files:
193
+ - spec/brainstem/controller_methods_spec.rb
194
+ - spec/brainstem/presenter_collection_spec.rb
195
+ - spec/brainstem/presenter_spec.rb
196
+ - spec/brainstem_spec.rb
197
+ - spec/spec_helper.rb
198
+ - spec/spec_helpers/cleanup.rb
199
+ - spec/spec_helpers/db.rb
200
+ - spec/spec_helpers/presenters.rb
201
+ has_rdoc: