graph_starter 0.0.1

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.
Files changed (99) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +38 -0
  5. data/app/assets/images/missing.png +0 -0
  6. data/app/assets/javascripts/graph_starter/application.coffee.erb +25 -0
  7. data/app/assets/javascripts/graph_starter/ember-template-compiler.js +22130 -0
  8. data/app/assets/javascripts/graph_starter/ember.js +52794 -0
  9. data/app/assets/javascripts/graph_starter/ember_apps/permissions_modal.coffee +146 -0
  10. data/app/assets/javascripts/graph_starter/ember_apps/user_list_dropdown.coffee +22 -0
  11. data/app/assets/javascripts/graph_starter/jquery-ui.min.js +7 -0
  12. data/app/assets/javascripts/graph_starter/underscore.js +1548 -0
  13. data/app/assets/stylesheets/graph_starter/application.scss +9 -0
  14. data/app/controllers/graph_starter/application_controller.rb +11 -0
  15. data/app/controllers/graph_starter/assets_controller.rb +76 -0
  16. data/app/controllers/graph_starter/authorizables_controller.rb +47 -0
  17. data/app/controllers/graph_starter/categories_controller.rb +79 -0
  18. data/app/controllers/graph_starter/groups_controller.rb +85 -0
  19. data/app/controllers/graph_starter/models_controller.rb +11 -0
  20. data/app/controllers/graph_starter/properties_controller.rb +11 -0
  21. data/app/helpers/graph_starter/application_helper.rb +11 -0
  22. data/app/models/concerns/graph_starter/authorizable.rb +30 -0
  23. data/app/models/graph_starter/asset.rb +161 -0
  24. data/app/models/graph_starter/can_access.rb +14 -0
  25. data/app/models/graph_starter/category.rb +24 -0
  26. data/app/models/graph_starter/group.rb +36 -0
  27. data/app/models/graph_starter/image.rb +16 -0
  28. data/app/models/graph_starter/model.rb +23 -0
  29. data/app/models/graph_starter/property.rb +19 -0
  30. data/app/models/graph_starter/view.rb +30 -0
  31. data/app/views/graph_starter/assets/TODO.md +7 -0
  32. data/app/views/graph_starter/assets/_cards.html.slim +34 -0
  33. data/app/views/graph_starter/assets/edit.html.slim +24 -0
  34. data/app/views/graph_starter/assets/home.html.slim +1 -0
  35. data/app/views/graph_starter/assets/index.html.slim +28 -0
  36. data/app/views/graph_starter/assets/show.html.slim +98 -0
  37. data/app/views/graph_starter/authorizables/show.json.jbuilder +20 -0
  38. data/app/views/graph_starter/authorizables/user_and_group_search.json.jbuilder +8 -0
  39. data/app/views/graph_starter/categories/show.html.slim +9 -0
  40. data/app/views/graph_starter/groups/_form.html.slim +30 -0
  41. data/app/views/graph_starter/groups/_list.html.slim +19 -0
  42. data/app/views/graph_starter/groups/edit.html.slim +8 -0
  43. data/app/views/graph_starter/groups/index.html.slim +18 -0
  44. data/app/views/graph_starter/groups/index.json.jbuilder +4 -0
  45. data/app/views/graph_starter/groups/new.html.slim +5 -0
  46. data/app/views/graph_starter/groups/show.html.slim +16 -0
  47. data/app/views/graph_starter/groups/show.json.jbuilder +1 -0
  48. data/app/views/graph_starter/models/index.html.slim +10 -0
  49. data/app/views/graph_starter/properties/_property.html.slim +34 -0
  50. data/app/views/layouts/graph_starter/_change_permissions_modal.html.slim +90 -0
  51. data/app/views/layouts/graph_starter/_custom_menu.html.slim +0 -0
  52. data/app/views/layouts/graph_starter/_menu.html.slim +7 -0
  53. data/app/views/layouts/graph_starter/_twitter_meta_tags.html.slim +17 -0
  54. data/app/views/layouts/graph_starter/application.html.slim +45 -0
  55. data/app/views/layouts/graph_starter/custom_menu.html.slim +0 -0
  56. data/config/routes.rb +24 -0
  57. data/lib/graph_starter.rb +4 -0
  58. data/lib/graph_starter/engine.rb +25 -0
  59. data/lib/graph_starter/query_authorizer.rb +81 -0
  60. data/lib/graph_starter/version.rb +3 -0
  61. data/lib/tasks/graph_starter_tasks.rake +4 -0
  62. data/test/dummy/README.rdoc +28 -0
  63. data/test/dummy/Rakefile +6 -0
  64. data/test/dummy/app/assets/javascripts/application.js +13 -0
  65. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  66. data/test/dummy/app/controllers/application_controller.rb +5 -0
  67. data/test/dummy/app/helpers/application_helper.rb +2 -0
  68. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  69. data/test/dummy/bin/bundle +3 -0
  70. data/test/dummy/bin/rails +4 -0
  71. data/test/dummy/bin/rake +4 -0
  72. data/test/dummy/bin/setup +29 -0
  73. data/test/dummy/config.ru +4 -0
  74. data/test/dummy/config/application.rb +26 -0
  75. data/test/dummy/config/boot.rb +5 -0
  76. data/test/dummy/config/database.yml +25 -0
  77. data/test/dummy/config/environment.rb +5 -0
  78. data/test/dummy/config/environments/development.rb +41 -0
  79. data/test/dummy/config/environments/production.rb +79 -0
  80. data/test/dummy/config/environments/test.rb +42 -0
  81. data/test/dummy/config/initializers/assets.rb +11 -0
  82. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  83. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  84. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  85. data/test/dummy/config/initializers/inflections.rb +16 -0
  86. data/test/dummy/config/initializers/mime_types.rb +4 -0
  87. data/test/dummy/config/initializers/session_store.rb +3 -0
  88. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  89. data/test/dummy/config/locales/en.yml +23 -0
  90. data/test/dummy/config/routes.rb +4 -0
  91. data/test/dummy/config/secrets.yml +22 -0
  92. data/test/dummy/public/404.html +67 -0
  93. data/test/dummy/public/422.html +67 -0
  94. data/test/dummy/public/500.html +66 -0
  95. data/test/dummy/public/favicon.ico +0 -0
  96. data/test/graph_starter_test.rb +7 -0
  97. data/test/integration/navigation_test.rb +10 -0
  98. data/test/test_helper.rb +20 -0
  99. metadata +262 -0
