rest_rails 0.1.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.
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: []