forest_liana 1.0.0.beta5
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +675 -0
- data/README.rdoc +1 -0
- data/Rakefile +37 -0
- data/app/assets/javascripts/forest_liana/application.js +13 -0
- data/app/assets/stylesheets/forest_liana/application.css +15 -0
- data/app/assets/stylesheets/scaffold.css +56 -0
- data/app/controllers/forest_liana/apimaps_controller.rb +18 -0
- data/app/controllers/forest_liana/application_controller.rb +30 -0
- data/app/controllers/forest_liana/resources_controller.rb +62 -0
- data/app/deserializers/forest_liana/resource_deserializer.rb +41 -0
- data/app/helpers/forest_liana/application_helper.rb +4 -0
- data/app/models/forest_liana/collection.rb +26 -0
- data/app/serializers/forest_liana/apimap_serializer.rb +10 -0
- data/app/serializers/forest_liana/serializer_factory.rb +88 -0
- data/app/services/forest_liana/resource_getter.rb +15 -0
- data/app/services/forest_liana/resources_getter.rb +101 -0
- data/app/services/forest_liana/schema_adapter.rb +118 -0
- data/app/services/forest_liana/schema_utils.rb +28 -0
- data/app/services/forest_liana/search_query_builder.rb +70 -0
- data/app/views/layouts/forest_liana/application.html.erb +14 -0
- data/config/initializers/mimetype.rb +1 -0
- data/config/routes.rb +9 -0
- data/lib/forest_liana.rb +5 -0
- data/lib/forest_liana/engine.rb +30 -0
- data/lib/forest_liana/version.rb +3 -0
- data/lib/generators/forest_liana/install_generator.rb +15 -0
- data/lib/tasks/forest_tasks.rake +4 -0
- data/test/dummy/README.rdoc +28 -0
- data/test/dummy/Rakefile +6 -0
- data/test/dummy/app/assets/javascripts/application.js +13 -0
- data/test/dummy/app/assets/stylesheets/application.css +15 -0
- data/test/dummy/app/controllers/application_controller.rb +5 -0
- data/test/dummy/app/helpers/application_helper.rb +2 -0
- data/test/dummy/app/models/belongs_to_class_name_field.rb +3 -0
- data/test/dummy/app/models/belongs_to_field.rb +3 -0
- data/test/dummy/app/models/boolean_field.rb +2 -0
- data/test/dummy/app/models/date_field.rb +2 -0
- data/test/dummy/app/models/decimal_field.rb +2 -0
- data/test/dummy/app/models/float_field.rb +2 -0
- data/test/dummy/app/models/has_and_belongs_to_many_field.rb +3 -0
- data/test/dummy/app/models/has_many_class_name_field.rb +3 -0
- data/test/dummy/app/models/has_many_field.rb +3 -0
- data/test/dummy/app/models/has_one_field.rb +3 -0
- data/test/dummy/app/models/integer_field.rb +2 -0
- data/test/dummy/app/models/polymorphic_field.rb +3 -0
- data/test/dummy/app/models/string_field.rb +2 -0
- data/test/dummy/app/views/layouts/application.html.erb +14 -0
- data/test/dummy/bin/bundle +3 -0
- data/test/dummy/bin/rails +4 -0
- data/test/dummy/bin/rake +4 -0
- data/test/dummy/bin/setup +29 -0
- data/test/dummy/config.ru +4 -0
- data/test/dummy/config/application.rb +26 -0
- data/test/dummy/config/boot.rb +5 -0
- data/test/dummy/config/database.yml +25 -0
- data/test/dummy/config/environment.rb +5 -0
- data/test/dummy/config/environments/development.rb +41 -0
- data/test/dummy/config/environments/production.rb +79 -0
- data/test/dummy/config/environments/test.rb +42 -0
- data/test/dummy/config/initializers/assets.rb +11 -0
- data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
- data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
- data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
- data/test/dummy/config/initializers/inflections.rb +16 -0
- data/test/dummy/config/initializers/mime_types.rb +4 -0
- data/test/dummy/config/initializers/session_store.rb +3 -0
- data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
- data/test/dummy/config/locales/en.yml +23 -0
- data/test/dummy/config/routes.rb +4 -0
- data/test/dummy/config/secrets.yml +22 -0
- data/test/dummy/db/development.sqlite3 +0 -0
- data/test/dummy/db/migrate/20150608130516_create_date_field.rb +7 -0
- data/test/dummy/db/migrate/20150608131430_create_integer_field.rb +7 -0
- data/test/dummy/db/migrate/20150608131603_create_decimal_field.rb +7 -0
- data/test/dummy/db/migrate/20150608131610_create_float_field.rb +7 -0
- data/test/dummy/db/migrate/20150608132159_create_boolean_field.rb +7 -0
- data/test/dummy/db/migrate/20150608132621_create_string_field.rb +7 -0
- data/test/dummy/db/migrate/20150608133038_create_belongs_to_field.rb +7 -0
- data/test/dummy/db/migrate/20150608133044_create_has_one_field.rb +6 -0
- data/test/dummy/db/migrate/20150608150016_create_has_many_field.rb +6 -0
- data/test/dummy/db/migrate/20150609114636_create_belongs_to_class_name_field.rb +7 -0
- data/test/dummy/db/migrate/20150612112520_create_has_and_belongs_to_many_field.rb +6 -0
- data/test/dummy/db/migrate/20150616150629_create_polymorphic_field.rb +7 -0
- data/test/dummy/db/migrate/20150623115554_create_has_many_class_name_field.rb +6 -0
- data/test/dummy/db/schema.rb +70 -0
- data/test/dummy/db/test.sqlite3 +0 -0
- data/test/dummy/log/test.log +395 -0
- data/test/dummy/public/404.html +67 -0
- data/test/dummy/public/422.html +67 -0
- data/test/dummy/public/500.html +66 -0
- data/test/dummy/public/favicon.ico +0 -0
- data/test/forest_liana_test.rb +7 -0
- data/test/integration/navigation_test.rb +10 -0
- data/test/services/forest_liana/schema_adapter_test.rb +87 -0
- data/test/test_helper.rb +20 -0
- metadata +274 -0
@@ -0,0 +1,118 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
class SchemaAdapter
|
3
|
+
def initialize(model)
|
4
|
+
@model = model
|
5
|
+
end
|
6
|
+
|
7
|
+
def perform
|
8
|
+
@collection = Collection.new({ name: @model.name.tableize, fields: [] })
|
9
|
+
add_columns
|
10
|
+
add_associations
|
11
|
+
|
12
|
+
@collection
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def add_columns
|
18
|
+
@model.columns.each do |column|
|
19
|
+
@collection.fields << get_schema_for_column(column)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
def add_associations
|
24
|
+
SchemaUtils.associations(@model).each do |association|
|
25
|
+
begin
|
26
|
+
if schema = column_association(@collection, association)
|
27
|
+
schema[:reference] = get_ref_for(association)
|
28
|
+
schema[:field] = deforeign_key(schema[:field])
|
29
|
+
schema[:inverseOf] = inverse_of(association)
|
30
|
+
else
|
31
|
+
@collection.fields << get_schema_for_association(association)
|
32
|
+
end
|
33
|
+
rescue => error
|
34
|
+
puts error.inspect
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def inverse_of(association)
|
40
|
+
association.inverse_of.try(:name).try(:to_s) ||
|
41
|
+
automatic_inverse_of(association)
|
42
|
+
end
|
43
|
+
|
44
|
+
def automatic_inverse_of(association)
|
45
|
+
name = association.active_record.name.demodulize.underscore
|
46
|
+
|
47
|
+
inverse_association = association.klass.reflections.keys.find do |k|
|
48
|
+
k.to_s == name || k.to_s == name.pluralize
|
49
|
+
end
|
50
|
+
|
51
|
+
inverse_association.try(:to_s)
|
52
|
+
end
|
53
|
+
|
54
|
+
def get_schema_for_column(column)
|
55
|
+
{ field: column.name, type: get_type_for(column) }
|
56
|
+
end
|
57
|
+
|
58
|
+
def get_schema_for_association(association)
|
59
|
+
{
|
60
|
+
field: association.name.to_s,
|
61
|
+
type: get_type_for_association(association),
|
62
|
+
reference: "#{association.class_name.to_s.tableize}.id",
|
63
|
+
inverseOf: inverse_of(association)
|
64
|
+
}
|
65
|
+
end
|
66
|
+
|
67
|
+
def get_type_for(column)
|
68
|
+
case column.type
|
69
|
+
when :integer
|
70
|
+
'Number'
|
71
|
+
when :float
|
72
|
+
'Number'
|
73
|
+
when :decimal
|
74
|
+
'Number'
|
75
|
+
when :datetime
|
76
|
+
'Date'
|
77
|
+
when :date
|
78
|
+
'Date'
|
79
|
+
when :string
|
80
|
+
'String'
|
81
|
+
when :text
|
82
|
+
'String'
|
83
|
+
when :boolean
|
84
|
+
'Boolean'
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def get_ref_for(association)
|
89
|
+
if association.options[:polymorphic] == true
|
90
|
+
'*.id'
|
91
|
+
else
|
92
|
+
"#{association.class_name.to_s.tableize}.id"
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def column_association(collection, field)
|
97
|
+
collection.fields.find {|x| x[:field] == field.foreign_key }
|
98
|
+
end
|
99
|
+
|
100
|
+
def get_type_for_association(association)
|
101
|
+
if association.macro == :has_many ||
|
102
|
+
association.macro == :has_and_belongs_to_many
|
103
|
+
['Number']
|
104
|
+
else
|
105
|
+
'Number'
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
def deforeign_key(column_name)
|
110
|
+
if column_name[-3..-1] == '_id'
|
111
|
+
column_name[0..-4]
|
112
|
+
else
|
113
|
+
column_name
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
end
|
118
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
class SchemaUtils
|
3
|
+
|
4
|
+
def self.associations(active_record_class)
|
5
|
+
active_record_class
|
6
|
+
.reflect_on_all_associations
|
7
|
+
.select {|a| !polymorphic?(a)}
|
8
|
+
end
|
9
|
+
|
10
|
+
def self.find_model_from_table_name(table_name)
|
11
|
+
(table_name.classify.constantize rescue nil) ||
|
12
|
+
(table_name.capitalize.constantize rescue nil) ||
|
13
|
+
(table_name.sub('_', '/').camelize.singularize.constantize rescue nil)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.tables_names
|
17
|
+
ActiveRecord::Base.connection.tables
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def self.polymorphic?(association)
|
23
|
+
association.options[:polymorphic]
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
@@ -0,0 +1,70 @@
|
|
1
|
+
module ForestLiana
|
2
|
+
class SearchQueryBuilder
|
3
|
+
|
4
|
+
def initialize(resource, params)
|
5
|
+
@resource = resource
|
6
|
+
@params = params
|
7
|
+
end
|
8
|
+
|
9
|
+
def perform
|
10
|
+
search_param
|
11
|
+
filter_param
|
12
|
+
associations_param
|
13
|
+
|
14
|
+
@resource
|
15
|
+
end
|
16
|
+
|
17
|
+
def search_param
|
18
|
+
if @params[:search]
|
19
|
+
conditions = []
|
20
|
+
|
21
|
+
@resource.columns.each_with_index do |column, index|
|
22
|
+
if column.name == 'id'
|
23
|
+
conditions << "id = #{@params[:search].to_i}"
|
24
|
+
elsif column.type == :string || column.type == :text
|
25
|
+
conditions <<
|
26
|
+
"#{column.name} LIKE '%#{@params[:search].downcase}%'"
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
@resource = @resource.where(conditions.join(' OR '))
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
def filter_param
|
35
|
+
if @params[:filter]
|
36
|
+
@params[:filter].each do |field, value|
|
37
|
+
|
38
|
+
operator = nil
|
39
|
+
if value.first == '!'
|
40
|
+
operator = '!='
|
41
|
+
value.slice!(0)
|
42
|
+
elsif value.include?('*')
|
43
|
+
operator = 'LIKE'
|
44
|
+
value.gsub!('*', '%')
|
45
|
+
else
|
46
|
+
operator = '='
|
47
|
+
end
|
48
|
+
|
49
|
+
@resource = @resource.where("#{field} #{operator} '#{value}'")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def associations_param
|
55
|
+
associations = @resource.reflect_on_all_associations
|
56
|
+
.select {|x| x.macro == :belongs_to}
|
57
|
+
|
58
|
+
associations.each do |association|
|
59
|
+
name = association.name.to_s
|
60
|
+
|
61
|
+
if @params[name + 'Id']
|
62
|
+
@resource = @resource.where("#{name.foreign_key} =
|
63
|
+
#{@params[name + 'Id']}")
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
@@ -0,0 +1,14 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html>
|
3
|
+
<head>
|
4
|
+
<title>ForestLiana</title>
|
5
|
+
<%= stylesheet_link_tag "forest/application", media: "all" %>
|
6
|
+
<%= javascript_include_tag "forest/application" %>
|
7
|
+
<%= csrf_meta_tags %>
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
|
11
|
+
<%= yield %>
|
12
|
+
|
13
|
+
</body>
|
14
|
+
</html>
|
@@ -0,0 +1 @@
|
|
1
|
+
Mime::Type.register "application/json", :json, %w( text/x-json application/jsonrequest application/vnd.api+json )
|
data/config/routes.rb
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
ForestLiana::Engine.routes.draw do
|
2
|
+
get '/' => 'apimaps#index'
|
3
|
+
get ':resource' => 'resources#index'
|
4
|
+
get ':resource/:id' => 'resources#show'
|
5
|
+
get ':resource/:id' => 'resources#show'
|
6
|
+
post ':resource' => 'resources#create'
|
7
|
+
put ':resource/:id' => 'resources#update'
|
8
|
+
delete ':resource/:id' => 'resources#destroy'
|
9
|
+
end
|
data/lib/forest_liana.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'rack/cors'
|
2
|
+
|
3
|
+
module ForestLiana
|
4
|
+
class Engine < ::Rails::Engine
|
5
|
+
isolate_namespace ForestLiana
|
6
|
+
|
7
|
+
config.middleware.insert_before 0, 'Rack::Cors' do
|
8
|
+
allow do
|
9
|
+
origins '*'
|
10
|
+
resource '*', headers: :any, methods: :any
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
config.after_initialize do
|
15
|
+
SchemaUtils.tables_names.map do |table_name|
|
16
|
+
model = SchemaUtils.find_model_from_table_name(table_name)
|
17
|
+
SerializerFactory.new.serializer_for(model) if \
|
18
|
+
model.try(:table_exists?)
|
19
|
+
end
|
20
|
+
|
21
|
+
# Monkey patch the find_serializer_class_name method to specify the good
|
22
|
+
# serializer to use.
|
23
|
+
JSONAPI::Serializer.class_eval do
|
24
|
+
def self.find_serializer_class_name(obj)
|
25
|
+
"ForestLiana::#{obj.class.name.demodulize}Serializer"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'rails/generators'
|
2
|
+
|
3
|
+
module ForestLiana
|
4
|
+
class InstallGenerator < Rails::Generators::Base
|
5
|
+
desc 'Forest Rails Liana installation generator'
|
6
|
+
|
7
|
+
def install
|
8
|
+
jwt_signing_key = ask('What\'s your project secret key?')
|
9
|
+
route("mount ForestLiana::Engine => '/forest'")
|
10
|
+
initializer 'forest_liana.rb' do
|
11
|
+
"ForestLiana.jwt_signing_key = '#{jwt_signing_key}'"
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
== README
|
2
|
+
|
3
|
+
This README would normally document whatever steps are necessary to get the
|
4
|
+
application up and running.
|
5
|
+
|
6
|
+
Things you may want to cover:
|
7
|
+
|
8
|
+
* Ruby version
|
9
|
+
|
10
|
+
* System dependencies
|
11
|
+
|
12
|
+
* Configuration
|
13
|
+
|
14
|
+
* Database creation
|
15
|
+
|
16
|
+
* Database initialization
|
17
|
+
|
18
|
+
* How to run the test suite
|
19
|
+
|
20
|
+
* Services (job queues, cache servers, search engines, etc.)
|
21
|
+
|
22
|
+
* Deployment instructions
|
23
|
+
|
24
|
+
* ...
|
25
|
+
|
26
|
+
|
27
|
+
Please feel free to use a different markup language if you do not plan to run
|
28
|
+
<tt>rake doc:app</tt>.
|
data/test/dummy/Rakefile
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
// This is a manifest file that'll be compiled into application.js, which will include all the files
|
2
|
+
// listed below.
|
3
|
+
//
|
4
|
+
// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
|
5
|
+
// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
|
6
|
+
//
|
7
|
+
// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
|
8
|
+
// compiled file.
|
9
|
+
//
|
10
|
+
// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
|
11
|
+
// about supported directives.
|
12
|
+
//
|
13
|
+
//= require_tree .
|
@@ -0,0 +1,15 @@
|
|
1
|
+
/*
|
2
|
+
* This is a manifest file that'll be compiled into application.css, which will include all the files
|
3
|
+
* listed below.
|
4
|
+
*
|
5
|
+
* Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
|
6
|
+
* or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
|
7
|
+
*
|
8
|
+
* You're free to add application-wide styles to this file and they'll appear at the bottom of the
|
9
|
+
* compiled file so the styles you add here take precedence over styles defined in any styles
|
10
|
+
* defined in the other CSS/SCSS files in this directory. It is generally better to create a new
|
11
|
+
* file per style scope.
|
12
|
+
*
|
13
|
+
*= require_tree .
|
14
|
+
*= require_self
|
15
|
+
*/
|