praxis 0.21 → 2.0.pre.3

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 (102) hide show
  1. checksums.yaml +4 -4
  2. data/.travis.yml +8 -15
  3. data/CHANGELOG.md +328 -299
  4. data/CONTRIBUTING.md +4 -4
  5. data/README.md +11 -9
  6. data/lib/api_browser/app/js/directives/attribute_table.js +2 -1
  7. data/lib/api_browser/app/js/directives/conditional_requirements.js +13 -0
  8. data/lib/api_browser/app/js/directives/type_placeholder.js +10 -1
  9. data/lib/api_browser/app/js/factories/normalize_attributes.js +4 -2
  10. data/lib/api_browser/app/js/factories/template_for.js +5 -2
  11. data/lib/api_browser/app/js/filters/has_requirement.js +14 -0
  12. data/lib/api_browser/app/js/filters/tag_requirement.js +13 -0
  13. data/lib/api_browser/app/sass/praxis.scss +11 -0
  14. data/lib/api_browser/app/views/action.html +2 -2
  15. data/lib/api_browser/app/views/directives/attribute_description/member_options.html +2 -2
  16. data/lib/api_browser/app/views/directives/attribute_table.html +1 -1
  17. data/lib/api_browser/app/views/type.html +1 -1
  18. data/lib/api_browser/app/views/type/details.html +2 -2
  19. data/lib/api_browser/app/views/types/embedded/array.html +2 -0
  20. data/lib/api_browser/app/views/types/embedded/default.html +3 -1
  21. data/lib/api_browser/app/views/types/embedded/requirements.html +6 -0
  22. data/lib/api_browser/app/views/types/embedded/single_req.html +9 -0
  23. data/lib/api_browser/app/views/types/embedded/struct.html +14 -2
  24. data/lib/api_browser/app/views/types/standalone/array.html +1 -1
  25. data/lib/api_browser/app/views/types/standalone/struct.html +2 -1
  26. data/lib/api_browser/package.json +1 -1
  27. data/lib/praxis.rb +9 -3
  28. data/lib/praxis/action_definition.rb +1 -1
  29. data/lib/praxis/action_definition/headers_dsl_compiler.rb +1 -1
  30. data/lib/praxis/application.rb +1 -9
  31. data/lib/praxis/bootloader.rb +1 -4
  32. data/lib/praxis/config.rb +1 -1
  33. data/lib/praxis/dispatcher.rb +10 -6
  34. data/lib/praxis/docs/generator.rb +2 -1
  35. data/lib/praxis/extensions/attribute_filtering/active_record_filter_query_builder.rb +180 -0
  36. data/lib/praxis/extensions/attribute_filtering/filtering_params.rb +273 -0
  37. data/lib/praxis/extensions/attribute_filtering/sequel_filter_query_builder.rb +125 -0
  38. data/lib/praxis/extensions/field_selection.rb +1 -9
  39. data/lib/praxis/extensions/field_selection/active_record_query_selector.rb +51 -0
  40. data/lib/praxis/extensions/field_selection/sequel_query_selector.rb +61 -0
  41. data/lib/praxis/extensions/rails_compat.rb +2 -0
  42. data/lib/praxis/extensions/rails_compat/request_methods.rb +19 -0
  43. data/lib/praxis/handlers/xml.rb +1 -1
  44. data/lib/praxis/mapper/active_model_compat.rb +98 -0
  45. data/lib/praxis/mapper/resource.rb +242 -0
  46. data/lib/praxis/mapper/selector_generator.rb +149 -0
  47. data/lib/praxis/mapper/sequel_compat.rb +76 -0
  48. data/lib/praxis/media_type_identifier.rb +2 -1
  49. data/lib/praxis/middleware_app.rb +20 -2
  50. data/lib/praxis/multipart/parser.rb +14 -2
  51. data/lib/praxis/notifications.rb +1 -1
  52. data/lib/praxis/plugins/mapper_plugin.rb +64 -0
  53. data/lib/praxis/plugins/rails_plugin.rb +104 -0
  54. data/lib/praxis/request.rb +7 -1
  55. data/lib/praxis/request_superclassing.rb +11 -0
  56. data/lib/praxis/resource_definition.rb +5 -5
  57. data/lib/praxis/response.rb +1 -1
  58. data/lib/praxis/route.rb +1 -1
  59. data/lib/praxis/routing_config.rb +1 -1
  60. data/lib/praxis/trait.rb +1 -1
  61. data/lib/praxis/types/media_type_common.rb +2 -2
  62. data/lib/praxis/types/multipart.rb +1 -1
  63. data/lib/praxis/types/multipart_array.rb +2 -2
  64. data/lib/praxis/types/multipart_array/part_definition.rb +1 -1
  65. data/lib/praxis/version.rb +1 -1
  66. data/praxis.gemspec +14 -13
  67. data/spec/functional_spec.rb +4 -7
  68. data/spec/praxis/action_definition_spec.rb +1 -1
  69. data/spec/praxis/application_spec.rb +1 -1
  70. data/spec/praxis/collection_spec.rb +3 -2
  71. data/spec/praxis/config_spec.rb +2 -2
  72. data/spec/praxis/extensions/field_selection/active_record_query_selector_spec.rb +106 -0
  73. data/spec/praxis/extensions/field_selection/sequel_query_selector_spec.rb +147 -0
  74. data/spec/praxis/extensions/field_selection/support/spec_resources_active_model.rb +130 -0
  75. data/spec/praxis/extensions/field_selection/support/spec_resources_sequel.rb +106 -0
  76. data/spec/praxis/handlers/xml_spec.rb +2 -2
  77. data/spec/praxis/mapper/resource_spec.rb +169 -0
  78. data/spec/praxis/mapper/selector_generator_spec.rb +293 -0
  79. data/spec/praxis/media_type_spec.rb +0 -10
  80. data/spec/praxis/middleware_app_spec.rb +29 -9
  81. data/spec/praxis/request_stages/action_spec.rb +8 -1
  82. data/spec/praxis/response_definition_spec.rb +7 -4
  83. data/spec/praxis/response_spec.rb +1 -1
  84. data/spec/praxis/responses/internal_server_error_spec.rb +2 -2
  85. data/spec/praxis/responses/validation_error_spec.rb +2 -2
  86. data/spec/praxis/router_spec.rb +1 -1
  87. data/spec/spec_app/app/controllers/instances.rb +1 -1
  88. data/spec/spec_app/config/environment.rb +3 -21
  89. data/spec/spec_helper.rb +11 -15
  90. data/spec/support/be_deep_equal_matcher.rb +39 -0
  91. data/spec/support/spec_resources.rb +124 -0
  92. data/tasks/thor/templates/generator/empty_app/Gemfile +3 -3
  93. metadata +102 -77
  94. data/.ruby-version +0 -1
  95. data/lib/praxis/extensions/mapper_selectors.rb +0 -16
  96. data/lib/praxis/media_type_collection.rb +0 -127
  97. data/lib/praxis/plugins/praxis_mapper_plugin.rb +0 -246
  98. data/lib/praxis/stats.rb +0 -113
  99. data/spec/praxis/media_type_collection_spec.rb +0 -157
  100. data/spec/praxis/plugins/praxis_mapper_plugin_spec.rb +0 -142
  101. data/spec/praxis/stats_spec.rb +0 -9
  102. data/spec/spec_app/app/models/person.rb +0 -3
