Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/documents #8

Merged
merged 14 commits into from
Jul 23, 2019
11 changes: 10 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ The backoffice for laws and pathways

- Ruby v2.5.5
- Rails v5.2.3
- Node v10

## Local installation

Expand All @@ -15,6 +16,10 @@ These are the steps to run the project locally:

On the project's root run `bundle install`.

### Installing npm dependencies

`yarn`

### Database

#### Create database schema
Expand All @@ -23,7 +28,11 @@ On the project's root run `bundle install`.

### Run the server

`bundle exec rails s'` and access the project on `http://localhost:3000`
`yarn start'` and access the project on `http://localhost:3000`

### Run the tests

`yarn test`

## Docker

Expand Down
36 changes: 36 additions & 0 deletions app/admin/documents.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
ActiveAdmin.register Document do
menu priority: 3

decorate_with DocumentDecorator

filter :name_contains
filter :documentable_type, label: 'Attached to'

config.batch_actions = false

actions :all, except: [:new, :edit, :create, :update]

show do
attributes_table do
row :id
row :name
row :link, &:open_link
row :last_verified_on
row :created_at
row :updated_at
end
end

index do
column 'name', &:name_link
column 'Attached To', :documentable
column :last_verified_on
actions
end

controller do
def scoped_collection
super.includes(:documentable)
end
end
end
22 changes: 9 additions & 13 deletions app/admin/litigations.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,10 @@

decorate_with LitigationDecorator

permit_params :title, :location_id, :document_type, :summary, :core_objective
permit_params :title, :location_id, :document_type, :summary, :core_objective,
documents_attributes: [
:id, :_destroy, :name, :external_url, :type, :file
]

filter :title_contains
filter :summary_contains
Expand Down Expand Up @@ -36,6 +39,7 @@
row :core_objective
row :created_at
row :updated_at
list_row 'Documents', :document_list
end
end

Expand All @@ -51,21 +55,13 @@
end
end

form do |f|
f.semantic_errors(*f.object.errors.keys)
form partial: 'form'

f.inputs do
f.input :title
f.input :location
f.input :document_type, as: :select, collection: array_to_select_collection(Litigation::DOCUMENT_TYPES)
f.input :summary, as: :trix
f.input :core_objective, as: :trix
controller do
def scoped_collection
super.includes(:location)
end

f.actions
end

controller do
def find_resource
scoped_collection.friendly.find(params[:id])
end
Expand Down
46 changes: 40 additions & 6 deletions app/assets/stylesheets/active_admin.scss
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
$am-theme-primary: #00689f;
$am-theme-accent: #7dd2f7;
$am-theme-accent-fallback: #ff7547;

$padding: 20px;

@import 'admin/settings';
@import 'trix/dist/trix';
@import 'activeadmin_addons/all';

// SASS variable overrides must be declared before loading up Active Admin's styles.
//
// To view the variables that Active Admin provides, take a look at
Expand All @@ -23,6 +19,22 @@ $padding: 20px;
display: none;
}

.button {
margin: 0;

&--raised {
@include am-btn-raised;
}

&--red {
background-color: $admin-red;

&:hover {
background-color: lighten($admin-red, 20%);
}
}
}

.table_tools {
.scope {
margin: 0;
Expand Down Expand Up @@ -128,3 +140,25 @@ form {
}
}
}

// Documents

.document-list {
&__actions {
padding: 16px;
}
}

.inline-fields {
display: flex;
justify-content: space-around;
align-items: flex-end;

li {
margin: 0;
}

.flex-grow-1 {
flex-grow: 1;
}
}
9 changes: 9 additions & 0 deletions app/assets/stylesheets/admin/settings.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
$padding: 20px;

//Colors
$admin-red: red;

// Active Material Theme Colors Change
$am-theme-primary: #00689f;
$am-theme-accent: #7dd2f7;
$am-theme-accent-fallback: $admin-red;
17 changes: 17 additions & 0 deletions app/decorators/document_decorator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
class DocumentDecorator < Draper::Decorator
delegate_all

def name_link
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what do you think about renaming those methods ..
name_link => document_page_link (or just document_link)
open_link => document_url_link
.. to indicate where those links actually go.

h.link_to model.name, h.admin_document_path(model)
end

def open_link
h.link_to model.name, model.url, target: '_blank'
end

def last_verified_on
return 'N/A' if model.uploaded?

model.last_verified_on
end
end
8 changes: 8 additions & 0 deletions app/decorators/litigation_decorator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,4 +16,12 @@ def summary
def core_objective
model.core_objective.html_safe
end

def document_list
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

document_links?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea.

return [] if model.documents.empty?

model.documents.map do |document|
h.link_to document.name, document.url, target: '_blank'
end
end
end
2 changes: 1 addition & 1 deletion app/javascript/controllers/dependent_input_controller.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Controller } from "stimulus";
import { Controller } from 'stimulus';

