Skip to content

Commit

Permalink
Add events including tests.
Browse files Browse the repository at this point in the history
0015, 0195, 0196, 0197, 0199, 0201, 0211

Create route for community events.  Use events#index and apply a filter for
the community.

0020

0024

0025

Add sample map of location for event.

0027

Add sample directions

0028

Remove styles.  Will add style after it is functional.

0048

Remove disabled code

0049

Translate all the things.

0055

0090

0115, 0120, 0123

Create validation and model test for events.  Improve factory.

0128

Add test for events in the past.

Adjust secondary-actions to match design pattern.

0195

0197

Make events index page look better.  Show only future events.

0179

Make event list look better.

Move edit link from show to index.  Factor out showMap code which is also used
by the microcosm show page (this was because there was a bug with showing the
map on the event page).

0180

Replace content_tag with tag.

In the "take one" rubocop reported this.  In "take two" rubocop does not
complain.  I agree with rubocop.

0202

Factor out formMapInput from event/new form and let community/new also use it.

0182

0203

Make sure events are ordered.  Separate past and future events.

0183

Add more tests for access control as non-organizer.

0213, 0125

Get tests working by not using :controller/:action in tests.

0247

Warn if event was created in or updated to the past.

Fixes #35

0234
  • Loading branch information
openbrian committed May 10, 2023
1 parent d3c28a0 commit df589ac
Show file tree
Hide file tree
Showing 20 changed files with 764 additions and 5 deletions.
9 changes: 6 additions & 3 deletions app/abilities/ability.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ def initialize(user)
can [:confirm, :confirm_resend, :confirm_email], :confirmation
can [:index, :rss, :show, :comments], DiaryEntry
can [:index], CommunityLink
can [:index, :show], Event
can [:index], Note
can [:lost_password, :reset_password], :password
can [:index, :show], Redaction
Expand Down Expand Up @@ -66,9 +67,11 @@ def initialize(user)
}
can [:create, :new, :step_up], Community
can [:edit, :update], Community, user_is_community_organizer
can [:edit, :create, :destroy, :new, :update], CommunityLink, { :community => user_is_community_organizer }
can [:create, :destroy], CommunityMember, { :user_id => user.id }
can [:destroy, :edit, :update], CommunityMember, { :community => user_is_community_organizer }
can [:edit, :create, :destroy, :new, :update], CommunityLink, :community => user_is_community_organizer
can [:create, :destroy], CommunityMember, :user_id => user.id
can [:destroy, :edit, :update], CommunityMember, :community => user_is_community_organizer
can [:destroy], CommunityMember, :user_id => user.id
can [:new, :create], Event, :community => user_is_community_organizer

if user.moderator?
can [:hide, :hidecomment], DiaryEntry
Expand Down
9 changes: 9 additions & 0 deletions app/assets/javascripts/event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
/*global showMap,formMapInput*/

$(document).ready(function () {
if ($("#event_map_form").length) {
formMapInput("event_map_form", "event");
} else if ($("#event_map_show").length) {
showMap("event_map_show");
}
});
85 changes: 85 additions & 0 deletions app/controllers/events_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
class EventsController < ApplicationController
layout "site"
before_action :authorize_web
before_action :set_event, :only => [:edit, :show]
# This needs to be one before load_and_authorize_resource, so cancancan will be handed
# an event that contains a community, based on the input parameter community_id.
before_action :set_params_for_new, :only => [:new]

load_and_authorize_resource

# GET /events
# GET /events.json
def index
if params[:community_id]
@community = Community.friendly.find(params[:community_id])
@events = @community.events
else
@community = nil
@events = Event.all
end
rescue ActiveRecord::RecordNotFound
@not_found_community = params[:community_id]
render :template => "communities/no_such_community", :status => :not_found
end

# GET /events/new
def new
@title = t ".new"
@event = Event.new(event_params_new)
end

# POST /events
# POST /events.json
def create
@event = Event.new(event_params)

respond_to do |format|
if @event.save
warn_if_event_in_past
format.html { redirect_to @event, :notice => t(".success") }
format.json { render :show, :status => :created, :location => @event }
else
format.html { render :new }
format.json { render :json => @event.errors, :status => :unprocessable_entity }
end
end
end

# GET /events/1/edit
def edit; end

# GET /events/1
# GET /events/1.json
def show
@community = Community.friendly.find(params[:community_id]) if params[:community_id]
rescue ActiveRecord::RecordNotFound
@not_found_community = params[:community_id]
render :template => "communities/no_such_community", :status => :not_found
end