@@ -0,0 +1,130 @@
1
+ require 'active_record'
2
+
3
+ require 'praxis/mapper/active_model_compat'
4
+
5
+ # Creates a new in-memory DB, and the necessary tables (and mini-seeds) for the models in this file
6
+ def create_tables
7
+
8
+ ActiveRecord::Base.establish_connection(
9
+ adapter: 'sqlite3',
10
+ dbfile: ':memory:',
11
+ database: ':memory:'
12
+ )
13
+
14
+ ActiveRecord::Schema.define do
15
+ ActiveRecord::Migration.suppress_messages do
16
+ create_table :active_books do |table|
17
+ table.column :simple_name, :string
18
+ table.column :added_column, :string
19
+ table.column :category_uuid, :string
20
+ table.column :author_id, :integer
21
+ end
22
+
23
+ create_table :active_authors do |table|
24
+ table.column :name, :string
25
+ end
26
+
27
+ create_table :active_categories do |table|
28
+ table.column :uuid, :string
29
+ table.column :name, :string
30
+ end
31
+
32
+ create_table :active_tags do |table|
33
+ table.column :name, :string
34
+ end
35
+
36
+ create_table :active_taggings do |table|
37
+ table.column :book_id, :integer
38
+ table.column :tag_id, :integer
39
+ end
40
+ end
41
+ end
42
+ end
43
+
44
+ create_tables
45
+
46
+ class ActiveBook < ActiveRecord::Base
47
+ include Praxis::Mapper::ActiveModelCompat
48
+
49
+ belongs_to :category, class_name: 'ActiveCategory', foreign_key: :category_uuid, primary_key: :uuid
50
+ belongs_to :author, class_name: 'ActiveAuthor'
51
+ has_many :taggings, class_name: 'ActiveTagging', foreign_key: :book_id
52
+ has_many :tags, class_name: 'ActiveTag', through: :taggings
53
+ end
54
+
55
+ class ActiveAuthor < ActiveRecord::Base
56
+ include Praxis::Mapper::ActiveModelCompat
57
+ has_many :books, class_name: 'ActiveBook', foreign_key: :author_id
58
+ end
59
+
60
+ class ActiveCategory < ActiveRecord::Base
61
+ include Praxis::Mapper::ActiveModelCompat
62
+ has_many :books, class_name: 'ActiveBook', primary_key: :uuid, foreign_key: :category_uuid
63
+ end
64
+
65
+ class ActiveTag < ActiveRecord::Base
66
+ include Praxis::Mapper::ActiveModelCompat
67
+ end
68
+
69
+ class ActiveTagging < ActiveRecord::Base
70
+ include Praxis::Mapper::ActiveModelCompat
71
+ belongs_to :book, class_name: 'ActiveBook', foreign_key: :book_id
72
+ belongs_to :tag, class_name: 'ActiveTag', foreign_key: :tag_id
73
+ end
74
+
75
+
76
+ # A set of resource classes for use in specs
77
+ class ActiveBaseResource < Praxis::Mapper::Resource
78
+ end
79
+
80
+ class ActiveAuthorResource < ActiveBaseResource
81
+ model ActiveAuthor
82
+
83
+ property :display_name, dependencies: [:name]
84
+ end
85
+
86
+ class ActiveCategoryResource < ActiveBaseResource
87
+ model ActiveCategory
88
+ end
89
+
90
+ class ActiveTagResource < ActiveBaseResource
91
+ model ActiveTag
92
+ end
93
+
94
+ class ActiveBookResource < ActiveBaseResource
95
+ model ActiveBook
96
+
97
+ # Forces to add an extra column (added_column)...and yet another (author_id) that will serve
98
+ # to check that if that's already automatically added due to an association, it won't interfere or duplicate
99
+ property :author, dependencies: [:author, :added_column, :author_id]
100
+
101
+ property :name, dependencies: [:simple_name]
102
+ end
103
+
104
+
105
+ def seed_data
106
+ cat1 = ActiveCategory.create( id: 1 , uuid: 'deadbeef1', name: 'cat1' )
107
+ cat2 = ActiveCategory.create( id: 2 , uuid: 'deadbeef2', name: 'cat2' )
108
+
109
+ author1 = ActiveAuthor.create( id: 11, name: 'author1' )
110
+ author2 = ActiveAuthor.create( id: 22, name: 'author2' )
111
+
112
+ tag_blue = ActiveTag.create(id: 1 , name: 'blue' )
113
+ tag_red = ActiveTag.create(id: 2 , name: 'red' )
114
+
115
+ book1 = ActiveBook.create( id: 1 , simple_name: 'Book1', category_uuid: 'deadbeef1')
116
+ book1.author = author1
117
+ book1.category = cat1
118
+ book1.save
119
+ ActiveTagging.create(book: book1, tag: tag_blue)
120
+ ActiveTagging.create(book: book1, tag: tag_red)
121
+
122
+
123
+ book2 = ActiveBook.create( id: 2 , simple_name: 'Book2', category_uuid: 'deadbeef1')
124
+ book2.author = author2
125
+ book2.category = cat2
126
+ book2.save
127
+ ActiveTagging.create(book: book2, tag: tag_red)
128
+ end
129
+
130
+ seed_data
@@ -0,0 +1,106 @@
1
+ require 'sequel'
2
+
3
+ require 'praxis/mapper/sequel_compat'
4
+
5
+
6
+ # Creates a new in-memory DB, and the necessary tables (and mini-seeds) for the sequel models in this file
7
+ def create_and_seed_tables
8
+ sequeldb = Sequel.sqlite
9
+ # sequeldb.loggers = [Logger.new($stdout)] # Uncomment to see sequel logs
10
+
11
+ sequeldb.create_table! :sequel_simple_models do
12
+ primary_key :id
13
+ String :simple_name
14
+ Integer :parent_id
15
+ String :parent_uuid
16
+ Integer :other_model_id
17
+ String :added_column
18
+ end
19
+ sequeldb.create_table! :sequel_other_models do
20
+ primary_key :id
21
+ end
22
+ sequeldb.create_table! :sequel_parent_models do
23
+ primary_key :id
24
+ String :uuid
25
+ end
26
+ sequeldb.create_table! :sequel_tag_models do
27
+ primary_key :id
28
+ String :tag_name
29
+ end
30
+ sequeldb.create_table! :sequel_simple_models_sequel_tag_models do
31
+ Integer :sequel_simple_model_id
32
+ Integer :tag_id
33
+ end
34
+
35
+ sequeldb[:sequel_parent_models] << { id: 1 , uuid: 'deadbeef1'}
36
+ sequeldb[:sequel_parent_models] << { id: 2 , uuid: 'deadbeef2'}
37
+
38
+ sequeldb[:sequel_other_models] << { id: 11 }
39
+ sequeldb[:sequel_other_models] << { id: 22 }
40
+
41
+ sequeldb[:sequel_tag_models] << { id: 1 , tag_name: 'blue' }
42
+ sequeldb[:sequel_tag_models] << { id: 2 , tag_name: 'red' }
43
+
44
+ # Simple model 1 is tagged as blue and red
45
+ sequeldb[:sequel_simple_models_sequel_tag_models] << { sequel_simple_model_id: 1, tag_id: 1 }
46
+ sequeldb[:sequel_simple_models_sequel_tag_models] << { sequel_simple_model_id: 1, tag_id: 2 }
47
+ # Simple model 2 is tagged as red
48
+ sequeldb[:sequel_simple_models_sequel_tag_models] << { sequel_simple_model_id: 2, tag_id: 2 }
49
+
50
+ # It's weird to have a parent id and parent uuid (which points to different actual parents)
51
+ # But it allows us to check pointing to both PKs and not PK columns
52
+ sequeldb[:sequel_simple_models] << { id: 1 , simple_name: 'Simple1', parent_id: 1, other_model_id: 11, parent_uuid: 'deadbeef1'}
53
+ sequeldb[:sequel_simple_models] << { id: 2 , simple_name: 'Simple2', parent_id: 2, other_model_id: 22, parent_uuid: 'deadbeef1'}
54
+ end
55
+
56
+ create_and_seed_tables
57
+
58
+ class SequelSimpleModel < Sequel::Model
59
+ include Praxis::Mapper::SequelCompat
60
+
61
+ many_to_one :parent, class: 'SequelParentModel'
62
+ many_to_one :other_model, class: 'SequelOtherModel'
63
+ many_to_many :tags, class: 'SequelTagModel'
64
+ end
65
+
66
+ class SequelOtherModel < Sequel::Model
67
+ include Praxis::Mapper::SequelCompat
68
+ end
69
+
70
+ class SequelParentModel < Sequel::Model
71
+ include Praxis::Mapper::SequelCompat
72
+ one_to_many :children, class: 'SequelSimpleModel', primary_key: :uuid, key: :parent_uuid
73
+ end
74
+
75
+ class SequelTagModel < Sequel::Model
76
+ include Praxis::Mapper::SequelCompat
77
+ end
78
+
79
+
80
+ # A set of resource classes for use in specs
81
+ class SequelBaseResource < Praxis::Mapper::Resource
82
+ end
83
+
84
+ class SequelOtherResource < SequelBaseResource
85
+ model SequelOtherModel
86
+
87
+ property :display_name, dependencies: [:name]
88
+ end
89
+
90
+ class SequelParentResource < SequelBaseResource
91
+ model SequelParentModel
92
+ end
93
+
94
+ class SequelTagResource < SequelBaseResource
95
+ model SequelTagModel
96
+ end
97
+
98
+ class SequelSimpleResource < SequelBaseResource
99
+ model SequelSimpleModel
100
+
101
+ # Forces to add an extra column (added_column)...and yet another (parent_id) that will serve
102
+ # to check that if that's already automatically added due to an association, it won't interfere or duplicate
103
+ property :parent, dependencies: [:parent, :added_column, :parent_id]
104
+
105
+ property :name, dependencies: [:simple_name]
106
+ end
@@ -128,7 +128,7 @@ describe Praxis::Handlers::XML do
128
128
  it_behaves_like 'xml something'
