back_office 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/README.md +28 -0
- data/Rakefile +32 -0
- data/app/assets/config/back_office_manifest.js +0 -0
- data/config/routes.rb +2 -0
- data/lib/back_office.rb +22 -0
- data/lib/back_office/auth.rb +61 -0
- data/lib/back_office/authorization.rb +25 -0
- data/lib/back_office/cursor.rb +16 -0
- data/lib/back_office/engine.rb +4 -0
- data/lib/back_office/form_builder.rb +117 -0
- data/lib/back_office/password.rb +23 -0
- data/lib/back_office/reset.rb +30 -0
- data/lib/back_office/rest.rb +136 -0
- data/lib/back_office/searchable.rb +69 -0
- data/lib/back_office/version.rb +3 -0
- data/lib/tasks/back_office_tasks.rake +4 -0
- metadata +88 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9d15cc3bf6faebdf066736e07a547fedf21d361a
|
4
|
+
data.tar.gz: 82434cdab661c38a6a47b9c311d72132df073ce7
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e9ac1e61668ea1ebf67b5cc2b88034154b44460b12c3b9a17550ae9a3b4025f478c697b0a4af6969fd7abcea1abfa19feaeecf1c5eed80369ef69dd3d549af38
|
7
|
+
data.tar.gz: '0944bf7a5e7ecc935a3b87a7f7598a21b013a5aa8f06dfa1f9b284e0387e80516151231c54b84ad931d904ec586040bcbb83af0c2cee4b144d6499ee942dc4c0'
|
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# BackOffice
|
2
|
+
Short description and motivation.
|
3
|
+
|
4
|
+
## Usage
|
5
|
+
How to use my plugin.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
Add this line to your application's Gemfile:
|
9
|
+
|
10
|
+
```ruby
|
11
|
+
gem 'back_office'
|
12
|
+
```
|
13
|
+
|
14
|
+
And then execute:
|
15
|
+
```bash
|
16
|
+
$ bundle
|
17
|
+
```
|
18
|
+
|
19
|
+
Or install it yourself as:
|
20
|
+
```bash
|
21
|
+
$ gem install back_office
|
22
|
+
```
|
23
|
+
|
24
|
+
## Contributing
|
25
|
+
Contribution directions go here.
|
26
|
+
|
27
|
+
## License
|
28
|
+
The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
|
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 = 'BackOffice'
|
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', __FILE__)
|
18
|
+
|
19
|
+
load 'rails/tasks/engine.rake'
|
20
|
+
load 'rails/tasks/statistics.rake'
|
21
|
+
|
22
|
+
require 'bundler/gem_tasks'
|
23
|
+
require 'rake/testtask'
|
24
|
+
|
25
|
+
Rake::TestTask.new(:test) do |t|
|
26
|
+
t.libs << 'lib'
|
27
|
+
t.libs << 'test'
|
28
|
+
t.pattern = 'test/**/*_test.rb'
|
29
|
+
t.verbose = false
|
30
|
+
end
|
31
|
+
|
32
|
+
task default: :test
|
File without changes
|
data/config/routes.rb
ADDED
data/lib/back_office.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
require 'back_office/engine'
|
2
|
+
require 'back_office/authorization'
|
3
|
+
require 'back_office/form_builder'
|
4
|
+
|
5
|
+
module BackOffice
|
6
|
+
THRESHOLD = 8.days
|
7
|
+
|
8
|
+
def self.verifier
|
9
|
+
Rails.application.message_verifier('backoffice')
|
10
|
+
end
|
11
|
+
|
12
|
+
def self.encrypt(value)
|
13
|
+
verifier.generate([value, THRESHOLD.from_now])
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.decrypt(token)
|
17
|
+
value, expiration = verifier.verify(token)
|
18
|
+
value if expiration > Time.current
|
19
|
+
rescue ActiveSupport::MessageVerifier::InvalidSignature
|
20
|
+
nil
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module BackOffice
|
2
|
+
module Auth
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
before_action :authorize
|
7
|
+
delegate :current_user, to: :current_device
|
8
|
+
delegate :authorized?, to: :authorization
|
9
|
+
helper_method :current_user, :authorized?
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def current_device
|
15
|
+
@current_device ||= Device.current(device_token, device_attributes)
|
16
|
+
end
|
17
|
+
|
18
|
+
def device_token
|
19
|
+
authenticate_with_http_token { |token, _| token } || cookies.signed[:token]
|
20
|
+
end
|
21
|
+
|
22
|
+
def device_attributes
|
23
|
+
{
|
24
|
+
token: SecureRandom.urlsafe_base64,
|
25
|
+
user_agent: request.user_agent,
|
26
|
+
remote_ip: request.remote_ip
|
27
|
+
}
|
28
|
+
end
|
29
|
+
|
30
|
+
def sign_in(user)
|
31
|
+
current_device.update(user: user)
|
32
|
+
cookies.signed[:token] = current_device.token
|
33
|
+
end
|
34
|
+
|
35
|
+
def sign_out
|
36
|
+
cookies.delete(:token)
|
37
|
+
session.clear
|
38
|
+
end
|
39
|
+
|
40
|
+
def authorize
|
41
|
+
return true if authorized?(controller_name, action_name, resource)
|
42
|
+
|
43
|
+
respond_to do |format|
|
44
|
+
format.any(:js, :json) { head :unauthorized }
|
45
|
+
format.html { redirect_to(root_path, alert: t(:unauthorized)) }
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def authorization
|
50
|
+
@authorization ||= authorization_class.new(current_user)
|
51
|
+
end
|
52
|
+
|
53
|
+
def authorization_class
|
54
|
+
"#{self.class.parent_name || 'Application'}Authorization".constantize
|
55
|
+
end
|
56
|
+
|
57
|
+
def resource
|
58
|
+
nil
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module BackOffice
|
2
|
+
class Authorization
|
3
|
+
def initialize(current_user)
|
4
|
+
raise NotImplementedError
|
5
|
+
end
|
6
|
+
|
7
|
+
def authorized?(controller, action, resource = nil)
|
8
|
+
if rule = rules.dig(controller.to_sym, action.to_sym)
|
9
|
+
rule == true || resource && rule.call(resource)
|
10
|
+
else
|
11
|
+
false
|
12
|
+
end
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def authorize(controller, *actions, &block)
|
18
|
+
actions.flatten.each { |action| rules[controller][action] = (block || true) }
|
19
|
+
end
|
20
|
+
|
21
|
+
def rules
|
22
|
+
@rules ||= Hash.new { |hash, key| hash[key] = {} }
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
module BackOffice
|
2
|
+
class Cursor
|
3
|
+
attr_reader :scopes, :params, :options
|
4
|
+
|
5
|
+
def initialize(default_scope, params, options = {})
|
6
|
+
@scopes = Hash.new(default_scope).merge!(options.delete(:scopes) || {})
|
7
|
+
@params = params.permit(:scope, :query, :sort, :page, :filters).to_h
|
8
|
+
@options = options
|
9
|
+
@scope = params[:scope].to_s
|
10
|
+
end
|
11
|
+
|
12
|
+
def results
|
13
|
+
scopes[@scope.to_sym].search(params)
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,117 @@
|
|
1
|
+
module BackOffice
|
2
|
+
class FormBuilder < ActionView::Helpers::FormBuilder
|
3
|
+
FIELD_CLASS = 'field'.freeze
|
4
|
+
LABEL_CLASS = 'label'.freeze
|
5
|
+
ACTION_CLASS = 'action'.freeze
|
6
|
+
HINT_CLASS = 'hint'.freeze
|
7
|
+
ERRORS_CLASS = 'errors'.freeze
|
8
|
+
FIELD_METHODS = %w(
|
9
|
+
text_field
|
10
|
+
number_field
|
11
|
+
text_area
|
12
|
+
email_field
|
13
|
+
phone_field
|
14
|
+
password_field
|
15
|
+
file_field
|
16
|
+
collection_select
|
17
|
+
collection_check_boxes
|
18
|
+
check_box
|
19
|
+
date_select
|
20
|
+
time_zone_select
|
21
|
+
).freeze
|
22
|
+
|
23
|
+
delegate :content_tag, :tag, :capture, to: :@template
|
24
|
+
|
25
|
+
FIELD_METHODS.each do |method_name|
|
26
|
+
define_method method_name do |name, *args, &block|
|
27
|
+
options = args.extract_options!
|
28
|
+
hint = options.delete(:hint)
|
29
|
+
confirm = options.delete(:confirm)
|
30
|
+
classes = object.class.validators_on(name).map { |v| v.to_s.underscore }
|
31
|
+
content = field_label(name, options) +
|
32
|
+
super(name, *args, options, &block) +
|
33
|
+
discloser(method_name) +
|
34
|
+
confirmation(confirm) +
|
35
|
+
hint_label(hint)
|
36
|
+
|
37
|
+
content_tag(:div, content, class: classes.push(FIELD_CLASS))
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def submit(value = nil, options = {}, &block)
|
42
|
+
content = if block
|
43
|
+
block_before ? capture(&block) + super : super + capture(&block)
|
44
|
+
else
|
45
|
+
super
|
46
|
+
end
|
47
|
+
|
48
|
+
content_tag(:div, content, class: ACTION_CLASS)
|
49
|
+
end
|
50
|
+
|
51
|
+
def field_input(confirm: false)
|
52
|
+
field = object.field
|
53
|
+
options = { label: field.name, hint: field.hint, confirm: confirm }
|
54
|
+
|
55
|
+
case field.data_type
|
56
|
+
when 'string'
|
57
|
+
if field.options.present?
|
58
|
+
collection_select(:value, field.options.lines.map(&:chomp), :itself, :itself, options)
|
59
|
+
elsif field.scale.positive?
|
60
|
+
text_area(:value, options.merge(rows: field.scale))
|
61
|
+
else
|
62
|
+
text_field(:value, options)
|
63
|
+
end
|
64
|
+
when 'datetime'
|
65
|
+
text_field(:value, options.merge(class: 'datepicker'))
|
66
|
+
when 'boolean'
|
67
|
+
check_box(:value, options)
|
68
|
+
else
|
69
|
+
text_field(:value, options)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def confirmation(confirm)
|
74
|
+
check_box(:confirm, label: false, hint: false) if confirm
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def field_label(name, options)
|
80
|
+
base_text = case options[:label]
|
81
|
+
when false
|
82
|
+
''
|
83
|
+
when nil
|
84
|
+
object.class.human_attribute_name(name)
|
85
|
+
else
|
86
|
+
options[:label]
|
87
|
+
end
|
88
|
+
|
89
|
+
if base_text.present? && object.errors[name].first
|
90
|
+
|
91
|
+
|
92
|
+
end
|
93
|
+
label_text = label_and_error_text(name, options)
|
94
|
+
|
95
|
+
label(name, label_text, class: css_classes)
|
96
|
+
end
|
97
|
+
|
98
|
+
def label_and_error_text(name, options)
|
99
|
+
label_text = options.fetch(:label, )
|
100
|
+
error_text =
|
101
|
+
|
102
|
+
[label_text, error_text].compact.join(SEPARATOR)
|
103
|
+
end
|
104
|
+
|
105
|
+
def objectify_options(options)
|
106
|
+
super.except(:label)
|
107
|
+
end
|
108
|
+
|
109
|
+
def hint_label(text)
|
110
|
+
content_tag(:span, text.try(:html_safe), class: HINT_CLASS) unless text == false
|
111
|
+
end
|
112
|
+
|
113
|
+
def discloser(method_name, css_class = 'discloser')
|
114
|
+
method_name.to_s.end_with?('select') ? content_tag(:span, class: css_class) {} : ''
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module BackOffice
|
2
|
+
class Password
|
3
|
+
include ActiveModel::Model
|
4
|
+
|
5
|
+
attr_accessor :email, :plaintext, :plaintext_confirmation
|
6
|
+
|
7
|
+
validates :plaintext, on: :update, presence: true, confirmation: true
|
8
|
+
|
9
|
+
def update(attrs)
|
10
|
+
self.plaintext = attrs[:plaintext]
|
11
|
+
self.plaintext_confirmation = attrs[:plaintext_confirmation]
|
12
|
+
|
13
|
+
if valid?(:update)
|
14
|
+
user.update(password: plaintext)
|
15
|
+
true
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def user
|
20
|
+
User.find_by(email: email) if email
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module Backoffice
|
2
|
+
class Reset < Password
|
3
|
+
attr_reader :token
|
4
|
+
validates :email, on: :create, presence: true
|
5
|
+
validate :user_existence, on: :create
|
6
|
+
|
7
|
+
def self.find(token)
|
8
|
+
if email = Backoffice.decrypt(token)
|
9
|
+
new(email: email, token: token)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_param
|
14
|
+
token
|
15
|
+
end
|
16
|
+
|
17
|
+
def save
|
18
|
+
if token.nil? && valid?(:create)
|
19
|
+
@token = Backoffice.encrypt(email)
|
20
|
+
true
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def user_existence
|
27
|
+
errors.add(:email, :not_registered) unless user
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,136 @@
|
|
1
|
+
module BackOffice
|
2
|
+
module Rest
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
include Auth
|
5
|
+
|
6
|
+
def index
|
7
|
+
@cursor = BackOffice::Cursor.new(default_scope, params, cursor_options)
|
8
|
+
end
|
9
|
+
|
10
|
+
def create
|
11
|
+
resource.attributes = resource_params
|
12
|
+
|
13
|
+
if resource.save
|
14
|
+
created
|
15
|
+
success
|
16
|
+
else
|
17
|
+
failure
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def update
|
22
|
+
if resource.update(resource_params)
|
23
|
+
updated
|
24
|
+
success
|
25
|
+
else
|
26
|
+
failure
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def destroy
|
31
|
+
if resource.destroy
|
32
|
+
deleted
|
33
|
+
success
|
34
|
+
else
|
35
|
+
failure
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def resource
|
42
|
+
instance_variable_get("@#{param_key}") ||
|
43
|
+
instance_variable_set("@#{param_key}", (find_resource || build_resource))
|
44
|
+
end
|
45
|
+
|
46
|
+
def param_key
|
47
|
+
resource_class.model_name.param_key
|
48
|
+
end
|
49
|
+
|
50
|
+
def find_resource
|
51
|
+
find_scope.find(params[:id]) if params[:id].present?
|
52
|
+
end
|
53
|
+
|
54
|
+
def find_scope
|
55
|
+
resource_class
|
56
|
+
end
|
57
|
+
|
58
|
+
def resource_class
|
59
|
+
controller_name.classify.constantize
|
60
|
+
end
|
61
|
+
|
62
|
+
def build_resource
|
63
|
+
resource_class.new(build_params)
|
64
|
+
end
|
65
|
+
|
66
|
+
def build_params
|
67
|
+
{}
|
68
|
+
end
|
69
|
+
|
70
|
+
def default_scope
|
71
|
+
resource_class.order(created_at: :desc)
|
72
|
+
end
|
73
|
+
|
74
|
+
def cursor_options
|
75
|
+
{}
|
76
|
+
end
|
77
|
+
|
78
|
+
def success
|
79
|
+
respond_to do |format|
|
80
|
+
format.js
|
81
|
+
format.json { json_success }
|
82
|
+
format.html { html_success }
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
def failure
|
87
|
+
respond_to do |format|
|
88
|
+
format.js
|
89
|
+
format.json { json_failure }
|
90
|
+
format.html { html_failure }
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def html_success
|
95
|
+
redirect_to after_path, notice: success_notice
|
96
|
+
end
|
97
|
+
|
98
|
+
def json_success
|
99
|
+
case action_name.to_sym
|
100
|
+
when :create then render :show, status: :created
|
101
|
+
when :update then render :show, status: :ok
|
102
|
+
when :destroy then head :ok
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def html_failure
|
107
|
+
render(resource.new_record? ? :new : :edit)
|
108
|
+
end
|
109
|
+
|
110
|
+
def json_failure
|
111
|
+
render status: :unprocessable_entity, json: { errors: resource.errors.messages }
|
112
|
+
end
|
113
|
+
|
114
|
+
def resource_params
|
115
|
+
params.require(param_key).permit(permitted_attributes)
|
116
|
+
end
|
117
|
+
|
118
|
+
def permitted_attributes
|
119
|
+
[]
|
120
|
+
end
|
121
|
+
|
122
|
+
def success_notice
|
123
|
+
nil
|
124
|
+
end
|
125
|
+
|
126
|
+
def after_path
|
127
|
+
url_for controller: controller_name, action: :index
|
128
|
+
end
|
129
|
+
|
130
|
+
def created; end
|
131
|
+
|
132
|
+
def updated; end
|
133
|
+
|
134
|
+
def deleted; end
|
135
|
+
end
|
136
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
module BackOffice
|
2
|
+
module Searchable
|
3
|
+
extend ActiveSupport::Concern
|
4
|
+
|
5
|
+
included do
|
6
|
+
include PgSearch
|
7
|
+
searchable_by :id
|
8
|
+
end
|
9
|
+
|
10
|
+
class_methods do
|
11
|
+
def sort_options
|
12
|
+
@sort_options ||= {}
|
13
|
+
end
|
14
|
+
|
15
|
+
def default_searchable_options
|
16
|
+
{
|
17
|
+
against: [],
|
18
|
+
using: { tsearch: { prefix: true } }
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
def searchable_by(*attrs)
|
23
|
+
options = attrs.extract_options!
|
24
|
+
scope_options = default_searchable_options.deep_merge(options).merge!(against: attrs)
|
25
|
+
|
26
|
+
pg_search_scope(:search_by_query, scope_options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def filterable_by(key, filter_scope = nil)
|
30
|
+
scope("filter_#{key}", filter_scope || ->(filter_param) { where(key => filter_param) })
|
31
|
+
end
|
32
|
+
|
33
|
+
def filter_with(key, value)
|
34
|
+
send("filter_#{key}", value)
|
35
|
+
end
|
36
|
+
|
37
|
+
def sortable_by(key, asc_statement, desc_statement = nil)
|
38
|
+
sort_options[key.to_s] = asc_statement
|
39
|
+
sort_options["-#{key}"] = desc_statement || asc_statement.gsub(/ASC/i, 'DESC')
|
40
|
+
end
|
41
|
+
|
42
|
+
def sort_with(param)
|
43
|
+
sort_statement = sort_options.fetch(param) do
|
44
|
+
sort_parts = param.split('-')
|
45
|
+
sort_direction = sort_parts.length > 1 ? 'DESC' : 'ASC'
|
46
|
+
sort_column = sort_parts.last
|
47
|
+
sort_column = :created_at unless column_names.include?(sort_column)
|
48
|
+
|
49
|
+
"#{table_name}.#{sort_column} #{sort_direction}"
|
50
|
+
end
|
51
|
+
|
52
|
+
reorder(sort_statement)
|
53
|
+
end
|
54
|
+
|
55
|
+
def search(params)
|
56
|
+
results = page(params[:page])
|
57
|
+
|
58
|
+
params.fetch(:filters, {}).each do |filter_name, filter_value|
|
59
|
+
results = results.filter_with(filter_name, filter_value) if filter_value.present?
|
60
|
+
end
|
61
|
+
|
62
|
+
results = results.search_by_query(params[:query]) if params[:query].present?
|
63
|
+
results = results.sort_with(params[:sort]) if params[:sort].present?
|
64
|
+
|
65
|
+
results
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: back_office
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gustavo Beathyate
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2017-01-21 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: '5.0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '5.0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: pg
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0.19'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0.19'
|
41
|
+
description: Back Office provides base objects for building custom Rails apps.
|
42
|
+
email:
|
43
|
+
- gustavo.bt@me.com
|
44
|
+
executables: []
|
45
|
+
extensions: []
|
46
|
+
extra_rdoc_files: []
|
47
|
+
files:
|
48
|
+
- README.md
|
49
|
+
- Rakefile
|
50
|
+
- app/assets/config/back_office_manifest.js
|
51
|
+
- config/routes.rb
|
52
|
+
- lib/back_office.rb
|
53
|
+
- lib/back_office/auth.rb
|
54
|
+
- lib/back_office/authorization.rb
|
55
|
+
- lib/back_office/cursor.rb
|
56
|
+
- lib/back_office/engine.rb
|
57
|
+
- lib/back_office/form_builder.rb
|
58
|
+
- lib/back_office/password.rb
|
59
|
+
- lib/back_office/reset.rb
|
60
|
+
- lib/back_office/rest.rb
|
61
|
+
- lib/back_office/searchable.rb
|
62
|
+
- lib/back_office/version.rb
|
63
|
+
- lib/tasks/back_office_tasks.rake
|
64
|
+
homepage: http://goddamnhippie.github.io/back_office
|
65
|
+
licenses:
|
66
|
+
- MIT
|
67
|
+
metadata: {}
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 2.6.9
|
85
|
+
signing_key:
|
86
|
+
specification_version: 4
|
87
|
+
summary: Back Office is a Rails engine that packs shared app functionality.
|
88
|
+
test_files: []
|