private

def warn_if_event_in_past
flash[:warning] = t "events.show.past" if @event.past?
end

def set_event
@event = Event.find(params[:id])
end

def set_params_for_new
@params = event_params_new
end

def event_params
params.require(:event).permit(
:title, :moment, :location, :location_url,
:latitude, :longitude, :description, :community_id
)
end

def event_params_new
params.require(:event).permit(:community_id)
end
end
9 changes: 9 additions & 0 deletions app/helpers/events_helper.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module EventsHelper
def event_location(event)
if event.location_url.present?
link_to event.location, event.location_url
else
event.location
end
end
end
1 change: 1 addition & 0 deletions app/models/community.rb
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ class Community < ApplicationRecord

belongs_to :leader, :class_name => "User"
has_many :community_links
has_many :events, -> { order(:moment) }, :inverse_of => :community

validates :name, :presence => true, :length => 1..255, :characters => true
validates :description, :presence => true, :length => 1..1023, :characters => true
Expand Down
53 changes: 53 additions & 0 deletions app/models/event.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# == Schema Information
#
# Table name: events
#
# id :bigint(8) not null, primary key
# title :string not null
# moment :datetime not null
# location :string not null
# location_url :string
# latitude :float
# longitude :float
# description :text not null
# community_id :integer not null
# created_at :datetime not null
# updated_at :datetime not null
#

class Event < ApplicationRecord
belongs_to :community

scope :future, -> { where("moment >= ?", Time.now.utc) }
scope :past, -> { where("moment < ?", Time.now.utc) }

validates :moment, :datetime_format => true
validates :location, :length => { :maximum => 255 }, :presence => true
# While latitude and longitude below will implicitly convert blanks to nil,
# the string/url here will not and I don't know why.
validates(
:location_url,
:allow_nil => true, :length => { :maximum => 255 },
:url => { :allow_nil => true, :allow_blank => true, :schemes => ["https"] }
)
validates(
:latitude,
:allow_nil => true,
:numericality => {
:greater_than_or_equal_to => -90,
:less_than_or_equal_to => 90
}
)
validates(
:longitude,
:allow_nil => true,
:numericality => {
:greater_than_or_equal_to => -180,
:less_than_or_equal_to => 180
}
)

def past?
moment < Time.now.utc
end
end
16 changes: 16 additions & 0 deletions app/validators/datetime_format_validator.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
require "date"

class DatetimeFormatValidator < ActiveModel::EachValidator
# No need to pull in validates_timeless for just a simple validation.
def validate_each(record, attribute, _value)
# By this point in time, rails has already converted an invalid _value to
# Nil. With built in rails validation, there's no good way to say the
# input is not a valid date. Validate the user input.
before_value = record.read_attribute_before_type_cast(attribute)
return if before_value.is_a? Time

Date.iso8601(before_value)
rescue ArgumentError
record.errors.add(attribute, options[:message] || I18n.t("validations.invalid_datetime_range"))
end
end
15 changes: 15 additions & 0 deletions app/views/communities/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,21 @@
</div>
<div class="row">
<div class="col-sm">
<h2>
<%= link_to t(".events"), community_community_events_path(@community) %>
</h2>
<p>
<% if current_user && @community.organizer?(current_user) %>
<%= link_to t(".new_event"), new_event_path(:community_id => @community.id) %>
<% end %>
</p>
<ul>
<% @community.events.future.each do |event| %>
<li>
<%= link_to "#{l(event.moment, :format => '%e %B')} - #{event.title}", community_event_path(@community, event) %>
</li>
<% end %>
</ul>
<h2>
<%= t(".recent_changes") %>
</h2>
Expand Down
48 changes: 48 additions & 0 deletions app/views/events/_form.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<%= javascript_include_tag "event" %>