129
129
  end
130
130
  context 'a array with elements of all types' do
131
- let(:parsed){ ["just text",:a,1,BigDecimal.new(100),0.1,true,Date.new] }
131
+ let(:parsed){ ["just text",:a,1,BigDecimal(100),0.1,true,Date.new] }
132
132
  it_behaves_like 'xml something'
133
133
  end
134
134
  context 'a hash with a complex substructure' do
@@ -137,7 +137,7 @@ describe Praxis::Handlers::XML do
137
137
  "text" => "just text",
138
138
  "symbol" => :a,
139
139
  "num" => 1,
140
- "bd" => BigDecimal.new(100),
140
+ "bd" => BigDecimal(100),
141
141
  "float" => 0.1,
142
142
  "truthyness" => true,
143
143
  "day" => Date.new,
@@ -0,0 +1,169 @@
1
+ require 'spec_helper'
2
+
3
+ describe Praxis::Mapper::Resource do
4
+ let(:parent_record) { ParentModel.new(id: 100, name: 'george sr') }
5
+ let(:parent_records) { [ParentModel.new(id: 101, name: "georgia"),ParentModel.new(id: 102, name: 'georgina')] }
6
+ let(:record) { SimpleModel.new(id: 103, name: 'george xvi') }
7
+ let(:model) { SimpleModel}
8
+
9
+ context 'configuration' do
10
+ subject(:resource) { SimpleResource }
11
+ its(:model) { should == model }
12
+
13
+ context 'properties' do
14
+ subject(:properties) { resource.properties }
15
+
16
+ it 'includes directly-set properties' do
17
+ expect(properties[:other_resource]).to eq(dependencies: [:other_model])
18
+ end
19
+
20
+ it 'inherits from a superclass' do
21
+ expect(properties[:href]).to eq(dependencies: [:id])
22
+ end
23
+
24
+ it 'properly overrides a property from the parent' do
25
+ expect(properties[:name]).to eq(dependencies: [:simple_name])
26
+ end
27
+ end
28
+ end
29
+
30
+ context 'retrieving resources' do
31
+ context 'getting a single resource' do
32
+ before do
33
+ expect(SimpleModel).to receive(:get).with(name: 'george xvi').and_return(record)
34
+ end
35
+
36
+ subject(:resource) { SimpleResource.get(:name => 'george xvi') }
37
+
38
+ it { is_expected.to be_kind_of(SimpleResource) }
39
+
40
+ its(:record) { should be record }
41
+ end
42
+
43
+ context 'getting multiple resources' do
44
+ before do
45
+ expect(SimpleModel).to receive(:all).with(name: ['george xvi']).and_return([record])
46
+ end
47
+
48
+ subject(:resource_collection) { SimpleResource.all(:name => ["george xvi"]) }
49
+
50
+ it { is_expected.to be_kind_of(Array) }
51
+
52
+ it 'fetches the models and wraps them' do
53
+ resource = resource_collection.first
54
+ expect(resource).to be_kind_of(SimpleResource)
55
+ expect(resource.record).to eq record
56
+ end
57
+ end
58
+ end
59
+
60
+ context 'delegating to the underlying model' do
61
+
62
+ subject { SimpleResource.new(record) }
63
+
64
+ it 'does respond_to attributes in the model' do
65
+ expect(subject).to respond_to(:name)
66
+ end
67
+
68
+ it 'does not respond_to :id if the model does not have it' do
69
+ resource = OtherResource.new(OtherModel.new(:name => "foo"))
70
+ expect(resource).not_to respond_to(:id)
71
+ end
72
+
73
+ it 'returns raw results for simple attributes' do
74
+ expect(record).to receive(:name).and_call_original
75
+ expect(subject.name).to eq("george xvi")
76
+ end
77
+
78
+ it 'wraps model objects in Resource instances' do
79
+ expect(record).to receive(:parent).and_return(parent_record)
80
+
81
+ parent = subject.parent
82
+
83
+ expect(parent).to be_kind_of(ParentResource)
84
+ expect(parent.name).to eq("george sr")
85
+ expect(parent.record).to eq(parent_record)
86
+ end
87
+
88
+ context "for serialized array associations" do
89
+ let(:record) { YamlArrayModel.new(:id => 1)}
90
+
91
+ subject { YamlArrayResource.new(record)}
92
+
93
+ it 'wraps arrays of model objects in an array of resource instances' do
94
+ expect(record).to receive(:parents).and_return(parent_records)
95
+
96
+ parents = subject.parents
97
+ expect(parents).to have(parent_records.size).items
98
+ expect(parents).to be_kind_of(Array)
99
+
100
+ parents.each { |parent| expect(parent).to be_kind_of(ParentResource) }
101
+ expect(parents.collect { |parent| parent.record }).to match_array(parent_records)
102
+ end
103
+ end
104
+ end
105
+
106
+ context 'resource_delegate' do
107
+ let(:other_name) { "foo" }
108
+ let(:other_attribute) { "other value" }
109
+ let(:other_record) { OtherModel.new(:name => other_name, :other_attribute => other_attribute)}
110
+ let(:other_resource) { OtherResource.new(other_record) }
111
+
112
+ let(:record) { SimpleModel.new(id: 105, name: "george xvi", other_name: other_name) }
113
+
114
+ subject(:resource) { SimpleResource.new(record) }
115
+
116
+ it 'delegates to the target' do
117
+ expect(record).to receive(:other_model).and_return(other_record)
118
+ expect(resource.other_attribute).to eq(other_attribute)
119
+ end
120
+ end
121
+
122
+
123
+ context "memoized resource creation" do
124
+ let(:other_name) { "foo" }
125
+ let(:other_attribute) { "other value" }
126
+ let(:other_record) { OtherModel.new(:name => other_name, :other_attribute => other_attribute)}
127
+ let(:other_resource) { OtherResource.new(other_record) }
128
+ let(:record) { SimpleModel.new(id: 105, name: "george xvi", other_name: other_name) }
129
+
130
+ subject(:resource) { SimpleResource.new(record) }
131
+
132
+ it 'memoizes related resource creation' do
133
+ allow(record).to receive(:other_model).and_return(other_record)
134
+ expect(resource.other_resource).to be(SimpleResource.new(record).other_resource)
135
+ end
136
+
137
+ end
138
+
139
+
140
+ context ".wrap" do
141
+ it 'memoizes resource creation' do
142
+ expect(SimpleResource.wrap(record)).to be(SimpleResource.wrap(record))
143
+ end
144
+
145
+ it 'works with nil resources, returning an empty set' do
146
+ wrapped_obj = SimpleResource.wrap(nil)
147
+ expect(wrapped_obj).to be_kind_of(Array)
148
+ expect(wrapped_obj.length).to be(0)
149
+ end
150
+
151
+ it 'works array with nil member, returning only existing records' do
152
+ wrapped_set = SimpleResource.wrap([nil, record])
153
+ expect(wrapped_set).to be_kind_of(Array)
154
+ expect(wrapped_set.length).to be(1)
155
+ end
156
+
157
+ it 'works with non-enumerable objects, that respond to collect' do
158
+ collectable = double("ArrayProxy", to_a: [record, record] )
159
+
160
+ wrapped_set = SimpleResource.wrap(collectable)
161
+ expect(wrapped_set.length).to be(2)
162
+ end
163
+
164
+ it 'works regardless of the resource class used' do
165
+ expect(SimpleResource.wrap(record)).to be(OtherResource.wrap(record))
166
+ end
167
+ end
168
+
169
+ end
@@ -0,0 +1,293 @@
1
+ require 'spec_helper'
2
+
3
+
4
+ describe Praxis::Mapper::SelectorGenerator do
5
+ let(:resource) { SimpleResource }
6
+ subject(:generator) {described_class.new }
7
+
8
+ context '#add' do
9
+ let(:resource) { SimpleResource }
10
+ shared_examples 'a proper selector' do
11
+ it { expect(generator.add(resource, fields).dump).to be_deep_equal selectors }
12
+ end
13
+
14
+ context 'basic combos' do
15
+ context 'direct column fields' do
16
+ let(:fields) { {id: true, foobar: true} }
17
+ let(:selectors) do
18
+ {
19
+ model: SimpleModel,
20
+ columns: [:id, :foobar]
21
+ }
22
+ end
23
+ it_behaves_like 'a proper selector'
24
+ end
25
+
26
+ context 'aliased column fields' do
27
+ let(:fields) { {id: true, name: true} }
28
+ let(:selectors) do
29
+ {
30
+ model: SimpleModel,
31
+ columns: [:id, :simple_name]
32
+ }
33
+ end
34
+ it_behaves_like 'a proper selector'
35
+ end
36
+
37
+ context 'pure associations without recursion' do
38
+ let(:fields) { {other_model: true} }
39
+ let(:selectors) do
40
+ {
41
+ model: SimpleModel,
42
+ columns: [:other_model_id], # FK of the other_model association
43
+ tracks: {
44
+ other_model: {
45
+ columns: [:id], # joining key for the association
46
+ model: OtherModel
47
+ }
48
+ }
49
+ }
50
+ end
51
+ it_behaves_like 'a proper selector'
52
+ end
53
+
54
+ context 'aliased associations without recursion' do
55
+ let(:fields) { {other_resource: true} }
56
+ let(:selectors) do
57
+ {
58
+ model: SimpleModel,
59
+ columns: [:other_model_id], # FK of the other_model association
60
+ tracks: {
61
+ other_model: {
62
+ columns: [:id], # joining key for the association
63
+ model: OtherModel
64
+ }
65
+ }
66
+ }
67
+ end
68
+ it_behaves_like 'a proper selector'
69
+ end
70
+ context 'aliased associations without recursion (that map to columns and other associations)' do
71
+ let(:fields) { {aliased_method: true} }
72
+ let(:selectors) do
73
+ {
74
+ model: SimpleModel,
75
+ columns: [:column1, :other_model_id], # other_model_id => because of the association
76
+ tracks: {
77
+ other_model: {
78
+ columns: [:id], # joining key for the association
79
+ model: OtherModel
80
+ }
81
+ }
82
+ }
83
+ end
84
+ it_behaves_like 'a proper selector'
85
+ end
86
+
87
+ context 'redefined associations that add some extra columns (would need both the underlying association AND the columns in place)' do
88
+ let(:fields) { {parent: true} }
89
+ let(:selectors) do
90
+ {
91
+ model: SimpleModel,
92
+ columns: [:parent_id, :added_column],
93
+ tracks: {
94
+ parent: {
95
+ columns: [:id],
96
+ model: ParentModel
97
+ }
98
+ }
99
+ }
100
+ end
101
+ it_behaves_like 'a proper selector'
102
+ end
103
+
104
+ context 'a simple property that requires all fields' do
105
+ let(:fields) { {everything: true} }
106
+ let(:selectors) do
107
+ {
108
+ model: SimpleModel,
109
+ columns: [:*],
110
+ }
111
+ end
112
+ it_behaves_like 'a proper selector'
113
+ end
114
+
115
+ context 'a simple property that requires itself' do
116
+ let(:fields) { {circular_dep: true} }
117
+ let(:selectors) do
118
+ {
119
+ model: SimpleModel,
120
+ columns: [:circular_dep, :column1], #allows to "expand" the dependency into itself + others
121
+ }
122
+ end
123
+ it_behaves_like 'a proper selector'
124
+ end
125
+
126
+ context 'a simple property without dependencies' do
127
+ let(:fields) { {no_deps: true} }
128
+ let(:selectors) do
129
+ {
130
+ model: SimpleModel
131
+ }
132
+ end
133
+ it_behaves_like 'a proper selector'
134
+ end
135
+
136
+ end
137
+
138
+ context 'nested tracking' do
139
+ context 'pure associations follow the nested fields' do
140
+ let(:fields) do
141
+ {
142
+ other_model: {
143
+ id: true
144
+ }
145
+ }
146
+ end
147
+ let(:selectors) do
148
+ {
149
+ model: SimpleModel,
150
+ columns: [:other_model_id],
151
+ tracks: {
152
+ other_model: {
153
+ model: OtherModel,
154
+ columns: [:id]
155
+ }
156
+ }
157
+ }
158
+ end
159
+ it_behaves_like 'a proper selector'
160
+ end
161
+
162
+ context 'Aliased resources disregard any nested fields...' do
163
+ let(:fields) do
164
+ {
165
+ other_resource: {
166
+ id: true
167
+ }
168
+ }
169
+ end
170
+ let(:selectors) do
171
+ {
172
+ model: SimpleModel,
173
+ columns: [:other_model_id],
174
+ tracks: {
175
+ other_model: {
176
+ model: OtherModel,
177
+ columns: [:id]
178
+ }
179
+ }
180
+ }
181
+ end
182
+ it_behaves_like 'a proper selector'
183
+ end
184
+ end
185
+
186
+ context 'string associations' do
187
+ context 'that specify a direct existing colum in the target dependency' do
188
+ let(:fields) { { direct_other_name: true } }
189
+ let(:selectors) do
190
+ {
191
+ model: SimpleModel,
192
+ columns: [:other_model_id],
193
+ tracks: {
194
+ other_model: {
195
+ model: OtherModel,
196
+ columns: [:id, :name]
197
+ }
198
+ }
199
+ }
200
+ end
201
+ it_behaves_like 'a proper selector'
202
+ end
203
+
204
+ context 'that specify an aliased property in the target dependency' do
205
+ let(:fields) { { aliased_other_name: true } }
206
+ let(:selectors) do
207
+ {
208
+ model: SimpleModel,
209
+ columns: [:other_model_id],
210
+ tracks: {
211
+ other_model: {
212
+ model: OtherModel,
213
+ columns: [:id, :name]
214
+ }
215
+ }
216
+ }
217
+ end
218
+ it_behaves_like 'a proper selector'
219
+ end
220
+
221
+ context 'for a property that requires all fields from an association' do
222
+ let(:fields) { {everything_from_parent: true} }
223
+ let(:selectors) do
224
+ {
225
+ model: SimpleModel,
226
+ columns: [:parent_id],
227
+ tracks: {
228
+ parent: {
229
+ model: ParentModel,
230
+ columns: [:*]
231
+ }
232
+ }
233
+ }
234
+ end
235
+ it_behaves_like 'a proper selector'
236
+ end
237
+ end
238
+
239
+ context 'required extra select fields due to associations' do
240
+ context 'many_to_one' do
241
+ let(:fields) { {other_model: true} }
242
+ let(:selectors) do
243
+ {
244
+ model: SimpleModel,
245
+ columns: [:other_model_id], # FK of the other_model association
246
+ tracks: {
247
+ other_model: {
248
+ columns: [:id],
249
+ model: OtherModel
250
+ }
251
+ }
252
+ }
253
+ end
254
+ it_behaves_like 'a proper selector'
255
+ end
256
+ context 'one_to_many' do
257
+ let(:resource) { ParentResource }
258
+ let(:fields) { {simple_children: true} }
259
+ let(:selectors) do
260
+ {
261
+ model: ParentModel,
262
+ columns: [:id], # No FKs in the source model for one_to_many
263
+ tracks: {
264
+ simple_children: {
265
+ columns: [:parent_id],
266
+ model: SimpleModel
267
+ }
268
+ }
269
+ }
270
+ end
271
+ it_behaves_like 'a proper selector'
272
+ end
273
+ context 'many_to_many' do
274
+ let(:resource) { OtherResource }
275
+ let(:fields) { {simple_models: true} }
276
+ let(:selectors) do
277
+ {
278
+ model: OtherModel,
279
+ columns: [:id], #join key in the source model for many_to_many (where the middle table points to)
280
+ tracks: {
281
+ simple_models: {
282
+ columns: [:id], #join key in the target model for many_to_many (where the middle table points to)
283
+ model: SimpleModel
284
+ }
285
+ }
286
+ }
287
+ end
288
+ it_behaves_like 'a proper selector'
289
+ end
290
+
291
+ end
292
+ end
293
+ end