standardapi 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 86fbddaa79809d2f3b0f075474e08154e200bbe5
4
+ data.tar.gz: 82b6a3783e344c8e51b9f1bcc21c1373207365f8
5
+ SHA512:
6
+ metadata.gz: 8c4e88e5ab6b8409d459130e3536c2c8de33201a09235507222ce11c07c4cdbde14382393d8df2d3cbd7614f8f79b3794ba2a946b32f97b435f1e084ccefc7c3
7
+ data.tar.gz: b0edbe753c752f464931a4341bb9084577beab115d60c3d356b5a81477953b7f0fe83987f3f3f594714f20b40c3d1ca79042f9551e98ed7d052f896ae63fbe32
data/README.md ADDED
@@ -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: standardapi
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: []