<%= bootstrap_form_for(@event) do |form| %>
<% if event.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(event.errors.count, "error") %> prohibited this event from being saved:</h2>
<ul>
<% event.errors.full_messages.each do |message| %>
<li><%= message %></li>
<% end %>
</ul>
</div>
<% end %>
<div class="row">
<%= form.text_field :title, :id => :event_title %>
</div>
<div class="row">
<%= form.datetime_local_field :moment, :pattern => "[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}" %>
</div>
<div class="row">
<%= form.text_field :location, :id => :event_location %>
</div>
<div class="row">
<%= form.text_field :location_url, :id => :event_location_url %>
</div>
<div class="row">
<%= form.text_area :description, :id => :event_description %>
</div>
<fieldset>
<div class='row'>
<%= form.text_field :latitude, :id => "event_latitude" %>
</div>
<div class='row'>
<%= form.text_field :longitude, :id => "event_longitude" %>
</div>
<div class="event_set_location">
<div id="event_map_form" class="content_map"></div>
</div>
</fieldset>
<% if @event&.community_id %>
<%= form.hidden_field(:community_id, :value => @event.community_id) %>
<% else %>
<div class="row">
<%= collection_select(:event, :community_id, Community.all, :id, :name, :prompt => true) %>
</div>
<% end %>
<%= form.primary %>
<% end %>
25 changes: 25 additions & 0 deletions app/views/events/_index_list.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
<% if !events.empty? %>
<h2>
<%= header %>
</h2>
<table class="table table-borderless table-striped">
<thead>
<tr>
<th><%= t(".moment") %></th>
<th><%= t(".title") %></th>
<th><%= t(".location") %></th>
<th><%= t(".community") %></th>
</tr>
</thead>
<tbody>
<% events.each do |event| %>
<tr>
<td><%= l(event.moment, :format => :blog) %></td>
<td><%= link_to event.title, event %></td>
<td><%= event.location %></td>
<td><%= link_to event.community.name, community_path(event.community) %></td>
</tr>
<% end %>
</tbody>
</table>
<% end %>
15 changes: 15 additions & 0 deletions app/views/events/index.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<% content_for :heading do %>
<h1><%= @community&.name %> <%= t(".events") %></h1>
<nav class="secondary-actions">
<ul class="clearfix">
<% if current_user && @community&.organizer?(current_user) %>
<li><%= link_to image_tag("new.png", :class => "small_icon", :border => 0) + t(".new_event"), new_event_path(:event => { :community_id => @community.id }), :title => t(".new_event") %></li>
<% end %>
</ul>
</nav>
<% end %>

<p id="notice"><%= notice %></p>

<%= render :partial => "index_list", :locals => { :events => @events.future, :header => t(".upcoming_events") } %>
<%= render :partial => "index_list", :locals => { :events => @events.past, :header => t(".past_events") } %>
7 changes: 7 additions & 0 deletions app/views/events/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<% content_for :heading do %>
<h1>
<%= @title %>
</h1>
<% end %>

<%= render "form", :event => @event %>
69 changes: 69 additions & 0 deletions app/views/events/show.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<%= javascript_include_tag "event" %>

<% content_for :heading do %>
<h1>
<%= @event.title %>
</h1>
<% if current_user && @community&.organizer?(current_user) %>
<%= link_to t(".edit"), edit_event_path(@event) %>
<% end %>
<% end %>

<div class="container">
<div class="row">
<div class="col-sm">
<p>
<%= t(".when") %>: <%= l @event.moment, :format => :friendly %>
</p>
<p>
<%= t(".hosted_by") %>: <strong><%= link_to @event.community.name, community_path(@event.community) %></strong>
</p>
<p>
<%= t(".organized_by") %>: <strong>TBD</strong>
</p>
</div>
<div class="col-sm">
<p><%= t(".are_you_going") %></p>
</div>
<h1>
<%= @event.title %>
</h1>
<p>
<%= t(".hosted_by") %>: <strong><%= link_to @event.community.name, community_path(@event.community) %></strong>
</p>
<p>
<%= t(".organized_by") %>: <strong>TBD</strong>
</p>
</div>
<div>
<p><%= t(".are_you_going") %></p>
</div>
</div>
<hr />
<div class="row">
<div>
<p>
<strong><%= t(".description") %>:</strong>
<%= @event.description %>
</p>
</div>
<hr />
<div class="row">
<div class="col-sm">
<p>
<strong><%= t(".description") %>:</strong>
<%= @event.description %>
</p>
</div>
<div class="col-sm">
<p>
<%= t(".location") %>: <strong><%= event_location(@event) %></strong>
</p>
<p>
<a href="https://www.openstreetmap.org/directions?engine=fossgis_osrm_car&route=44.97067%2C-93.26822%3B38.90201%2C-77.04469#map=6/42.026/-85.156"><%= t(".directions_to") %></a>
</p>
<%# TODO: replace these attributes @map_coords %>
<%= tag.div :class => "content_map", :id => "event_map_show", :data => { :lat => @event.latitude, :lon => @event.longitude, :zoom => 11 } %>
</div>
</div>
</div>
Loading

0 comments on commit df589ac

Please sign in to comment.