export default class extends Controller {
connect() {
Expand Down
36 changes: 36 additions & 0 deletions app/javascript/controllers/document_list_controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Controller } from 'stimulus';

export default class extends Controller {
static targets = [ 'links', 'templateExternal', 'templateUploaded']

connect() {
this.wrapperClass = this.data.get('wrapperClass') || 'nested-fields';
}

addRecord(event) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This code here looks like it shouldn't be directly related with "documents", rather, it is more like generic add/remove resource logic, right?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are right. I will change that

event.preventDefault();

const templateName = event.target.dataset['template'];
const content = this[`template${templateName}Target`]
.innerHTML
.replace(/NEW_RECORD/g, new Date().getTime());

this.linksTarget.insertAdjacentHTML('beforebegin', content);
}

removeRecord(event) {
event.preventDefault();

const wrapper = event.target.closest('.' + this.wrapperClass);

// New records are simply removed from the page
if (wrapper.dataset.newRecord == 'true') {
wrapper.remove();

// Existing records are hidden and flagged for deletion
} else {
wrapper.querySelector('input[name*="_destroy"]').value = 1;
wrapper.style.display = 'none';
}
}
}
51 changes: 51 additions & 0 deletions app/models/document.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# == Schema Information
#
# Table name: documents
#
# id :bigint not null, primary key
# name :string not null
# type :string not null
# external_url :text
# language :string
# last_verified_on :date
# documentable_type :string
# documentable_id :bigint
# created_at :datetime not null
# updated_at :datetime not null
#

class Document < ApplicationRecord
self.inheritance_column = nil

TYPES = %w[uploaded external].freeze
enum type: array_to_enum_hash(TYPES)

belongs_to :documentable, polymorphic: true

has_one_attached :file

before_create :set_last_verified_on

validates :external_url, url: true, presence: true, if: :external?
validates :file, attached: true, if: :uploaded?

validates_presence_of :name, :type

def url
return file_url if uploaded?

external_url
end

private

def file_url
return unless file.attached?

Rails.application.routes.url_helpers.polymorphic_url(file, only_path: true)
end

def set_last_verified_on
self.last_verified_on = Time.zone.now.to_date
end
end
3 changes: 3 additions & 0 deletions app/models/litigation.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,9 @@ class Litigation < ApplicationRecord

belongs_to :location
has_many :litigation_sides
has_many :documents, as: :documentable, dependent: :destroy

accepts_nested_attributes_for :documents, allow_destroy: true

validates_presence_of :title, :slug, :document_type
end
5 changes: 5 additions & 0 deletions app/validators/attached_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class AttachedValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
record.errors.add(attribute, :attached, options) unless value.attached?
end
end
16 changes: 16 additions & 0 deletions app/validators/url_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
class UrlValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
return unless value.present?
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we combine return unless value.present? and ..unless url_valid?(value) in single condition?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, I will make two separate guard clauses. I think 2 will be more readable.


record.errors[attribute] << (options[:message] || 'must be a valid URL') unless url_valid?(value)
end

def url_valid?(url)
url = begin
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think url = URI.parse(url) rescue false would be enough here

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Rubocop changed that automagically.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, let's not mess with RuboCop 🤖

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already did. lol. I don't like that rule

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

haha ok )

URI.parse(url)
rescue StandardError
false
end
url.is_a?(URI::HTTP) || url.is_a?(URI::HTTPS)
end
end
17 changes: 17 additions & 0 deletions app/views/admin/documents/_fields.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
<% is_new_record = form.object.new_record? %>

<%= content_tag :div, class: "nested-fields inline-fields", data: { new_record: is_new_record } do %>
<%= form.input :type, as: :hidden, input_html: { value: form.object.type } %>
<div style="width: 33%;">
<% if form.object.external? %>
<%= form.input :external_url, as: :string, label: 'Provide external url' %>
<% else %>
<%= form.input :file, as: :file %>
<% end %>
</div>
<%= form.input :name, wrapper_html: { class: 'flex-grow-1' } %>
<li class="input">
<%= button_tag "Remove", type: 'button', class: 'button button--raised button--red', data: { action: "click->document-list#removeRecord" } %>
</li>
<%= form.hidden_field :_destroy %>
<% end %>
22 changes: 22 additions & 0 deletions app/views/admin/documents/_list.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<div class="document-list" data-controller="document-list">
<template data-target="document-list.templateExternal">
<%= form.semantic_fields_for :documents, Document.new(type: 'external'), child_index: 'NEW_RECORD' do |document| %>
<%= render "admin/documents/fields", form: document %>
<% end %>
</template>

<template data-target="document-list.templateUploaded">
<%= form.semantic_fields_for :documents, Document.new(type: 'uploaded'), child_index: 'NEW_RECORD' do |document| %>
<%= render "admin/documents/fields", form: document %>
<% end %>
</template>

<%= form.semantic_fields_for :documents do |document| %>
<%= render "admin/documents/fields", form: document %>
<% end %>

<div class="document-list__actions" data-target="document-list.links">
<%= button_tag "Add External URL", type: 'button', class: 'button button--raised', data: { action: "click->document-list#addRecord", template: 'External' } %>
<%= button_tag "Upload a new file", type: 'button', class: 'button button--raised', data: { action: "click->document-list#addRecord", template: 'Uploaded' } %>
</div>
</div>