@@ -0,0 +1,9 @@
1
+ /*
2
+ *= require_self
3
+ */
4
+
5
+ @import "semantic-ui";
6
+
7
+ #menu {
8
+ margin-top: 0;
9
+ }
@@ -0,0 +1,11 @@
1
+ module GraphStarter
2
+ class ApplicationController < ::ApplicationController
3
+
4
+ protected
5
+
6
+ def model_class
7
+ @model_slug = params[:model_slug]
8
+ @model_slug.classify.constantize
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,76 @@
1
+ module GraphStarter
2
+ class AssetsController < ::GraphStarter::ApplicationController
3
+ def home
4
+ end
5
+
6
+ def index
7
+ @assets = asset_set
8
+ end
9
+
10
+ def search
11
+ regexp = Regexp.new('.*' + params[:query].gsub(/\s+/, '.*') + '.*', 'i')
12
+ assets = asset_set { |scope| scope.where(title: regexp) }
13
+ results_data = assets.map do |asset|
14
+ {
15
+ title: asset.title,
16
+ url: asset_path(id: asset, model_slug: asset.class.model_slug),
17
+ image: images? && asset.first_image_source && asset.first_image_source.url || nil
18
+ }.reject {|_, v| v.nil? }
19
+ end
20
+
21
+ render json: {results: results_data}.to_json
22
+ end
23
+
24
+ def asset_set
25
+ associations = []
26
+ associations << :images if images?
27
+ associations << model_class.category_association if model_class.category_association
28
+
29
+ scope = model_class_scope
30
+ scope = yield scope if block_given?
31
+
32
+ scope = scope.limit(50)
33
+
34
+ if associations.present?
35
+ scope.query_as(:s).with(:s).proxy_as(model_class_scope.model, :s).with_associations(*associations)
36
+ else
37
+ scope
38
+ end
39
+ end
40
+
41
+ def show
42
+ @asset = asset
43
+
44
+ render file: 'public/404.html', status: :not_found, layout: false if !@asset
45
+ end
46
+
47
+ def edit
48
+ @asset = asset
49
+
50
+ render file: 'public/404.html', status: :not_found, layout: false if !@asset
51
+ end
52
+
53
+ def update
54
+ @asset = asset
55
+ @asset.update(params[:book])
56
+
57
+ redirect_to action: :edit
58
+ end
59
+
60
+ def asset
61
+ model_class_scope.find(params[:id])
62
+ end
63
+
64
+ def images?
65
+ model_class_scope.has_images?
66
+ end
67
+
68
+ def model_class_scope
69
+ @model_class_scope = if defined?(current_user)
70
+ model_class.authorized_for(current_user)
71
+ else
72
+ model_class.all
73
+ end
74
+ end
75
+ end
76
+ end
@@ -0,0 +1,47 @@
1
+ module GraphStarter
2
+ class AuthorizablesController < ApplicationController
3
+ before_action :require_admin
4
+
5
+ before_action :set_object, only: [:show, :update]
6
+
7
+ def show
8
+ end
9
+
10
+ def update
11
+ @object.update_attribute(:private, params[:private])
12
+
13
+ @object.set_access_levels User,
14
+ access_levels_from_permissions(params[:user_permissions], :user)
15
+
16
+ @object.set_access_levels Group,
17
+ access_levels_from_permissions(params[:group_permissions], :group)
18
+
19
+ render :show
20
+ end
21
+
22
+ def access_levels_from_permissions(permissions, type)
23
+ (permissions || []).each_with_object({}) do |permission, result|
24
+ result[permission[type][:id]] = permission[:level]
25
+ end
26
+ end
27
+
28
+ def user_and_group_search
29
+ @users, @groups = if params[:query].present?
30
+
31
+ [User.for_query(params[:query]), Group.for_query(params[:query])]
32
+ else
33
+ [[], []]
34
+ end
35
+ end
36
+
37
+ def change_permissions_modal
38
+ render layout: false
39
+ end
40
+
41
+ private
42
+
43
+ def set_object
44
+ @object = model_class.find(params[:id])
45
+ end
46
+ end
47
+ end
@@ -0,0 +1,79 @@
1
+ module GraphStarter
2
+ class CategoriesController < ApplicationController
3
+ before_action :set_category, only: [:show, :edit, :update, :destroy]
4
+
5
+ # GET /categories
6
+ # GET /categories.json
7
+ def index
8
+ @categories = Category.all
9
+ end
10
+
11
+ # GET /categories/1
12
+ # GET /categories/1.json
13
+ def show
14
+ end
15
+
16
+ # GET /categories/new
17
+ def new
18
+ @category = Category.new
19
+ end
20
+
21
+ # GET /categories/1/edit
22
+ def edit
23
+ end
24
+
25
+ # POST /categories
26
+ # POST /categories.json
27
+ def create
28
+ @category = Category.new(category_params)
29
+
30
+ respond_to do |format|
31
+ if @category.save
32
+ format.html { redirect_to @category, notice: 'Category was successfully created.' }
33
+ format.json { render :show, status: :created, location: @category }
34
+ else
35
+ format.html { render :new }
36
+ format.json { render json: @category.errors, status: :unprocessable_entity }
37
+ end
38
+ end
39
+ end
40
+
41
+ # PATCH/PUT /categories/1
42
+ # PATCH/PUT /categories/1.json
43
+ def update
44
+ respond_to do |format|
45
+ if @category.update(category_params)
46
+ format.html { redirect_to @category, notice: 'Category was successfully updated.' }
47
+ format.json { render :show, status: :ok, location: @category }
48
+ else
49
+ format.html { render :edit }
50
+ format.json { render json: @category.errors, status: :unprocessable_entity }
51
+ end
52
+ end
53
+ end
54
+
55
+ # DELETE /categories/1
56
+ # DELETE /categories/1.json
57
+ def destroy
58
+ @category.destroy
59
+ respond_to do |format|
60
+ format.html do
61
+ redirect_to categories_url, notice: 'Category was successfully destroyed.'
62
+ end
63
+ format.json { head :no_content }
64
+ end
65
+ end
66
+
67
+ private
68
+
69
+ # Use callbacks to share common setup or constraints between actions.
70
+ def set_category
71
+ @category = Category.find(params[:id])
72
+ end
73
+
74
+ # Never trust parameters from the scary internet, only allow the white list through.
75
+ def category_params
76
+ params[:category]
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,85 @@
1
+ module GraphStarter
2
+ class GroupsController < ApplicationController
3
+ before_action :require_admin
4
+
5
+ before_action :set_group, only: [:show, :edit, :update, :destroy, :users_to_add]
6
+
7
+ # GET /groups
8
+ # GET /groups.json
9
+ def index
10
+ @groups = Group.roots
11
+ end
12
+
13
+ # GET /groups/1
14
+ # GET /groups/1.json
15
+ def show
16
+ end
17
+
18
+ # GET /groups/new
19
+ def new
20
+ @group = Group.new
21
+ end
22
+
23
+ # GET /groups/1/edit
24
+ def edit
25
+ end
26
+
27
+ # POST /groups
28
+ # POST /groups.json
29
+ def create
30
+ @group = Group.new(group_params)
31
+
32
+ respond_to do |format|
33
+ if @group.save
34
+ format.html { redirect_to @group, notice: 'Group was successfully created.' }
35
+ format.json { render :show, status: :created, location: @group }
36
+ else
37
+ format.html { render :new }
38
+ format.json { render json: @group.errors, status: :unprocessable_entity }
39
+ end
40
+ end
41
+ end
42
+
43
+ # PATCH/PUT /groups/1
44
+ # PATCH/PUT /groups/1.json
45
+ def update
46
+ respond_to do |format|
47
+ if @group.update(group_params)
48
+ format.html { redirect_to @group, notice: 'Group was successfully updated.' }
49
+ format.json { render :show, status: :ok, location: @group }
50
+ else
51
+ format.html { render :edit }
52
+ format.json { render json: @group.errors, status: :unprocessable_entity }
53
+ end
54
+ end
55
+ end
56
+
57
+ # DELETE /groups/1
58
+ # DELETE /groups/1.json
59
+ def destroy
60
+ @group.destroy
61
+ respond_to do |format|
62
+ format.html { redirect_to groups_url, notice: 'Group was successfully destroyed.' }
63
+ format.json { head :no_content }
64
+ end
65
+ end
66
+
67
+ def users_to_add
68
+ render json: @group.addable_users
69
+ end
70
+
71
+ private
72
+
73
+ # Use callbacks to share common setup or constraints between actions.
74
+ def set_group
75
+ @group = Group.find(params[:id])
76
+ end
77
+
78
+ # Never trust parameters from the scary internet, only allow the white list through.
79
+ def group_params
80
+ params[:group][:member_ids].reject!(&:blank?)
81
+
82
+ params[:group]
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,11 @@
1
+ module GraphStarter
2
+ class ModelsController < ApplicationController
3
+ def index
4
+ @models = Model.all
5
+ end
6
+
7
+ def show
8
+ @model = Model.find_by(name: params[:name])
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module GraphStarter
2
+ class PropertiesController < ApplicationController
3
+ def index
4
+ @properties = Property.all
5
+ end
6
+
7
+ def show
8
+ @property = Property.find(params[:id])
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,11 @@
1
+ module GraphStarter
2
+ module ApplicationHelper
3
+ def asset_path(asset)
4
+ super(id: asset, model_slug: asset.class.model_slug)
5
+ end
6
+
7
+ def engine_view(&b)
8
+ yield eval("__FILE__.gsub(Rails.root.to_s, GraphStarter::Engine.root.to_s)",b.binding)
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,30 @@
1
+ module GraphStarter
2
+ module Authorizable
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ property :private, type: ActiveAttr::Typecasting::Boolean, default: false
7
+ validates :private, inclusion: {in: [true, false]}
8
+
9
+ has_many :in, :allowed_users, rel_class: :CanAccess, model_class: :User
10
+ has_many :in, :allowed_groups, rel_class: :CanAccess, model_class: :Group
11
+ end
12
+
13
+ def set_access_levels(model, access_levels)
14
+ records_by_id = model.where(id: access_levels.keys).index_by(&:id)
15
+
16
+ model.where_not(id: records_by_id.keys)
17
+ .query_as(:r)
18
+ .match_nodes(this: self)
19
+ .match('r-[rel:CAN_ACCESS]->(this)')
20
+ .delete(:rel).exec
21
+
22
+ access_levels.each do |id, level|
23
+ CanAccess.create(from_node: records_by_id[id], to_node: self, level: level)
24
+ end
25
+ end
26
+
27
+ module ClassMethods
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,161 @@
1
+ require 'graph_starter/query_authorizer'
2
+
3
+ module GraphStarter
4
+ class Asset
5
+ include Neo4j::ActiveNode
6
+ self.mapped_label_name = 'Asset'
7
+
8
+ include Neo4j::Timestamps
9
+
10
+ include Authorizable
11
+
12
+ property :title
13
+ validates :title, presence: true
14
+
15
+ property :summary
16
+
17
+ property :view_count, type: Integer
18
+
19
+ property :private, type: Boolean, default: false
20
+
21
+ #has_many :in, :creators, type: :CREATED, model_class: :User
22
+
23
+ has_many :in, :viewers, rel_class: :View, model_class: :User
24
+
25
+ SecretSauceRecommendation = Struct.new(:asset, :score)
26
+
27
+ def body
28
+ end
29
+
30
+ IMAGE_MODELS = []
31
+ def self.has_images
32
+ GraphStarter::Asset::IMAGE_MODELS << self
33
+ has_many :out, :images, type: :HAS_IMAGE, model_class: '::GraphStarter::Image'
34
+ end
35
+
36
+ def self.has_images?
37
+ GraphStarter::Asset::IMAGE_MODELS.include?(self)
38
+ end
39
+
40
+ def first_image_source
41
+ images.first && images.first.source
42
+ end
43
+
44
+ def self.category_association
45
+ @category_association
46
+ end
47
+
48
+ def self.category_association=(association_name)
49
+ @category_association = association_name
50
+ end
51
+
52
+ def categories
53
+ if self.class.category_association
54
+ send(self.class.category_association)
55
+ else
56
+ []
57
+ end
58
+ end
59
+
60
+
61
+ def self.for_query(query)
62
+ all.where(title: /.*#{query}.*/i)
63
+ end
64
+
65
+ def secret_sauce_recommendations
66
+ query_as(:source)
67
+ .match('source-[:HAS_CATEGORY]->(category:Category)<-[:HAS_CATEGORY]-(asset:Asset)')
68
+ .break
69
+ .optional_match('source<-[:CREATED]-(creator:User)-[:CREATED]->asset')
70
+ .break
71
+ .optional_match('source<-[:VIEWED]-(viewer:User)-[:VIEWED]->asset')
72
+ .limit(5)
73
+ .order('score DESC')
74
+ .pluck(
75
+ :asset,
76
+ '(count(category) * 2) +
77
+ (count(creator) * 4) +
78
+ (count(viewer) * 0.1) AS score').map do |other_asset, score|
79
+ SecretSauceRecommendation.new(other_asset, score)
80
+ end
81
+ end
82
+
83
+ def name
84
+ title
85
+ end
86
+
87
+ def as_json(_options = {})
88
+ {self.class.model_slug =>
89
+ {id: id,
90
+ title: title,
91
+ name: title,
92
+ images: images.map {|image| image.source.url },
93
+ model_slug: self.class.model_slug}
94
+ }
95
+ end
96
+
97
+ def self.descendants
98
+ Rails.application.eager_load! if Rails.env == 'development'
99
+ Neo4j::ActiveNode::Labels._wrapped_classes.select { |klass| klass < self }
100
+ end
101
+
102
+ def self.model_slug
103
+ name.tableize
104
+ end
105
+
106
+ def self.properties
107
+ attributes.keys - Asset.attributes.keys
108
+ end
109
+
110
+ def self.authorized_for(user)
111
+ require 'graph_starter/query_authorizer'
112
+
113
+ ::GraphStarter::QueryAuthorizer.new(all(:asset).categories(:category, nil, optional: true))
114
+ .authorized_query([:asset, :category], user)
115
+ .with('DISTINCT asset AS asset')
116
+ .proxy_as(self, :asset)
117
+ end
118
+
119
+ def self.authorized_properties(user)
120
+ authorized_properties_query(user).pluck(:property)
121
+ end
122
+
123
+ def self.authorized_properties_and_levels(user)
124
+ authorized_properties_query(user).pluck(:property, :level)
125
+ end
126
+
127
+ def self.authorized_properties_query(user)
128
+ query = property_name_and_uuid_and_ruby_type_query
129
+ .merge(model: {Model: {name: name}})
130
+ .on_create_set(model: {private: false})
131
+ .break
132
+ .merge('model-[:HAS_PROPERTY]->(property:Property {name: property_name})')
133
+ .on_create_set(property: {private: false})
134
+ .on_create_set('property.uuid = uuid, property.ruby_type = ruby_type')
135
+ .with(:property)
136
+
137
+ ::GraphStarter::Property # rubocop:disable Lint/Void
138
+ QueryAuthorizer.new(query).authorized_query(:property, user)
139
+ end
140
+
141
+ def self.property_name_and_uuid_and_ruby_type_query
142
+ properties_and_uuids_and_ruby_types = properties.map do |property|
143
+ [property, SecureRandom.uuid, self.attributes[property][:type]]
144
+ end
145
+
146
+ Neo4j::Session.current.query
147
+ .with('{array} AS array')
148
+ .unwind('array AS row')
149
+ .params(array: properties_and_uuids_and_ruby_types)
150
+ .with('row[0] AS property_name, row[1] AS uuid, row[2] AS ruby_type')
151
+ end
152
+
153
+ def self.authorized_associations
154
+ associations.except(*Asset.associations.keys + [:images])
155
+ end
156
+
157
+ def self.icon_class
158
+ 'bookmark'
159
+ end
160
+ end
161
+ end