rest_rails 0.1.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
+ SHA256:
3
+ metadata.gz: 7fbfcc915cc6cd4b87ab45165c5ae83644327250414b2695ac6792afcb938903
4
+ data.tar.gz: 45955c0053d64c977ed9f4460681e2972503ca9c84ae739eb6b173553898a447
5
+ SHA512:
6
+ metadata.gz: 21952e44ba928b34a0232bd4e49a6dd38e60adebbc9f8c590d4fa8e94f2eed6f92a0064d15d2112e65c9b542232d5ce7e9cda3736aaa4a12ea39817b6d61725b
7
+ data.tar.gz: 8d6968c5743e3002095fccef95bdaf48b53032320c3a2349b4bc1e8265b5c2095c1a911f4d855095da55d17609765ae65e4dc4485efdc4f01d17660f7c502c44
data/MIT-LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright 2019 Sergio Rivas
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # RestRails
2
+ Rails Plugin for quick intelligent API Creation by Tuitu Technology Solutions
3
+
4
+ ## How To Use
5
+
6
+ Add To Gemfile: `gem 'rest_rails'`
7
+
8
+ Then place in the bottom of Rails Routes:
9
+
10
+ ```
11
+ mount RestRails::Engine => '/api/v1', as: 'rest'
12
+ ```
13
+ ## TODO
14
+
15
+ - Add Documentation
16
+ - Add Test Suite
data/Rakefile ADDED
@@ -0,0 +1,32 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'RestRails'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.md')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("test/dummy/Rakefile", __dir__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+ load 'rails/tasks/statistics.rake'
21
+
22
+ require 'bundler/gem_tasks'
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'test'
28
+ t.pattern = 'test/**/*_test.rb'
29
+ t.verbose = false
30
+ end
31
+
32
+ task default: :test
@@ -0,0 +1 @@
1
+ // no need for js
@@ -0,0 +1 @@
1
+ /* no need for CSS*/
@@ -0,0 +1,36 @@
1
+ module RestRails
2
+ class ApplicationController < ActionController::API
3
+ before_action :authenticate_user!, if: -> {self.respond_to?("authenticate_user!") && RestRails.authenticatable}
4
+ before_action :set_locale
5
+
6
+ rescue_from StandardError, with: :internal_server_error
7
+ rescue_from RestRails::Error, with: :internal_server_error
8
+ rescue_from ActiveRecord::RecordNotFound, with: :not_found
9
+
10
+ private
11
+
12
+ def not_found(exception)
13
+ if RestRails.debug
14
+ raise exception
15
+ else
16
+ render json: { error: exception.message }, status: :not_found
17
+ end
18
+ end
19
+
20
+ def internal_server_error(exception)
21
+ if RestRails.debug
22
+ raise exception
23
+ elsif Rails.env.development?
24
+ response = { type: exception.class.to_s, error: exception.message }
25
+ else
26
+ response = { error: "Internal Server Error" }
27
+ end
28
+ render json: response, status: :internal_server_error
29
+ end
30
+
31
+ def set_locale
32
+ I18n.locale = params[:locale] || session[:locale] || I18n.default_locale
33
+ session[:locale] = I18n.locale
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,180 @@
1
+ require 'json'
2
+ module RestRails
3
+ class DataController < RestRails::ApplicationController
4
+ include RestRails::ApplicationHelper
5
+ before_action :set_model, only: [:index, :show, :create, :update, :destroy, :fetch_column, :attach, :unattach]
6
+ before_action :set_object, only: [:show, :update, :destroy, :fetch_column, :attach, :unattach]
7
+
8
+ def index
9
+ p_hash = index_params.to_h
10
+
11
+ @objects = @model.all.in_groups_of(100) if p_hash.blank?
12
+ @objects = @model.where(p_hash).in_groups_of(100) if p_hash.present?
13
+
14
+ if params[:page].blank? || (params[:page].to_i < 1)
15
+ if @objects.empty?
16
+ render json: []
17
+ else
18
+ render json: @objects[0].reject{|x|x.nil?}.map{|x| standardize_json(x) }
19
+ end
20
+ else
21
+ i = params[:page].to_i - 1
22
+ render json: @objects[i].reject{|x|x.nil?}.map{|x| standardize_json(x) }
23
+ end
24
+ end
25
+
26
+ def show
27
+ render json: {code: 200}.merge(standardize_json(@object))
28
+ end
29
+
30
+ def create
31
+ @object = @model.new(model_params)
32
+
33
+ attach_files
34
+ if @object.save
35
+ render json: {code: 200, msg: "success", object: standardize_json(@object)}
36
+ else
37
+ render json: {code: 300, msg: "FAILED!"}
38
+ end
39
+ end
40
+
41
+ def update
42
+ attach_files
43
+ if @object.update(model_params)
44
+ render json: {code: 200, msg: "success", object: standardize_json(@object)}
45
+ else
46
+ render json: {code: 300, msg: "FAILED!"}
47
+ end
48
+ end
49
+
50
+ def destroy
51
+ if @object.destroy
52
+ render json: {code: 200, msg: "success"}
53
+ else
54
+ render json: {code: 300, msg: "FAILED!"}
55
+ end
56
+ end
57
+
58
+ def fetch_column
59
+ raise RestRails::Error.new "Column '#{params[:column]}' does not exist for #{params[:table_name]} table!" unless @model.attribute_names.include?(params[:column])
60
+
61
+ render json: {code: 200, value: @object.public_send(params[:column])}
62
+ end
63
+
64
+ def attach
65
+ # post '/:table_name/:id/attach/:attachment_name' => 'data#attach'
66
+ raise RestRails::Error.new "No Attached file!" unless params[:attachment].is_a?(ActionDispatch::Http::UploadedFile)
67
+ raise RestRails::Error.new "Attachment '#{params[:attachment_name]}' does not exist for #{params[:table_name]} table!" unless attachments_for(@model.new).include?(params[:attachment_name].to_sym)
68
+
69
+ @object.public_send(params[:attachment_name].to_sym).attach(params[:attachment])
70
+ render json: {code: 200, msg: "attached!"}
71
+ end
72
+
73
+ def unattach
74
+ # delete '/:table_name/:id/unattach/:attachment_id' => 'data#unattach'
75
+ att = ActiveStorage::Attachment.find(params[:attachment_id])
76
+
77
+ raise RestRails::Error.new "Unauthorized! Attachment does not belong to object!" unless (@object.id == att.record_id) && (@object.is_a? att.record_type.constantize)
78
+
79
+ att.purge
80
+
81
+ render json: {code: 200, msg: "success"}
82
+ end
83
+
84
+ private
85
+
86
+ def set_model
87
+ # /api/v1/:table_name/...
88
+ # e.g. /api/v1/users => User
89
+
90
+ # Take the tablename, and make the Model of the relative table_name
91
+ @model = model_for(params[:table_name])
92
+ end
93
+
94
+ def set_object
95
+ # Take model from "set_model"
96
+ @object = @model.find(params[:id])
97
+ end
98
+
99
+ def index_params
100
+ mn = @model.model_name.singular.to_sym
101
+ # {user: {name: "something", other_column_name: "somevalue"}}
102
+ return {} if params[mn].blank?
103
+ # MAKE LIST OF THINGS TO PERMIT:
104
+ arr = @model.attribute_names.map(&:to_sym)
105
+ arr.delete(:created_at)
106
+ arr.delete(:updated_at)
107
+
108
+ # allow arrays for all columns for flexible where queries
109
+ arr += arr.map do |attr|
110
+ {attr=>[]}
111
+ end
112
+ params.require(mn).permit(arr)
113
+ end
114
+
115
+ def attach_files
116
+ # BASED ON ACTIVE STORAGE
117
+ mn = @model.model_name.singular.to_sym # /users => user
118
+ #
119
+ file_set = attachments_for(@model.new)
120
+ file_set.each do |fs|
121
+ next if params[mn].blank? || params[mn][fs].blank?
122
+ attachment = params[mn][fs]
123
+
124
+ if attachment.is_a?(ActionDispatch::Http::UploadedFile)
125
+ @object.public_send(fs).attach(attachment)
126
+ elsif attachment.is_a?(Array) && attachment.first.is_a?(ActionDispatch::Http::UploadedFile)
127
+ @object.public_send(fs).attach(attachment)
128
+ elsif attachment.is_a?(Array)
129
+ params[mn][fs] = attachment.reject{|x| x.include?("/rails/active_storage/blobs")}
130
+ elsif attachment.is_a? String
131
+ params[mn][fs] = nil if attachment.include?("/rails/active_storage/blobs")
132
+ end
133
+ end
134
+ end
135
+
136
+ # ==========UNIVERSAL STRONG PARAMS SETUP=================
137
+ def model_params
138
+ mn = @model.model_name.singular.to_sym
139
+ # {user: {name: "something", other_column_name: "somevalue"}}
140
+ return {} if params[mn].blank? # to allow create for empty items
141
+
142
+ # params.require(:table_name).permit(:column_name1, :column_name2, etc., array_type: [], array_type2: [])
143
+ params.require(mn).permit(permitted_columns)
144
+ end
145
+
146
+ def permit_array?(attr)
147
+ permitable_classes = [ActiveStorage::Attached::Many, Array]
148
+ permitable_classes.include?(@model.new.send(attr).class)
149
+ end
150
+
151
+ def permitted_columns
152
+ @columns = @model.attribute_names.map(&:to_sym)
153
+ @columns.delete(:id)
154
+ @columns.delete(:created_at)
155
+ @columns.delete(:updated_at)
156
+
157
+ @columns.map! do |attr|
158
+ new_val = permit_array?(attr) ? {attr=>[]} : attr
159
+ new_val
160
+ end
161
+ permitted_attachments if RestRails.active_storage_attachments
162
+ end
163
+
164
+ def permitted_attachments
165
+ file_set = attachments_for(@model.new)
166
+ file_set += file_set.select{|x| permit_array?(x)}.map do |attr|
167
+ {attr=>[]}
168
+ end
169
+
170
+ @columns = @columns + file_set
171
+ end
172
+ end
173
+ end
174
+
175
+
176
+
177
+
178
+
179
+
180
+
@@ -0,0 +1,59 @@
1
+ module RestRails
2
+ module ApplicationHelper
3
+ def standardize_json(ar_object)
4
+ h = ar_object.serializable_hash
5
+ h.merge!(serial_attachments(ar_object)) if attachments_for(ar_object).present?
6
+ h
7
+ end
8
+
9
+ # ==========================================================================
10
+ # SERIALIZE ATTACHMENTS FOR JSON
11
+ # ==========================================================================
12
+
13
+ def serial_attachments(ar_object)
14
+ h = {}
15
+ attachment_types = attachments_for(ar_object)
16
+ attachment_types.each do |att|
17
+ attached = self.public_send(att)
18
+ att_h = {attachment_id: att.id, url: link_for_attached(attached)}
19
+ h[att] = att_h
20
+ end
21
+ return h
22
+ end
23
+
24
+ def blob_link(x)
25
+ host = RestRails.domain
26
+ Rails.application.routes.url_helpers.rails_blob_url(x, host: host)
27
+ end
28
+
29
+ def link_for_attached(attached)
30
+ if attached.class == ActiveStorage::Attached::Many
31
+ return attached.map{|x| blob_link(x) }
32
+ elsif attached.class == ActiveStorage::Attached::One
33
+ x = attached.attachment
34
+ return blob_link(x) unless x.nil?
35
+ end
36
+ end
37
+
38
+ def attachments_for(ar_object)
39
+ meths = ar_object.class.methods.map(&:to_s)
40
+ attach_meths = meths.select{|x| x.include?("with_attached")}
41
+ attach_meths.map{|x| x[14..-1].to_sym}
42
+ end
43
+
44
+ # ==========================================================================
45
+ # OTHER HELPERS
46
+ # ==========================================================================
47
+
48
+ def model_for(table_name)
49
+ ignored = ["active_storage_blobs", "active_storage_attachments",
50
+ "schema_migrations", "ar_internal_metadata"]
51
+ tables = ActiveRecord::Base.connection.tables.reject{ |x| ignored.include?(x) }
52
+
53
+ raise RestRails::Error.new "Table '#{table_name}' does not exist in your database!" unless tables.include?(table_name)
54
+
55
+ # Take the tablename, and make the Model of the relative table_name
56
+ table_name.classify.constantize # "users" => User
57
+ end
58
+ end
59
+ end
data/config/routes.rb ADDED
@@ -0,0 +1,15 @@
1
+ RestRails::Engine.routes.draw do
2
+ scope module: 'rest_rails' do
3
+ get '/:table_name' => 'data#index', as: 'data_index'
4
+ post '/:table_name' => 'data#create'
5
+
6
+ get '/:table_name/:id' => 'data#show', as: 'data_show'
7
+ patch '/:table_name/:id' => 'data#update'
8
+ delete '/:table_name/:id' => 'data#destroy', as: 'data_destroy'
9
+
10
+ post '/:table_name/:id/attach/:attachment_name' => 'data#attach'
11
+ delete '/:table_name/:id/unattach/:attachment_id' => 'data#unattach'
12
+
13
+ get '/:table_name/:id/:column' => 'data#fetch_column'
14
+ end
15
+ end
@@ -0,0 +1,12 @@
1
+ module RestRails
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace RestRails
4
+
5
+ def self.mounted_path
6
+ route = Rails.application.routes.routes.detect do |route|
7
+ route.app == self
8
+ end
9
+ route && route.path
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ module RestRails
2
+ class Error < StandardError
3
+ end
4
+ end
@@ -0,0 +1,3 @@
1
+ module RestRails
2
+ VERSION = '0.1.0'
3
+ end
data/lib/rest_rails.rb ADDED
@@ -0,0 +1,12 @@
1
+ require "rest_rails/engine"
2
+
3
+ module RestRails
4
+ mattr_accessor :debug, default: false
5
+ mattr_accessor :authenticatable, default: false
6
+ mattr_accessor :active_storage_attachments, default: true
7
+ mattr_accessor :domain, default: 'localhost:3000'
8
+
9
+ def self.configure
10
+ yield self
11
+ end
12
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :rest_rails do
3
+ # # Task goes here
4
+ # end
metadata ADDED
@@ -0,0 +1,92 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: rest_rails
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Sergio Rivas
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-12-23 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rails
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 6.0.2
20
+ - - ">="
21
+ - !ruby/object:Gem::Version
22
+ version: 6.0.2.1
23
+ type: :runtime
24
+ prerelease: false
25
+ version_requirements: !ruby/object:Gem::Requirement
26
+ requirements:
27
+ - - "~>"
28
+ - !ruby/object:Gem::Version
29
+ version: 6.0.2
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: 6.0.2.1
33
+ - !ruby/object:Gem::Dependency
34
+ name: sqlite3
35
+ requirement: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ type: :development
41
+ prerelease: false
42
+ version_requirements: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ description: Build a Full Rails REST API in 10 seconds
48
+ email:
49
+ - sergiorivas@aliyun.com
50
+ executables: []
51
+ extensions: []
52
+ extra_rdoc_files: []
53
+ files:
54
+ - MIT-LICENSE
55
+ - README.md
56
+ - Rakefile
57
+ - app/assets/config/rest_rails_manifest.js
58
+ - app/assets/stylesheets/rest_rails/application.css
59
+ - app/controllers/rest_rails/application_controller.rb
60
+ - app/controllers/rest_rails/data_controller.rb
61
+ - app/helpers/rest_rails/application_helper.rb
62
+ - config/routes.rb
63
+ - lib/rest_rails.rb
64
+ - lib/rest_rails/engine.rb
65
+ - lib/rest_rails/error.rb
66
+ - lib/rest_rails/version.rb
67
+ - lib/tasks/rest_rails_tasks.rake
68
+ homepage: http://rubygems.org/gems/rest_rails
69
+ licenses:
70
+ - MIT
71
+ metadata: {}
72
+ post_install_message:
73
+ rdoc_options: []
74
+ require_paths:
75
+ - lib
76
+ required_ruby_version: !ruby/object:Gem::Requirement
77
+ requirements:
78
+ - - ">="
79
+ - !ruby/object:Gem::Version
80
+ version: '0'
81
+ required_rubygems_version: !ruby/object:Gem::Requirement
82
+ requirements:
83
+ - - ">="
84
+ - !ruby/object:Gem::Version
85
+ version: '0'
86
+ requirements: []
87
+ rubyforge_project:
88
+ rubygems_version: 2.7.6
89
+ signing_key:
90
+ specification_version: 4
91
+ summary: Simple, Mountable, rails REST API
92
+ test_files: []