roar-extensions 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,16 @@
1
+ module RoarExtensions
2
+ module ResourceLinks
3
+ private
4
+ def merge_links(collection, &presenter_generator)
5
+ collection.inject({}) do |acc, element|
6
+ acc.merge(presenter_generator.call(element).to_hash)
7
+ end
8
+ end
9
+
10
+ def resource_link_json(link_hash)
11
+ link_hash.inject({}) do |acc, (rel, href)|
12
+ acc.merge(LinkPresenter.new(rel, href).to_hash)
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,3 @@
1
+ module RoarExtensions
2
+ VERSION = "0.0.2"
3
+ end
@@ -0,0 +1,27 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/roar_extensions/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Donald Plummer", "Michael Xavier"]
6
+ gem.email = ["developers@crystalcommerce.com"]
7
+ gem.description = %q{Useful extensions to roar}
8
+ gem.summary = %q{Useful extensions to roar}
9
+ gem.homepage = ""
10
+
11
+ gem.files = `git ls-files`.split($\)
12
+ gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
13
+ gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
14
+ gem.name = "roar-extensions"
15
+ gem.require_paths = ["lib"]
16
+ gem.version = RoarExtensions::VERSION
17
+
18
+ gem.add_dependency("roar", "0.11.3")
19
+ gem.add_dependency("activesupport", ">= 2.3.14")
20
+
21
+ gem.add_development_dependency "pry"
22
+ gem.add_development_dependency "pry-nav"
23
+ gem.add_development_dependency "rake", "~>0.9.2"
24
+ gem.add_development_dependency "rspec", "~>2.10.0"
25
+ gem.add_development_dependency "guard", "~>1.2.1"
26
+ gem.add_development_dependency "guard-rspec", "~>1.1.0"
27
+ end
@@ -0,0 +1,17 @@
1
+ require 'spec_helper'
2
+ require 'roar_extensions'
3
+
4
+ module RoarExtensions
5
+ describe DestroyedRecordPresenter do
6
+ class DestroyedRecordPresenterTest
7
+ include DestroyedRecordPresenter
8
+ root_element :foo
9
+ end
10
+
11
+ subject { DestroyedRecordPresenterTest.new(123) }
12
+
13
+ it "has just the id" do
14
+ subject.to_hash.should == {'foo' => {'id' => 123}}
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,52 @@
1
+ require 'spec_helper'
2
+ require 'roar_extensions'
3
+
4
+ module RoarExtensions::Helpers
5
+ describe EmbeddedParameterParsing do
6
+ class MyTestController
7
+ def self.before_filter(*args)
8
+ # yup
9
+ end
10
+ end
11
+
12
+ subject { MyTestController.new }
13
+
14
+ describe "including into a class" do
15
+ it "calls before_filter" do
16
+ MyTestController.should_receive(:before_filter).with(:parse_embedded_params_filter)
17
+ MyTestController.send(:include, EmbeddedParameterParsing)
18
+ end
19
+ end
20
+
21
+ describe "parse_embedded_params" do
22
+ before(:each) do
23
+ MyTestController.send(:include, EmbeddedParameterParsing)
24
+ end
25
+ it "returns empty array for blank string" do
26
+ subject.parse_embedded_params("").should == []
27
+ end
28
+
29
+ it "returns empty array for nil" do
30
+ subject.parse_embedded_params(nil).should == []
31
+ end
32
+
33
+ it "returns the given list if comma-separated as symbols" do
34
+ subject.parse_embedded_params("foo,bar").should == [:foo, :bar]
35
+ end
36
+
37
+ it "recursively parses nested embeddings" do
38
+ subject.parse_embedded_params("line_items:product,line_items:variant,customer,address:ip_address").should == [
39
+ :customer,
40
+ {:line_items => [:product, :variant], :address => [:ip_address]}
41
+ ]
42
+ end
43
+
44
+ it "recurses multiple levels" do
45
+ subject.parse_embedded_params("customer,line_items:product:category").should == [
46
+ :customer,
47
+ {:line_items => [{:product => [:category]}]}
48
+ ]
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,48 @@
1
+ require 'spec_helper'
2
+ require 'roar_extensions'
3
+
4
+ module RoarExtensions
5
+ describe LinkPresenter do
6
+ subject { LinkPresenter.new('search_engine', 'http://google.com') }
7
+
8
+ describe "#==" do
9
+ it "returns true if href, rel, and title match" do
10
+ subject.should == LinkPresenter.new('search_engine', 'http://google.com')
11
+ end
12
+
13
+ it "returns false if href, rel, or title differ" do
14
+ subject.should_not == LinkPresenter.new('search_engine', 'http://bing.com')
15
+ subject.should_not == LinkPresenter.new('weee', 'http://google.com')
16
+ subject.should_not == LinkPresenter.new('search_engine', 'http://google.com', 'cows')
17
+ end
18
+ end
19
+
20
+ context "no title" do
21
+ its(:as_json) do
22
+ should == { 'search_engine' => {:href => 'http://google.com'} }
23
+ end
24
+
25
+ it "aliases to_hash to as_json" do
26
+ subject.to_hash.should == subject.as_json
27
+ end
28
+ end
29
+
30
+ context "title given" do
31
+ subject { LinkPresenter.new('search_engine',
32
+ 'http://google.com',
33
+ 'Cool Search') }
34
+ its(:as_json) do
35
+ should == {
36
+ 'search_engine' => {
37
+ :href => 'http://google.com',
38
+ :title => 'Cool Search'
39
+ }
40
+ }
41
+ end
42
+
43
+ it "aliases to_hash to as_json" do
44
+ subject.to_hash.should == subject.as_json
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,19 @@
1
+ require 'spec_helper'
2
+ require 'roar_extensions'
3
+
4
+ module RoarExtensions
5
+ describe MoneyPresenter do
6
+ let(:currency) { stub(:iso_code => "USD") }
7
+ let(:money) { stub(:currency => currency, :cents => 100) }
8
+
9
+ subject { MoneyPresenter.new(money) }
10
+
11
+ it "has cents" do
12
+ subject.to_hash['money']['cents'].should == 100
13
+ end
14
+
15
+ it "has currency" do
16
+ subject.to_hash['money']['currency'].should == "USD"
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,176 @@
1
+ require 'spec_helper'
2
+ require "roar_extensions"
3
+
4
+ module RoarExtensions
5
+ describe PaginatedCollectionPresenter do
6
+ class TestEntry
7
+ include RoarExtensions::Presenter
8
+
9
+ attr_reader :name, :age
10
+
11
+ def initialize(name, age)
12
+ @name = name
13
+ @age = age
14
+ end
15
+
16
+ property :name
17
+ property :age
18
+ property :lowercased_name, :from => :name_downcase
19
+
20
+ def name_downcase
21
+ name.downcase
22
+ end
23
+
24
+ end
25
+
26
+ let(:current_page) { 1 }
27
+ let(:next_page) { 2 }
28
+ let(:previous_page) { nil }
29
+ let(:entries) {[
30
+ TestEntry.new("Bob", 41)
31
+ ]}
32
+ let(:paginated_result) do
33
+ mock("Paginated Result", :total_pages => 3,
34
+ :current_page => current_page,
35
+ :next_page => next_page,
36
+ :previous_page => previous_page,
37
+ :total_entries => 3,
38
+ :collect => entries,
39
+ :per_page => 1)
40
+ end
41
+ let(:base_path) { "/things" }
42
+ let(:json_options) {{}}
43
+
44
+ describe "#to_hash" do
45
+ let(:presenter) { PaginatedCollectionPresenter.new(paginated_result,
46
+ base_path)}
47
+ subject { JSON.parse(presenter.to_json(json_options))['paginated_collection'] }
48
+
49
+ it "includes the total pages" do
50
+ subject['total_pages'].should == 3
51
+ end
52
+
53
+ it "includes the total entries" do
54
+ subject['total_entries'].should == 3
55
+ end
56
+
57
+ it "includes the per page" do
58
+ subject['per_page'].should == 1
59
+ end
60
+
61
+ it "includes the self link" do
62
+ subject['_links']['self'].should == {"href" => "/things"}
63
+ end
64
+
65
+ it "includes the next_page link" do
66
+ subject['_links']['next_page'].should == {"href" => "/things?page=2"}
67
+ end
68
+
69
+ it "does not include the previous_page link on the first page" do
70
+ subject['_links'].should_not have_key('previous_page')
71
+ end
72
+
73
+ it "includes the paginated results under the entries key" do
74
+ subject['entries'].should == [{'name' => 'Bob',
75
+ 'age' => 41,
76
+ 'lowercased_name' => 'bob'}]
77
+ end
78
+
79
+ it "includes current_page" do
80
+ subject["current_page"].should == 1
81
+ end
82
+
83
+ it "includes next_page" do
84
+ subject["next_page"].should == 2
85
+ end
86
+
87
+ it "includes previous_page" do
88
+ subject["previous_page"].should == nil
89
+ end
90
+
91
+ context "on last page" do
92
+ let(:previous_page) { 2 }
93
+ let(:current_page) { 3 }
94
+ let(:next_page) { nil }
95
+
96
+ it "includes the self link" do
97
+ subject['_links']['self'].should == {"href" => "/things?page=3"}
98
+ end
99
+
100
+ it "does not include the next_page link" do
101
+ subject['_links'].should_not have_key('next_page')
102
+ end
103
+
104
+ it "includes the previous_page link" do
105
+ subject['_links']['previous_page'].should == {"href" => "/things?page=2"}
106
+ end
107
+
108
+ it "includes current_page" do
109
+ subject["current_page"].should == 3
110
+ end
111
+
112
+ it "includes next_page" do
113
+ subject["next_page"].should == nil
114
+ end
115
+
116
+ it "includes previous_page" do
117
+ subject["previous_page"].should == 2
118
+ end
119
+ end
120
+
121
+ context "middle page" do
122
+ let(:previous_page) { 1 }
123
+ let(:current_page) { 2 }
124
+ let(:next_page) { 3 }
125
+
126
+ it "includes the self link" do
127
+ subject['_links']['self'].should == {"href" => "/things?page=2"}
128
+ end
129
+
130
+ it "includes the next_page link" do
131
+ subject['_links']['next_page'].should == {"href" => "/things?page=3"}
132
+ end
133
+
134
+ it "includes the previous_page link" do
135
+ subject['_links']['previous_page'].should == {"href" => "/things"}
136
+ end
137
+
138
+ it "includes current_page" do
139
+ subject["current_page"].should == 2
140
+ end
141
+
142
+ it "includes next_page" do
143
+ subject["next_page"].should == 3
144
+ end
145
+
146
+ it "includes previous_page" do
147
+ subject["previous_page"].should == 1
148
+ end
149
+ end
150
+
151
+ context "attribute whitelisting" do
152
+ let(:json_options) {{:include => [:name]}}
153
+
154
+ it "the include option is passed to the entries" do
155
+ subject['entries'].should == [{'name' => 'Bob'}]
156
+ end
157
+
158
+ context "using the :from property option" do
159
+ let(:json_options) {{:include => ["lowercased_name"]}}
160
+
161
+ it "the include option is passed to the entries" do
162
+ subject['entries'].should == [{'lowercased_name' => 'bob'}]
163
+ end
164
+ end
165
+ end
166
+
167
+ context "attribute blacklisting" do
168
+ let(:json_options) {{:exclude => [:name]}}
169
+
170
+ it "the include option is passed to the entries" do
171
+ subject['entries'].should == [{'age' => 41, 'lowercased_name' => 'bob'}]
172
+ end
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,167 @@
1
+ require 'spec_helper'
2
+ require 'roar_extensions'
3
+
4
+ module RoarExtensions
5
+ describe Presenter do
6
+
7
+ class TestPhotoPresenter
8
+ include Presenter
9
+
10
+ root_element :photo
11
+
12
+ delegated_property :position
13
+ end
14
+
15
+ class TestProductPresenter
16
+ include Presenter
17
+
18
+ root_element :product
19
+
20
+ delegated_property :id, :always_include => true
21
+ delegated_property :name
22
+ delegated_property :is_buying, :from => :buying?
23
+ delegated_property :msrp, :as => MoneyPresenter
24
+ property :possible_variants_count
25
+ property :min_sell_price, :as => MoneyPresenter
26
+ property :catalog_links, :from => :catalog_links_as_json
27
+ delegated_collection :photos, :as => TestPhotoPresenter, :embedded => true
28
+
29
+ link(:rel => "self") { "/v1/products/#{record.id}" }
30
+ link(:rel => "category") { "/v1/categories/#{record.category_id}" }
31
+ link(:rel => "related_products") { "/v1/products/#{record.id}/related" }
32
+
33
+ def initialize(record, options = {})
34
+ super(record)
35
+
36
+ @embedded = options.fetch(:embedded, [])
37
+ end
38
+
39
+ private
40
+
41
+ def min_sell_price
42
+ OpenStruct.new(:cents => 499,
43
+ :currency => OpenStruct.new(:iso_code => 'USD'))
44
+ end
45
+
46
+ def possible_variants_count
47
+ 3
48
+ end
49
+
50
+ def catalog_links_as_json
51
+ {'omg' => 'wtf'}
52
+ end
53
+ end
54
+
55
+ let(:product) {
56
+ mock("Product Record",
57
+ :id => 9001,
58
+ :category_id => 800,
59
+ :name => "Worship",
60
+ :photos => [photo],
61
+ :msrp => stub(:cents => 300, :currency => stub(:iso_code => 'USD')),
62
+ :buying? => true)
63
+ }
64
+
65
+ let(:photo) {
66
+ mock("Photo", :position => 1)
67
+ }
68
+
69
+ let(:options) { {} }
70
+ let(:json_options) { {} }
71
+
72
+ let(:presenter) { TestProductPresenter.new(product, options) }
73
+
74
+ subject { presenter }
75
+
76
+ it "aliases to_hash to as_json" do
77
+ subject.to_hash.should == subject.as_json
78
+ end
79
+
80
+ context "as_json" do
81
+ subject { presenter.as_json(json_options)['product'] }
82
+
83
+ it "has a name" do
84
+ subject['name'].should == 'Worship'
85
+ end
86
+
87
+ it "has a possible_variants_count" do
88
+ subject['possible_variants_count'].should == 3
89
+ end
90
+
91
+ it "has a catalog_links" do
92
+ subject['catalog_links'].should == {'omg' => 'wtf'}
93
+ end
94
+
95
+ it "has an msrp as money" do
96
+ subject['msrp'].should == {
97
+ 'money' => {
98
+ 'cents' => 300,
99
+ 'currency' => 'USD'
100
+ }
101
+ }
102
+ end
103
+
104
+ it "has a min sell price as money" do
105
+ subject['min_sell_price'].should == {
106
+ 'money' => {
107
+ 'cents' => 499,
108
+ 'currency' => 'USD'
109
+ }
110
+ }
111
+ end
112
+
113
+ it "has an id" do
114
+ subject['id'].should == 9001
115
+ end
116
+
117
+ it "has the buyingness" do
118
+ subject['is_buying'].should == true
119
+ end
120
+
121
+
122
+ it "has api _links" do
123
+ subject['_links'].should == {
124
+ 'self' => { :href => "/v1/products/9001" },
125
+ 'related_products' => { :href => "/v1/products/9001/related" },
126
+ 'category' => { :href => '/v1/categories/800'}
127
+ }
128
+ end
129
+
130
+ it "does not embed" do
131
+ subject.should_not have_key('_embedded')
132
+ end
133
+
134
+ context "embedding of photos enabled" do
135
+ let(:options) { {:embedded => [:photos]} }
136
+
137
+ it "has embedded photos" do
138
+ subject['_embedded']['photos'].should == [
139
+ {
140
+ 'photo' => {
141
+ 'position' => 1
142
+ }
143
+ }
144
+ ]
145
+ end
146
+ end
147
+
148
+ context "always renders nil attributes" do
149
+ before(:each) do
150
+ product.stub(:name).and_return(nil)
151
+ end
152
+
153
+ it "has a null name" do
154
+ subject.fetch('name').should == nil
155
+ end
156
+ end
157
+
158
+ context "limiting attributes returned" do
159
+ let(:json_options) {{ :include => [:name] }}
160
+
161
+ it "limits to the attributes requested, plus required attributes" do
162
+ subject.keys.should == ['id', 'name']
163
+ end
164
+ end
165
+ end
166
+ end
167
+ end