standard-api 1.0.0

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,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 635772e9a30b830be1dabcd86a20b8aa72b1f0ba
4
+ data.tar.gz: 26f97f55d19a5e92f2116a4f043eb72e41704fcf
5
+ SHA512:
6
+ metadata.gz: f64d0693657117a2d7eccbacecdfda3900b49cedeab3af0e4d90abe641321864e84bf60e0985120d71e2ba3037d3f88262ee4a0c47a3ab7cc7feb2fb9f9b4a52
7
+ data.tar.gz: 4f8193a0352954c3d1e346731d6c5595cb207529a4f3bba0d1e3a6812528a8e23b88636bf48e5ff9687e820face5439b02f0934717d95aed9fb85c800673144b
@@ -0,0 +1,10 @@
1
+ # standardapi
2
+ StandardAPI makes it easy to expose a query interface for your Rails models
3
+
4
+
5
+ required models
6
+ - account { mask }
7
+ - api_keys
8
+
9
+ # TODO
10
+ - current_mask
@@ -0,0 +1,158 @@
1
+ require 'rails'
2
+ require 'action_view'
3
+ require 'action_pack'
4
+ require 'action_controller'
5
+ require 'jbuilder'
6
+ require 'jbuilder/railtie'
7
+ require 'active_record/filter'
8
+ require 'active_record/sort'
9
+
10
+ if !ActionView::Template.registered_template_handler(:jbuilder)
11
+ ActionView::Template.register_template_handler :jbuilder, JbuilderHandler
12
+ end
13
+
14
+ module StandardAPI
15
+
16
+ def self.included(klass)
17
+ klass.hide_action :current_mask
18
+ klass.helper_method :includes, :orders
19
+ klass.prepend_view_path(File.join(File.dirname(__FILE__), 'standard_api', 'views'))
20
+ end
21
+
22
+ def index
23
+ @records = resources.limit(params[:limit]).offset(params[:offset]).sort(orders)
24
+ instance_variable_set("@#{model.model_name.plural}", @records)
25
+ end
26
+
27
+ def calculate
28
+ @calculations = resources.reorder(nil).pluck(*calculate_selects).map do |c|
29
+ if c.is_a?(Array)
30
+ c.map { |v| v.is_a?(BigDecimal) ? v.to_f : v }
31
+ else
32
+ c.is_a?(BigDecimal) ? c.to_f : c
33
+ end
34
+ end
35
+ render json: @calculations
36
+ end
37
+
38
+ def show
39
+ @record = resources.find(params[:id])
40
+ instance_variable_set("@#{model.model_name.singular}", @record)
41
+ end
42
+
43
+ def create
44
+ @record = model.new(model_params)
45
+ instance_variable_set("@#{model.model_name.singular}", @record)
46
+ render :show, status: @record.save ? :created : :bad_request
47
+ end
48
+
49
+ def update
50
+ @record = resources.find(params[:id])
51
+ instance_variable_set("@#{model.model_name.singular}", @record)
52
+ render :show, status: @record.update_attributes(model_params) ? :ok : :bad_request
53
+ end
54
+
55
+ def destroy
56
+ resources.find(params[:id]).destroy!
57
+ render nothing: true, status: :no_content
58
+ end
59
+
60
+ # Override if you want to support masking
61
+ def current_mask
62
+ @current_mask ||= {}
63
+ end
64
+
65
+ private
66
+
67
+ def model
68
+ return @model if defined?(@model)
69
+ @model = self.class.name.sub(/Controller\z/, '').singularize.camelize.constantize
70
+ end
71
+
72
+ def model_params
73
+ params.require(model.model_name.singular).permit(self.send("#{model.model_name.singular}_params"))
74
+ end
75
+
76
+ def model_includes
77
+ self.send "#{model.model_name.singular}_includes"
78
+ end
79
+
80
+ def model_orders
81
+ self.send "#{model.model_name.singular}_orders"
82
+ end
83
+
84
+ def resources
85
+ model.filter(params[:where]).where(current_mask[model.table_name])
86
+ end
87
+
88
+ # TODO: sanitize includes
89
+ def includes
90
+ params[:include] || []
91
+ end
92
+
93
+ # TODO: sanitize orders
94
+ def orders
95
+ normalized_order(params[:order])
96
+ end
97
+
98
+ def normalized_order(orderings)
99
+ return nil if orderings.nil?
100
+
101
+ orderings = Array(orderings)
102
+
103
+ orderings.map! do |order|
104
+ if order.is_a?(Symbol) || order.is_a?(String)
105
+ order = order.to_s
106
+ if order.index(".")
107
+ relation, column = order.split('.').map(&:to_sym)
108
+ { relation => [column] }
109
+ else
110
+ order.to_sym
111
+ end
112
+ elsif order.is_a?(Hash)
113
+ normalized_order = {}
114
+ order.each do |key, value|
115
+ key = key.to_s
116
+
117
+ if key.index(".")
118
+ relation, column = key.split('.').map(&:to_sym)
119
+ normalized_order[relation] ||= []
120
+ normalized_order[relation] << { column => value }
121
+ elsif value.is_a?(Hash) && value.keys.first.to_s != 'desc' && value.keys.first.to_s != 'asc'
122
+ normalized_order[key.to_sym] ||= []
123
+ normalized_order[key.to_sym] << value
124
+ else
125
+ normalized_order[key.to_sym] = value
126
+ end
127
+ end
128
+ normalized_order
129
+ end
130
+ end
131
+
132
+ orderings
133
+ end
134
+
135
+ # Used in #calculate
136
+ # [{ count: :id }]
137
+ # [{ count: '*' }]
138
+ # [{ count: '*', maximum: :id, minimum: :id }]
139
+ # [{ count: '*' }, { maximum: :id }, { minimum: :id }]
140
+ # TODO: Sanitize (normalize_select_params(params[:select], model))
141
+ def calculate_selects
142
+ return @selects if defined?(@selects)
143
+
144
+ functions = ['minimum', 'maximum', 'average', 'sum', 'count']
145
+ @selects = []
146
+ Array(params[:select]).each do |select|
147
+ select.each do |func, column|
148
+ column = column == '*' ? Arel.star : column.to_sym
149
+ if functions.include?(func.to_s.downcase)
150
+ @selects << (model.arel_table[column.to_sym].send(func).to_sql)
151
+ end
152
+ end
153
+ end
154
+
155
+ @selects
156
+ end
157
+
158
+ end
@@ -0,0 +1,140 @@
1
+ require 'active_support/test_case'
2
+
3
+ require File.expand_path(File.join(__FILE__, '../test_case/calculate_tests'))
4
+ require File.expand_path(File.join(__FILE__, '../test_case/create_tests'))
5
+ require File.expand_path(File.join(__FILE__, '../test_case/destroy_tests'))
6
+ require File.expand_path(File.join(__FILE__, '../test_case/index_tests'))
7
+ require File.expand_path(File.join(__FILE__, '../test_case/show_tests'))
8
+ require File.expand_path(File.join(__FILE__, '../test_case/update_tests'))
9
+
10
+ module StandardAPI::TestCase
11
+
12
+ def self.included(klass)
13
+ model_class_name = klass.controller_class.name.gsub(/Controller$/, '').singularize
14
+
15
+ [:filters, :orders, :includes].each do |attribute|
16
+ klass.send(:class_attribute, attribute)
17
+ end
18
+ if defined?(model_class_name.constantize)
19
+ model_class = model_class_name.constantize
20
+ klass.send(:filters=, model_class.attribute_names)
21
+ klass.send(:orders=, model_class.attribute_names)
22
+ klass.send(:includes=, model_class.reflect_on_all_associations.map(&:name))
23
+ end
24
+
25
+ klass.extend(ClassMethods)
26
+
27
+ klass.controller_class.action_methods.each do |action|
28
+ klass.include("StandardAPI::TestCase::#{action.capitalize}Tests".constantize)
29
+ end
30
+ end
31
+
32
+ def model
33
+ self.class.model
34
+ end
35
+
36
+ def create_model(attrs={})
37
+ create(model.name.underscore, attrs)
38
+ end
39
+
40
+ def singular_name
41
+ model.model_name.singular
42
+ end
43
+
44
+ def plural_name
45
+ model.model_name.plural
46
+ end
47
+
48
+ def create_webmocks(attributes)
49
+ attributes.each do |attribute, value|
50
+ validators = self.class.model.validators_on(attribute)
51
+ end
52
+ end
53
+
54
+ def normalize_attribute(attribute, value)
55
+ validators = self.class.model.validators_on(attribute)
56
+ value
57
+ end
58
+
59
+ def normalize_to_json(attribute, value)
60
+ value = normalize_attribute(attribute, value)
61
+
62
+ return nil if value.nil?
63
+
64
+ if model.column_types[attribute].is_a?(ActiveRecord::ConnectionAdapters::PostgreSQL::OID::Decimal)
65
+ "#{value}"
66
+ elsif model.column_types[attribute].is_a?(ActiveRecord::AttributeMethods::TimeZoneConversion::TimeZoneConverter)
67
+ value.to_datetime.utc.iso8601.gsub(/\+00:00$/, 'Z')
68
+ else
69
+ value
70
+ end
71
+ end
72
+
73
+ module ClassMethods
74
+
75
+ def include_filter_tests
76
+ model.instance_variable_get('@filters').each do |filter|
77
+ next if filter[1].is_a?(Proc) # Custom filter
78
+ next if model.reflect_on_association(filter[0]) # TODO: Relation Filter Tests
79
+
80
+ define_method("test_model_filter_#{filter[0]}") do
81
+ m = create_model
82
+ value = m.send(filter[0])
83
+
84
+ assert_predicate = -> (predicate) {
85
+ get :index, where: predicate, format: 'json'
86
+ assert_equal model.filter(predicate).to_sql, assigns(plural_name).to_sql
87
+ }
88
+
89
+ # TODO: Test array
90
+ case model.columns_hash[filter[0].to_s].type
91
+ when :jsonb, :json # JSON
92
+ assert_predicate.call({ filter[0] => value })
93
+ else
94
+ case value
95
+ when Array
96
+ assert_predicate.call({ filter[0] => value }) # Overlaps
97
+ assert_predicate.call({ filter[0] => value[0] }) # Contains
98
+ else
99
+ assert_predicate.call({ filter[0] => value }) # Equality
100
+ assert_predicate.call({ filter[0] => { gt: value } }) # Greater Than
101
+ assert_predicate.call({ filter[0] => { greater_than: value } })
102
+ assert_predicate.call({ filter[0] => { lt: value } }) # Less Than
103
+ assert_predicate.call({ filter[0] => { less_than: value } })
104
+ assert_predicate.call({ filter[0] => { gte: value } }) # Greater Than or Equal To
105
+ assert_predicate.call({ filter[0] => { gteq: value } })
106
+ assert_predicate.call({ filter[0] => { greater_than_or_equal_to: value } })
107
+ assert_predicate.call({ filter[0] => { lte: value } }) # Less Than or Equal To
108
+ assert_predicate.call({ filter[0] => { lteq: value } })
109
+ assert_predicate.call({ filter[0] => { less_than_or_equal_to: value } })
110
+ end
111
+ end
112
+ end
113
+ end
114
+ end
115
+
116
+ def model=(val)
117
+ @model = val
118
+ end
119
+
120
+ def model
121
+ return @model if defined?(@model) && @model
122
+
123
+ klass_name = controller_class.name.gsub(/Controller$/, '').singularize
124
+
125
+ begin
126
+ @model = klass_name.constantize
127
+ rescue NameError
128
+ raise e unless e.message =~ /uninitialized constant #{klass_name}/
129
+ end
130
+
131
+ if @model.nil?
132
+ raise "@model is nil: make sure you set it in your test using `self.model = ModelClass`."
133
+ else
134
+ @model
135
+ end
136
+ end
137
+
138
+ end
139
+
140
+ end
@@ -0,0 +1,44 @@
1
+ module StandardAPI
2
+ module TestCase
3
+ module CalculateTests
4
+ extend ActiveSupport::Testing::Declarative
5
+
6
+ test '#calculate.json' do
7
+ m = create_model
8
+ selects = [{ count: :id}, { maximum: :id }, { minimum: :id }, { average: :id }]
9
+
10
+ get :calculate, select: selects, format: 'json'
11
+ assert_response :ok
12
+ assert_equal [[model.count(:id), model.maximum(:id), model.minimum(:id), model.average(:id).to_f]], assigns(:calculations)
13
+ end
14
+
15
+ test '#calculate.json params[:where]' do
16
+ m1 = create_model
17
+ m2 = create_model
18
+
19
+ selects = [{ count: :id}, { maximum: :id }, { minimum: :id }, { average: :id }]
20
+ predicate = { id: { gt: m1.id } }
21
+
22
+ get :calculate, where: predicate, select: selects, format: 'json'
23
+ assert_response :ok
24
+ assert_equal [[model.filter(predicate).count(:id), model.filter(predicate).maximum(:id), model.filter(predicate).minimum(:id), model.filter(predicate).average(:id).to_f]], assigns(:calculations)
25
+ end
26
+
27
+ test '#calculate.json mask' do
28
+ m = create_model
29
+ @controller.current_mask[plural_name] = { id: m.id + 100 }
30
+ selects = [{ count: :id}, { maximum: :id }, { minimum: :id }, { average: :id }]
31
+ get :calculate, select: selects, format: 'json'
32
+ assert_response :ok
33
+ assert_equal [[0, nil, nil, nil]], assigns(:calculations)
34
+ @controller.current_mask.delete(plural_name)
35
+ end
36
+
37
+ test 'route to #calculate.json' do
38
+ assert_routing "/#{plural_name}/calculate", path_with_action('calculate')
39
+ assert_recognizes(path_with_action('calculate'), "/#{plural_name}/calculate")
40
+ end
41
+
42
+ end
43
+ end
44
+ end
@@ -0,0 +1,71 @@
1
+ module StandardAPI
2
+ module TestCase
3
+ module CreateTests
4
+ extend ActiveSupport::Testing::Declarative
5
+
6
+ test '#create.json' do
7
+ attrs = attributes_for(singular_name, :nested).select{|k,v| !model.readonly_attributes.include?(k.to_s) }
8
+ create_webmocks(attrs)
9
+
10
+ assert_difference("#{model.name}.count") do
11
+ post :create, singular_name => attrs, :format => 'json'
12
+ assert_response :created
13
+ assert assigns(singular_name)
14
+
15
+ json = JSON.parse(response.body)
16
+ assert json.is_a?(Hash)
17
+ (model.attribute_names & attrs.keys.map(&:to_s)).each do |test_key|
18
+ assert_equal normalize_to_json(test_key, attrs[test_key.to_sym]), json[test_key]
19
+ end
20
+ end
21
+ end
22
+
23
+ test '#create.json with invalid attributes' do
24
+ attrs = attributes_for(singular_name, :nested, :invalid).select{|k,v| !model.readonly_attributes.include?(k.to_s) }
25
+ create_webmocks(attrs)
26
+
27
+ assert_difference("#{model.name}.count", 0) do
28
+ post :create, singular_name => attrs, :format => 'json'
29
+ assert_response :bad_request
30
+ json = JSON.parse(response.body)
31
+ assert json.is_a?(Hash)
32
+ assert json['errors']
33
+ end
34
+ end
35
+
36
+ test '#create.json params[:include]' do
37
+ attrs = attributes_for(singular_name, :nested).select{ |k,v| !model.readonly_attributes.include?(k.to_s) }
38
+ create_webmocks(attrs)
39
+
40
+ assert_difference("#{model.name}.count") do
41
+ post :create, singular_name => attrs, include: includes, :format => 'json'
42
+ assert_response :created
43
+ assert assigns(singular_name)
44
+
45
+ json = JSON.parse(response.body)
46
+ assert json.is_a?(Hash)
47
+ includes.each do |included|
48
+ assert json.key?(included.to_s), "#{included.inspect} not included in response"
49
+
50
+ association = assigns(:record).class.reflect_on_association(included)
51
+ if ['belongs_to', 'has_one'].include?(association.macro)
52
+ assigns(:record).send(included).attributes do |key, value|
53
+ assert_equal json[included.to_s][key.to_s], value
54
+ end
55
+ else
56
+ assigns(:record).send(included).first.attributes.each do |key, value|
57
+ assert_equal json[included.to_s][0][key.to_s], value
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+
64
+ test 'route to #create.json' do
65
+ assert_routing({ method: :post, path: "/#{plural_name}" }, path_with_action('create'))
66
+ assert_recognizes(path_with_action('create'), { method: :post, path: "/#{plural_name}" })
67
+ end
68
+
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,32 @@
1
+ module StandardAPI
2
+ module TestCase
3
+ module DestroyTests
4
+ extend ActiveSupport::Testing::Declarative
5
+
6
+ test '#destroy.json' do
7
+ m = create_model
8
+
9
+ assert_difference("#{model.name}.count", -1) do
10
+ delete :destroy, id: m.id, format: 'json'
11
+ assert_response :no_content
12
+ assert_equal '', response.body
13
+ end
14
+ end
15
+
16
+ test '#destroy.json mask' do
17
+ m = create_model
18
+ @controller.current_mask[plural_name] = { id: m.id + 1 }
19
+ assert_raises(ActiveRecord::RecordNotFound) do
20
+ delete :destroy, id: m.id, format: 'json'
21
+ end
22
+ @controller.current_mask.delete(plural_name)
23
+ end
24
+
25
+ test 'route to #destroy.json' do
26
+ assert_routing({ method: :delete, path: "/#{plural_name}/1" }, path_with_action('destroy', id: '1'))
27
+ assert_recognizes(path_with_action('destroy', id: '1'), { method: :delete, path: "/#{plural_name}/1" })
28
+ end
29
+
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,68 @@
1
+ module StandardAPI
2
+ module TestCase
3
+ module IndexTests
4
+ extend ActiveSupport::Testing::Declarative
5
+
6
+ test '#index.json' do
7
+ get :index, format: 'json'
8
+ assert_response :ok
9
+ assert_template :index
10
+ assert_equal model.all.map(&:id).sort, assigns(plural_name).map(&:id).sort
11
+ assert JSON.parse(response.body).is_a?(Array)
12
+ end
13
+
14
+ test '#index.json mask' do
15
+ m = create_model
16
+ @controller.current_mask[plural_name] = { id: m.id }
17
+ get :index, format: 'json'
18
+ assert_equal model.where(id: m.id).to_sql, assigns(plural_name).to_sql
19
+ @controller.current_mask.delete(plural_name)
20
+ end
21
+
22
+ test '#index.json params[:limit]' do
23
+ get :index, limit: 1, format: 'json'
24
+ assert_equal model.limit(1).to_sql, assigns(plural_name).to_sql
25
+ end
26
+
27
+ test '#index.json params[:where]' do
28
+ m = create_model
29
+ get :index, where: { id: m.id }, format: 'json'
30
+ assert_equal [m], assigns(plural_name)
31
+ end
32
+
33
+ test '#index.json params[:order]' do
34
+ orders.each do |order|
35
+ if order.is_a?(Hash)
36
+ order.values.last.each do |o|
37
+ get :index, order: {order.keys.first => o}, format: 'json'
38
+ assert_equal model.sort(order.keys.first => o).to_sql, assigns(plural_name).to_sql
39
+ end
40
+ else
41
+ get :index, order: order, format: 'json'
42
+ assert_equal model.sort(order).to_sql, assigns(:records).to_sql
43
+ end
44
+ end
45
+ end
46
+
47
+ test '#index.json params[:offset]' do
48
+ get :index, offset: 13, format: 'json'
49
+ assert_equal model.offset(13).to_sql, assigns(:records).to_sql
50
+ end
51
+
52
+ test '#index.json params[:include]' do
53
+ create_model
54
+
55
+ includes.each do |included|
56
+ get :index, include: [included], format: 'json'
57
+ assert JSON.parse(response.body)[0].key?(included.to_s), "#{included.inspect} not included in response"
58
+ end
59
+ end
60
+
61
+ test 'route to #index.json' do
62
+ assert_routing "/#{plural_name}", path_with_action('index')
63
+ assert_recognizes path_with_action('index'), "/#{plural_name}"
64
+ end
65
+
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,53 @@
1
+ module StandardAPI
2
+ module TestCase
3
+ module ShowTests
4
+ extend ActiveSupport::Testing::Declarative
5
+
6
+ test '#show.json' do
7
+ m = create_model
8
+
9
+ get :show, id: m.id, format: 'json'
10
+ assert_response :ok
11
+ assert_template :show
12
+ assert_equal m, assigns(singular_name)
13
+ assert JSON.parse(response.body).is_a?(Hash)
14
+ end
15
+
16
+ test '#show.json mask' do
17
+ m = create_model
18
+ @controller.current_mask[plural_name] = { id: m.id + 1 }
19
+ assert_raises(ActiveRecord::RecordNotFound) do
20
+ get :show, id: m.id, format: 'json'
21
+ end
22
+ @controller.current_mask.delete(plural_name)
23
+ end
24
+
25
+ test '#show.json params[:include]' do
26
+ m = create_model(:nested)
27
+ get :show, id: m.id, include: includes, format: 'json'
28
+
29
+ json = JSON.parse(response.body)
30
+ includes.each do |included|
31
+ assert json.key?(included.to_s), "#{included.inspect} not included in response"
32
+
33
+ association = assigns(:record).class.reflect_on_association(included)
34
+ if ['belongs_to', 'has_one'].include?(association.macro)
35
+ assigns(:record).send(included).attributes do |key, value|
36
+ assert_equal json[included.to_s][key.to_s], value
37
+ end
38
+ else
39
+ assigns(:record).send(included).first.attributes.each do |key, value|
40
+ assert_equal json[included.to_s][0][key.to_s], value
41
+ end
42
+ end
43
+ end
44
+ end
45
+
46
+ test 'route to #show.json' do
47
+ assert_routing "/#{plural_name}/1", path_with_action('show', id: '1')
48
+ assert_recognizes(path_with_action('show', id: '1'), "/#{plural_name}/1")
49
+ end
50
+
51
+ end
52
+ end
53
+ end
@@ -0,0 +1,72 @@
1
+ module StandardAPI
2
+ module TestCase
3
+ module UpdateTests
4
+ extend ActiveSupport::Testing::Declarative
5
+
6
+ test '#update.json' do
7
+ m = create_model
8
+ attrs = attributes_for(singular_name).select{|k,v| !model.readonly_attributes.include?(k.to_s) }
9
+ create_webmocks(attrs)
10
+
11
+ put :update, id: m.id, singular_name => attrs, format: 'json'
12
+ assert_response :ok
13
+
14
+ (m.attribute_names & attrs.keys.map(&:to_s)).each do |test_key|
15
+ assert_equal normalize_attribute(test_key, attrs[test_key.to_sym]), m.reload.send(test_key)
16
+ end
17
+ assert JSON.parse(@response.body).is_a?(Hash)
18
+ end
19
+
20
+ test '#update.json with invalid attributes' do
21
+ m = create_model
22
+ attrs = attributes_for(singular_name, :invalid).select{|k,v| !model.readonly_attributes.include?(k.to_s) }
23
+ create_webmocks(attrs)
24
+
25
+ put :update, id: m.id, singular_name => attrs, format: 'json'
26
+ assert_response :bad_request
27
+ assert JSON.parse(@response.body)['errors']
28
+ end
29
+
30
+ test '#update.json params[:include]' do
31
+ m = create_model(:nested)
32
+ attrs = attributes_for(singular_name).select{|k,v| !model.readonly_attributes.include?(k) }
33
+ create_webmocks(attrs)
34
+
35
+ put :update, id: m.id, include: includes, singular_name => attrs, format: 'json'
36
+
37
+ json = JSON.parse(response.body)
38
+ includes.each do |included|
39
+ assert json.key?(included.to_s), "#{included.inspect} not included in response"
40
+
41
+ association = assigns(:record).class.reflect_on_association(included)
42
+ if ['belongs_to', 'has_one'].include?(association.macro)
43
+ assigns(:record).send(included).attributes do |key, value|
44
+ assert_equal json[included.to_s][key.to_s], value
45
+ end
46
+ else
47
+ assigns(:record).send(included).first.attributes.each do |key, value|
48
+ assert_equal json[included.to_s][0][key.to_s], value
49
+ end
50
+ end
51
+ end
52
+ end
53
+
54
+ test '#update.json mask' do
55
+ m = create_model
56
+ @controller.current_mask[plural_name] = { id: m.id + 1 }
57
+ assert_raises(ActiveRecord::RecordNotFound) do
58
+ put :update, id: m.id, format: 'json'
59
+ end
60
+ @controller.current_mask.delete(plural_name)
61
+ end
62
+
63
+ test 'route to #update.json' do
64
+ assert_routing({ method: :put, path: "#{plural_name}/1" }, path_with_action('update', id: '1'))
65
+ assert_recognizes(path_with_action('update', id: '1'), { method: :put, path: "/#{plural_name}/1" })
66
+ assert_routing({ method: :patch, path: "/#{plural_name}/1" }, path_with_action('update', id: '1'))
67
+ assert_recognizes(path_with_action('update', id: '1'), { method: :patch, path: "/#{plural_name}/1" })
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,17 @@
1
+ model.attributes.each do |name, value|
2
+ json.set! name, value
3
+ end
4
+
5
+ includes.each do |inc|
6
+ if model.class.reflect_on_association(inc).klass.is_a?(ActiveModel)
7
+ json.set! inc do
8
+ json.partial! 'action_controller/standard_api/_model', locals: { model: model.send(inc) }
9
+ end
10
+ else
11
+ json.set! inc, model.send(inc)
12
+ end
13
+ end
14
+
15
+ if model.errors
16
+ json.set! 'errors', model.errors.to_h
17
+ end
@@ -0,0 +1,3 @@
1
+ models = @records if !models
2
+
3
+ json.array! models, partial: 'application/model', as: :model, includes: includes
@@ -0,0 +1,3 @@
1
+ model = @record if !model
2
+
3
+ json.partial! 'application/model', model: model, includes: includes
metadata ADDED
@@ -0,0 +1,214 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: standard-api
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - James Bracy
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2015-08-04 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activerecord-sort
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1.0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: activerecord-filter
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '1.0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '1.0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rails
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '4.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '4.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: jbuilder
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '2.3'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '2.3'
69
+ - !ruby/object:Gem::Dependency
70
+ name: bundler
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rake
85
+ requirement: !ruby/object:Gem::Requirement
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
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: minitest
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ - !ruby/object:Gem::Dependency
112
+ name: minitest-reporters
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - ">="
116
+ - !ruby/object:Gem::Version
117
+ version: '0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - ">="
123
+ - !ruby/object:Gem::Version
124
+ version: '0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: simplecov
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - ">="
130
+ - !ruby/object:Gem::Version
131
+ version: '0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - ">="
137
+ - !ruby/object:Gem::Version
138
+ version: '0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: factory_girl_rails
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - ">="
144
+ - !ruby/object:Gem::Version
145
+ version: '0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - ">="
151
+ - !ruby/object:Gem::Version
152
+ version: '0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: faker
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ description: StandardAPI makes it easy to expose a query interface for your Rails
168
+ models
169
+ email:
170
+ - waratuman@gmail.com
171
+ executables: []
172
+ extensions: []
173
+ extra_rdoc_files:
174
+ - README.md
175
+ files:
176
+ - README.md
177
+ - lib/standard_api.rb
178
+ - lib/standard_api/test_case.rb
179
+ - lib/standard_api/test_case/calculate_tests.rb
180
+ - lib/standard_api/test_case/create_tests.rb
181
+ - lib/standard_api/test_case/destroy_tests.rb
182
+ - lib/standard_api/test_case/index_tests.rb
183
+ - lib/standard_api/test_case/show_tests.rb
184
+ - lib/standard_api/test_case/update_tests.rb
185
+ - lib/standard_api/views/application/_model.json.jbuilder
186
+ - lib/standard_api/views/application/index.json.jbuilder
187
+ - lib/standard_api/views/application/show.json.jbuilder
188
+ homepage: https://github.com/waratuman/standardapi
189
+ licenses:
190
+ - MIT
191
+ metadata: {}
192
+ post_install_message:
193
+ rdoc_options:
194
+ - "--main"
195
+ - README.md
196
+ require_paths:
197
+ - lib
198
+ required_ruby_version: !ruby/object:Gem::Requirement
199
+ requirements:
200
+ - - ">="
201
+ - !ruby/object:Gem::Version
202
+ version: '0'
203
+ required_rubygems_version: !ruby/object:Gem::Requirement
204
+ requirements:
205
+ - - ">="
206
+ - !ruby/object:Gem::Version
207
+ version: '0'
208
+ requirements: []
209
+ rubyforge_project:
210
+ rubygems_version: 2.4.5
211
+ signing_key:
212
+ specification_version: 4
213
+ summary: StandardAPI makes it easy to expose a query interface for your Rails models
214
+ test_files: []