Modelisation - rodinux/plannings_cinema GitHub Wiki

L'application a quatre objets principaux, l'objet Film, l'objet Seance, l'objet Village et l'objet User, puis ont été ajouter l'objet Classification pour les calssifications des films et Disponibilité pour un calendrier des indisponibilités des projectionnistes ou caissières.

Voici le schéma des tables dans le fichier.

Base de données

db/schema.rb :

# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.

ActiveRecord::Schema.define(version: 2017_04_06_102111) do

  # These are extensions that must be enabled in order to support this database
  enable_extension "plpgsql"

  create_table "classifications", id: :serial, force: :cascade do |t|
    t.string "nom_classification"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "classifications_films", id: false, force: :cascade do |t|
    t.integer "classification_id", null: false
    t.integer "film_id", null: false
  end

  create_table "disponibilites", id: :serial, force: :cascade do |t|
    t.string "nom"
    t.datetime "start_time"
    t.datetime "end_time"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

  create_table "films", id: :serial, force: :cascade do |t|
    t.string "titrefilm"
    t.string "description"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "distribution"
    t.string "affiche"
  end

  create_table "seances", id: :serial, force: :cascade do |t|
    t.string "projection"
    t.string "caisse"
    t.datetime "horaire"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "annulee"
    t.string "version"
    t.string "commentaire"
    t.integer "film_id"
    t.integer "village_id"
    t.string "extras"
    t.integer "billets_adultes"
    t.integer "billets_enfants"
    t.integer "billets_scolaires"
    t.integer "total_billets"
  end

  create_table "users", force: :cascade do |t|
    t.string "email", null: false
    t.string "crypted_password"
    t.string "salt"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.string "nom"
    t.string "prenom"
    t.string "telephone"
    t.string "role"
    t.index ["email"], name: "index_users_on_email", unique: true
  end

  create_table "villages", id: :serial, force: :cascade do |t|
    t.string "commune"
    t.string "salle"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
  end

end

Modèles

Modèles

Une association est créée entre les tables Films et Villages avec la table Seances, elles sont liées pas avec les colonnes film_id (qui aura le même nombre que l'id du film correspondant à la séance) et avec village_id (qui aura le même nombre que l'id du village correspondant à la séance). Les tables Films et Villages sont liés à travers la Séance. Une association aussi est créée entres les tables Classifications et Film avec la table Classifications_films et sont liés à travers la Séance.

Ceci grâce aux modèles :

models/seance.rb

class Seance < ApplicationRecord

  belongs_to :film, :inverse_of => :seances, optional: true
  belongs_to :village, :inverse_of => :seances, optional: true
  validates :film_id, :presence => true
  validates :village_id, :presence => true
  validates :horaire, :presence => true

  before_create :setup_default_value_for_new_seances
  #throw(:abort)
  before_update :setup_default_value_for_updated_seances
  #throw(:abort)

  def self.lieuxtest
    lieuxtest = Hash[
    "lamastre" => Seance.order(horaire: :asc).map{ |seance|
     seance if seance.village.commune.upcase == "LAMASTRE" },
    "vernoux"  => Seance.all.order(horaire: :asc).map{ |seance| seance if seance.village.commune.upcase == "VERNOUX" },
    "chalencon" => Seance.all.order(horaire: :asc).map{ |seance| seance if seance.village.commune.upcase == "CHALENCON" },
    "itinerance" => Seance.all.order(horaire: :asc).map{ |seance| seance if seance.village.commune.upcase != "LAMASTRE" &&
            seance.village.commune.upcase != "VERNOUX" && seance.village.commune.upcase != "CHALENCON" },
      "tous les lieux" => Seance.all.order(horaire: :asc).map{|seance| seance }
    ]
  end

  def self.seances_calendrier
    seances_calendrier = Seance.where({horaire: ((Date.today - 6.day)..(Date.today + 6.day))})
  end

  def self.seances_a_venir
        seances_a_venir = Seance.where({horaire: (Date.today..Date.today + 60)})
  end

  def self.seances_passees_3_semaines
    seances_passees_3_semaines = Seance.where({horaire: (3.week.ago..(Date.today + 1))}).order(horaire: :asc)
  end

  def self.seances_passees_1_mois
         seances_passees_1_mois = Seance.where({horaire: (30.days.ago..Date.today)}).order(horaire: :desc)
  end

  def self.seances_semaine
         seances_semaine = Seance.where({horaire: (Date.today.midnight..(Date.today + 7))}).order(horaire: :desc)
  end

  def self.seances_1_mois_avant_apres
      seances_1_mois_avant_apres = Seance.where({horaire: (1.month.ago..(Date.today + 60))}).order(horaire: :asc)
  end

  def self.seances_1_semaine_avant_2_mois_apres
      seances_1_semaine_avant_1_mois_apres = Seance.where({horaire: (1.week.ago..(Date.today + 60))}).order(horaire: :asc)
  end

  def self.seances_asc
    seances_asc = Seance.order(horaire: :asc)
  end

  def self.seances_a_completer_projection
    seances_a_completer_projection = Seance.where(projection: [nil, ""]).order(horaire: :asc)
  end

  def self.seances_a_completer_caisse
    seances_a_completer_caisse = Seance.where(caisse: [nil, ""]).order(horaire: :asc)
  end

  def self.seances_date_range
      seances_date_range = Seance.where({horaire: (range.to_i.days.ago..Date.today)})
  end

  def self.seances_annulee
      seances_annulee = Seance.where(annulee: "Annulée")
  end

  private

  def setup_default_value_for_new_seances
      if self.billets_adultes.blank?
        self.billets_adultes = 0
      end
      if self.billets_enfants.blank?
        self.billets_enfants = 0
      end
      if self.billets_scolaires.blank?
        self.billets_scolaires = 0
      end
      if self.total_billets.blank?
        self.total_billets = 0
      end
  end

  def setup_default_value_for_updated_seances
      self.total_billets = self.billets_adultes + self.billets_enfants + self.billets_scolaires
  end
end

Ici on remarque aussi des conditions, une séance doit avoir un film_id, un village_id et un horaire (la date et horaire du film) pour être validée avec la condition :validates. Ensuite, une condition setup_default_value_for_new_seances pour que les billets (enfants, adultes,scolaires et total) est une valeur de 0 au moment de créer une séance pour éviter une erreur sql qui n'accepte pas que sa valeur soit nulle. Une condition aussi setup_default_value_for_updated_seances qui permet d'additionner toutes les entrées (adultes, enfants, scolaires) quand on édite les entrées et que l'on met à jour une séance.

Une méthode self.lieuxtest avec une array qui va permettre de regrouper les séances par lieux de la tournée du cinéma d'Ecran Village ou "tous les lieux". Ceci permet de jongler avec des calendriers par lieu et des séances par lieu. Pour les vues, j'utilise ensuite une variable lieu qui se trouve dans les controleurs des objets concernés.

  before_action :get_lieu

    def get_lieu
      @lieu = params[:lieu]
    end

Les autres méthodes sont surtout utilisées pour ranger des séances dans des périodes définies.

models/film.rb

class Film < ApplicationRecord
    has_many :seances, :dependent => :destroy, :inverse_of => :film
    accepts_nested_attributes_for :seances, :allow_destroy => true
    has_many :villages, :through => :seances
    has_and_belongs_to_many :classifications
    validates :id, :uniqueness => true, :case_sensitive => false
    validates :titrefilm, :uniqueness => true, :case_sensitive => false


      def self.films_3_semaines
          films_3_semaines = Film.where({ updated_at: (3.week.ago.midnight..(Date.today + 1))}).order(updated_at: :desc)
      end

      def self.films_2_mois_avant
          films_2_mois_avant = Film.where({ updated_at: (2.month.ago.midnight..(Date.today + 1))})
      end

      def self.films_mois
        films_mois = Film.where({updated_at: 1.month.ago.midnight..(Date.today + 1)}).order(created_at: :asc)
      end

      def self.films_entrees
          films_entrees = film.seances.where(horaire: date_range)
      end
end

Ici on peut remarquer la condition :allow_destroy => true qui détruit les séances qui se rapportent à un film si on supprime un film. la relation avec le lieu à travers la séance et l'unicité de l'id d'un film et de son titre avec la condition :uniqueness => true, ceci par soucis de la méthode qui permet de récupérer les films venant du site d'Ecran Village à partir d'un fichier .json.

models/village.rb

class Village < ApplicationRecord
    has_many :seances, :dependent => :destroy, :inverse_of => :village
    accepts_nested_attributes_for :seances, :allow_destroy => true
    has_many :films, :through => :seances
    validates :id, :uniqueness => true, :case_sensitive => false
    validates :commune, :presence => true, :case_sensitive => false
    validates :salle, :presence => true, :case_sensitive => false
end

Ici on remarque juste le lien avec les seances et avec les films à travers elles avec la méthode :through.

models/user.rb

class User < ApplicationRecord
  authenticates_with_sorcery!

   before_create :setup_default_role_for_new_users

  ROLES = %w[guest manager admin]

  validates :password, length: { minimum: 5 }, if: -> { new_record? || changes["password"] }
  validates :password, confirmation: true, if: -> { new_record? || changes["password"] }
  validates :password_confirmation, presence: true, if: -> { new_record? || changes["password"] }
  validates :email, uniqueness: true
  validates :prenom, uniqueness: true

 def self.users_alphabet
     users_alphabet = User.order(prenom: :asc)
 end

 private

   def setup_default_role_for_new_users
     if self.role.blank?
       self.role = "guest"
     end
   end
end

La classe User pour les utilisateur est créée au départ avec la gem Sorcery qui permet l'authentification sécurisée. La condition before_create :setup_default_role_for_new_users permet de donner aux inscrits un role guest (invité) lors de l'inscription.

models/ability.rb

class Ability
  include CanCan::Ability

  def initialize(user)
    user ||= User.new # guest user (not logged in)

      if user.role == "manager"
       can :read, Village
       can :update, User
       can :update, Seance
       cannot :update, Village
       cannot :destroy, User
       cannot :destroy, Seance
       cannot :rails_admin
       cannot :import, :all

    end
    # alias_action :update, :destroy, :create, :to => :write
    if user.role == "admin"
      can :manage, :all
      # can :write, :all
      can :update, :all
      can :destroy, :all
      can :rails_admin
      can :dashboard
      can :import, :all
    end
    # https://github.com/CanCanCommunity/cancancan/wiki/Defining-Abilities
  end
end

Ici ce fichier est là pour les permissions suivant les roles des utilisateurs gérer grâces à la gem Cancancan. Le rôle manager, celui des bénévoles pour les projections et la caisse des séances, peut modifier une séance pour s'inscrire et modifier les utilisateurs. En réalité dans les vues, il ne pourra éditer que la projection ou la caisse pour les séances et il ne pourra modifier que son utilisateur (lui-même).

models/classification.rb

class Classification < ApplicationRecord
  has_and_belongs_to_many :films
  has_many :seances, :through => :films
end

les classifications liées aux films et aux séances à travers les films. Elles seront liées car la table classifications est créée en lien avec la table classifications_films.

models/disponibilite.rb

class Disponibilite < ApplicationRecord
  validates :start_time, :presence => true
  validates :end_time, :presence => true
end

Les disponibilités utilisent la gem Simple Calendar. Les méthodes :validates permettent de ne pas avoir d'erreur sql si un utilisateur oublie de rentrer un champ date à remplir dans le formulaire.

models/calendar.rb

  class Calendar < Struct.new(:view, :date, :callback)

  def table(lieu)
    content_tag :table, class: "calendar table table-bordered table-striped table-responsive table-croped" do
      header + week_rows
    end
  end

  HEADER = %w[Mercredi Jeudi Vendredi Samedi Dimanche Lundi Mardi]
  START_DAY = :wednesday

  def header
    content_tag :thead do
      HEADER.map { |day| content_tag :th, day }.join.html_safe
    end
  end

  def weeks
    first = date.beginning_of_week(START_DAY)
    last = date.end_of_week(START_DAY)
    (first..last).to_a.in_groups_of(7)
  end

  delegate :content_tag, to: :view

  def week_rows
    weeks.map do |week|
        content_tag :tr do
        week.map { |day| day_cell(day) }.join.html_safe
      end
    end.join.html_safe
  end

  def day_cell(day)
        content_tag :td, view.capture(day, &callback), class: day_classes(day)
  end

  def day_classes(day)
    classes = []
    classes << "today" if day == Date.today
    classes << "nodate" if day.month != date.month
    classes.empty? ? nil : classes.join(" ")
  end
end

Ici c'est la structure qui permet d'éditer les calendriers, premier édifice de mon application.

Controlleurs

controlleurs

controllers/application_controller.rb

class ApplicationController < ActionController::Base

  before_action :set_locale
  def set_locale
    I18n.locale = :fr
  end

before_action :require_login

  rescue_from CanCan::AccessDenied do |exception|
  redirect_to main_app.root_path, :alert => exception.message
   # Rails.logger.debug "Access denied on #{exception.action} #{exception.subject.inspect}"
  end

  def current_ability
   @current_ability ||= Ability.new(current_user)
  end

  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

 private
  # Overwrite the method sorcery calls when it
    # detects a non-authenticated request.
    def not_authenticated
      # Make sure that we reference the route from the main app.
      redirect_to main_app.log_in_path
    end

    def allow_iframe
      response.headers.delete "X-Frame-Options"
      response.headers["Access-Control-Allow-Origin"] = "*"
    end
end

On définit la locale fr pour qu'il prenne en compte les fichiers de traduction en français de certaines extensions. Les conditions pour la gem Cancancan sont là pour les permissions et en dessous pour la gem Sorcery qui sert à l'authentification, si on a pas les permissions, on est redirigé vers la page de connexion. J'ai ajouté la possibilité d'avoir un iframe pour intégrer le calendrier sur des sites, mais isolé dans une page à cet effet.

##controllers/films_controller.rb

class FilmsController < ApplicationController

  skip_before_action :require_login, only: [:index, :films_a_venir, :ecranvillage, :show, :tous_les_films]
  before_action :set_film, only: [:show, :edit, :update, :destroy]

  require 'httparty'

  # GET /ecranvillage.json
  def ecranvillage
       @films = Film.all
       response = HTTParty.get('https://www.ecranvillage.net/wp-json/ecranvillage-api/v2/export/?nocache')
       puts response.body, response.code, response.message, response.headers.inspect
       JSON.parse(response.body).each do |item|
       nouveaux_films = Film.new( :id => item["id"], :titrefilm => item["titrefilm"], :description => item["description"], :affiche => item["affiche"] )
       nouveaux_films.save
    end
  end

  # GET /films
  # GET /films.json
  def index
    @films = Film.all
    @seances = Seance.all
    @search = Film.search(params[:q])
    @films = @search.result(distinct: true).page params[:page]
  end

  # GET /films
  # GET /films.json
  def films_a_venir
    @seances = Seance.all
    @films = Film.all
  end

# GET /films
# GET /films.json
  def tous_les_films
    @films = Film.all
  end

  # GET /films/1
  # GET /films/1.json
  def show
  end

  # GET /films/new
  def new
    @film = Film.new
  end

  # GET /films/1/edit
  def edit
  end

  # POST /films
  # POST /films.json
  def create
    @film = Film.new(film_params)

    respond_to do |format|
      if @film.save
        format.html { redirect_to @film, notice: 'Le Film a bien été créé.' }
        format.json { render :show, status: :created, location: @film }
      else
        format.html { render :new }
        format.json { render json: @film.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /films/1
  # PATCH/PUT /films/1.json
  def update
    respond_to do |format|
      if @film.update(film_params)
        format.html { redirect_to @film, notice: 'Le Film a bien été mis à jour.' }
        format.json { render :show, status: :ok, location: @film }
      else
        format.html { render :edit }
        format.json { render json: @film.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /films/1
  # DELETE /films/1.json
  def destroy
    @film.destroy
    respond_to do |format|
      format.html { redirect_to films_url, notice: 'Le Film a bien été supprimé.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_film
      @film = Film.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def film_params
      params.require(:film).permit(:titrefilm, :description, :distribution, :affiche, :updated_at, classification_ids:[])
    end
end

Ici on définit les pages et les requêttes POST ou GET pour chaque page des Films. Nous avons un CRUD (Create, Read, Update, Destroy). La condition skip_before_action :require_login, only: permet de rendre public les pages choisies entre les crochets. La page index est une page de recherche qui permet de rechercher un film avec la gem ransack, le paramètre :page va permettre de ranger les résultats avec un rendu de 10 résultats et de naviguer de 10 en 10 résultats pour ne pas surcharger les requêttes sql. La page films_a_venir permet de voir les films à venir. La page ecranvillage est intéressante, c'est elle qui permet grâce à une gem (librairie) Httparty de récupérer les films en .json du site d'Ecran Village. Si on se rend sur la page ecranvillage, l'application va inspecter le lien de la page .json créée par l'api-ecranvillage et rentrer le nouveaux films qui ne sont pas encore créer dans la base de données avec leur description, leur titre et leur id.

controllers/calendar_controller.rb

  class CalendarController < ApplicationController

   skip_before_action :require_login
   before_action :get_lieu

  def calendrier
      @seances = Seance.all
      @films = Film.all
      @villages = Village.all
      @date = params[:date] ? Date.parse(params[:date]) : Date.today
      respond_to do |format|
        format.pdf do
         render :pdf => "calendrier.pdf",
                :orientation => 'Landscape',
                :layout => "layouts/pdf.html",
                :disable_javascript => false,
                show_as_html: params[:debug].present?
        end
      format.json
      format.html
    end
  end

  def aide
  end

private

    def get_lieu
      @lieu = params[:lieu]
    end

end

Ici, c'est juste la page de bienvenue avec le calendrier des séances. C'est la page root de l'application, il n'y a pas de paramètres requis pour cette page. Pour la page calendrier on appelle les paramètres @date pour les dates du calendrier. Le calendrier fait appel à un fichier models/calendar.rb qui structure le calendrier et à un helper helpers/calendar_helper.rb. Sinon on a aussi un rendu en format pdf avec la gem wicked_pdf pour cette page. Ici on fait appel à la méthode get_lieu pour avoir des pages et des méthodes par lieux.

controllers/seances_controller.rb

class SeancesController < ApplicationController

  before_action :get_lieu
  before_action :require_login
  before_action :set_seance, only: [:show, :edit, :update, :destroy]

   def index
    @seances = Seance.all
    @disponibilites = Disponibilite.all
    respond_to do |format|
        format.pdf do
        render :pdf => "index.pdf",
          :orientation => 'Landscape',
          :layout => "layouts/pdf.html",
          :disable_javascript => false,
          show_as_html: params[:debug].present?
        end
      format.html
      end
  end

  def seances_passees
    @seances = Seance.all
    respond_to do |format|
        format.pdf do
        render :pdf => "seances_passees.pdf",
          :orientation => 'Landscape',
          :layout => "layouts/pdf.html",
          :disable_javascript => false,
          show_as_html: params[:debug].present?
        end
      format.html
      end
  end

  def mes_seances
    @seances = Seance.all
    @users = User.all
    user = current_user
  end

  def a_completer
    @seances = Seance.all
    @disponibilites = Disponibilite.all
  end

  def edition_calendrier
    @seances = Seance.all
    @date = params[:date] ? Date.parse(params[:date]) : Date.today
      respond_to do |format|
      format.pdf do
        render :pdf => "edition_calendrier.pdf",
          :orientation => 'Landscape',
          :layout => "layouts/pdf.html",
          :disable_javascript => false,
          show_as_html: params[:debug].present?
        end
      format.html
      end
  end

  def entrees
    @search = SeanceSearch.new(params[:search])
    @seances = @search.scope
    @stats = EntreeFacade.new(@seances)
    @films = Film.all
    respond_to do |format|
        format.pdf do
        render :pdf => "entrees.pdf",
          :layout => "layouts/pdf.html",
          :disable_javascript => false,
          show_as_html: params[:debug].present?
        end
      format.html
      end
  end

  # GET /seances/1
  # GET /seances/1.json
  def show
  end

  # GET /seances/new
  def new
    @seance = Seance.new(params[:seance])
    @films = Film.all
    @villages = Village.all
  end

  # GET /seances/1/edit
  def edit
    @seances = Seance.all
    @villages = Village.all
  end

  # POST /seances
  # POST /seances.json
  def create
    film_selectionne = Film.find_by_id(params["seance"]["film_id"])
    village_selectionne = Village.find_by_id(params["seance"]["village_id"])
    seance = Seance.new(seance_params)
    seance.film_id = film_selectionne.id
    seance.village_id = village_selectionne.id
    seance.save
    redirect_to films_a_venir_url, notice: 'la Séance a bien été créée.'
  end


  # PATCH/PUT /seances/1
  # PATCH/PUT /seances/1.json
  def update
    respond_to do |format|
      if @seance.update(seance_params)
        format.html { redirect_to seance_path, notice: 'La séance a bien été modifiée.' }
      else
        format.html { render :edit }
        format.json { render json: @seance.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /seances/1
  # DELETE /seances/1.json
  def destroy
    @seance.destroy
    respond_to do |format|
      format.html { redirect_to films_a_venir_url, notice: 'La Séance a bien été suprimée.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_seance
      @seance = Seance.find(params[:id])
    end

    def get_lieu
      @lieu = params[:lieu]
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def seance_params
      params.require(:seance).permit(:horaire, :film_id, :village_id, :version, :projection, :commentaire, :caisse, :extras, :annulee, :billets_adultes, :billets_scolaires, :billets_enfants, :total_billets )
    end
end

Ici plusieurs pages sont créées. L'objet séances est un CRUD, la page index correspond à la liste des séances à venir, j'ai rajouté une page seances_passes pour les séances passées, une page a_completer pour les séances à compléter, une page mes_seances pour les séances où est déjà inscrit l'utilisateur connecté. Une page edition_calendrier (avec des calendriers par lieu), c'est vers elle que l'on se redirige en se connectant à l'application. La page entrees avec un calcul des entrées inscrites par film et par lieu pour la billetterie. Elle fait appel à des méthodes éditées dans un fichier entree_facade.rb créé dans un dossier facades isolé. Dans la page create on voit la méthode qui lie les tables de données avec l'association entre la colonne village_id qui obtiendra la même valeur que l'id du lieu de la séance) et entre la colonne film_id l'id du film qui auront les même valeurs. Ceci permet grâce aux associations de table et aux méthodes de Rails d'utiliser des méthodes comme :

<%= @seance.village.salle %> = le lieu de la seance
<%= @village.seances.where(statut: "seances scolaires").count %> le nombre de séances scolaire pour le village
<%= @seance.film.titrefilm %> = le titre du film de la séance
<%= @film.seances.count %> = le nombre de séances pour le film, etc...

Très pratique, c'est la magie de Rails !

controllers/villages_controller.rb

class VillagesController < ApplicationController

  skip_before_action :require_login, only: [:index, :show ]
  before_action :set_village, only: [:show, :edit, :update, :destroy]

  # GET /villages
  # GET /villages.json
  def index
    @villages = Village.all
  end

  # GET /villages/1
  # GET /villages/1.json
  def show
  end

  # GET /villages/new
  def new
    @village = Village.new
  end

  # GET /villages/1/edit
  def edit
  end

  # POST /villages
  # POST /villages.json
  def create
    @village = Village.new(village_params)

    respond_to do |format|
      if @village.save
        format.html { redirect_to @village, notice: 'Le lieu a bien été créé.' }
      else
        format.html { render :new }
      end
    end
  end

  # PATCH/PUT /villages/1
  # PATCH/PUT /villages/1.json
  def update
    respond_to do |format|
      if @village.update(village_params)
        format.html { redirect_to @village, notice: 'La modification est bien prise en compte.' }
        format.json { render :show, status: :ok, location: @village }
      else
        format.html { render :edit }
        format.json { render json: @village.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /villages/1
  # DELETE /villages/1.json
  def destroy
    @village.destroy
    respond_to do |format|
      format.html { redirect_to villages_url, notice: 'Le lieu a bien été supprimé.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_village
      @village = Village.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def village_params
      params.require(:village).permit(:commune, :salle)
    end
end

Les lieux sont issus d'un simple CRUD.

controllers/users.rb

class UsersController < ApplicationController

  skip_before_action :require_login, only: [:index, :new, :create]
  before_action :set_user, only: [:show, :edit, :update, :destroy]

  # GET /users
  # GET /users.json
  def index
    @users = User.all
    respond_to do |format|
        format.pdf do
        render :pdf => "users.pdf",
          :layout => "layouts/pdf.html",
          :disable_javascript => false,
          show_as_html: params[:debug].present?
        end
      format.html
      end
  end

  # GET /users/1
  # GET /users/1.json
  def show
  end

  # GET /users/new
  def new
    @user = User.new
  end

  # GET /users/1/edit
  def edit
  end

  # POST /users
  # POST /users.json
  def create
    @user = User.new(user_params)
    if @user.save
      login(params[:user][:email], params[:user][:password])
      redirect_to root_path, notice: 'Bienvenue !'
    else
      render 'new'
    end
  end

  # PATCH/PUT /users/1
  # PATCH/PUT /users/1.json
  def update
    respond_to do |format|
      if @user.update(user_params)
        format.html { redirect_to @user, notice: 'L\'utilisateur a bien été modifié.' }
        format.json { render :show, status: :ok, location: @user }
      else
        format.html { render :edit }
        format.json { render json: @user.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /users/1
  # DELETE /users/1.json
  def destroy
    @user.destroy
    respond_to do |format|
      format.html { redirect_to users_url, notice: 'L\'utilisateur a bien été supprimé.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_user
      @user = User.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def user_params
      params.require(:user).permit(:email, :password, :password_confirmation, :nom, :prenom, :telephone, :role)
    end
end

Les utilisateurs, au début le fichier est créer avec la gem Sorcery.

controllers/users_sessions_controller.rb

class UserSessionsController < ApplicationController

    skip_before_action :require_login, except: [:destroy]

    def new
        @user = User.new
    end

    def create
      if @user = login(params[:email], params[:password])
        if @user.role == "guest"
          redirect_back_or_to(root_path, notice: 'Connexion réussie !')
        else
          redirect_back_or_to(edition_calendrier_path(:lieu => "tous les lieux"), notice: 'Connexion réussie !')
        end
      else
       flash.now[:error] = "Oups! une erreur semble-t-il... veuillez recommencez s'il vous plaît."
       render action: 'new'
      end
  end

    def destroy
      logout
      redirect_back_or_to(root_path, notice: 'Déconnecté !')
    end
end

Le controlleur de sessions, toujours liés à la gem Sorcery, ici on peut noter une condition, si on vient de s'insrire, on est redirigé vers la page de départ correspondant à la page public calendrier qui n'est pas une page d'édition, sinon on sera redirigé vers la page edition_calendrier.

controllers/classifications_controller.rb

class ClassificationsController < ApplicationController

  before_action :set_classification, only: [:show, :edit, :update, :destroy]

  # GET /classifications
  # GET /classifications.json
  def index
    @classifications = Classification.all
    @films = Film.all
    @seances = Seance.all
  end

  # GET /classifications/1
  # GET /classifications/1.json
  def show
  end

  # GET /classifications/new
  def new
    @classification = Classification.new
  end

  # GET /classifications/1/edit
  def edit
  end

  # POST /classifications
  # POST /classifications.json
  def create
    @classification = Classification.new(classification_params)

    respond_to do |format|
      if @classification.save
        format.html { redirect_to @classification, notice: 'la classification a bien été créée.' }
        format.json { render :show, status: :created, location: @classification }
      else
        format.html { render :new }
        format.json { render json: @classification.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /classifications/1
  # PATCH/PUT /classifications/1.json
  def update
    respond_to do |format|
      if @classification.update(classification_params)
        format.html { redirect_to @classification, notice: 'la classification a bien été mise à jour.' }
        format.json { render :show, status: :ok, location: @classification }
      else
        format.html { render :edit }
        format.json { render json: @classification.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /classifications/1
  # DELETE /classifications/1.json
  def destroy
    @classification.destroy
    respond_to do |format|
      format.html { redirect_to classifications_url, notice: 'la classification a été supprimée.' }
      format.json { head :no_content }
    end
  end

  private

    # Use callbacks to share common setup or constraints between actions.
    def set_classification
      @classification = Classification.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def classification_params
      params.require(:classification).permit(:nom_classification, :film_id)
    end
end

Classifications sert à classer les films par label (Art et Essai, Patrimoine et Répertoire, Recherche et Découvertes, Jeune Public), utile pour les envois au CNC.

controllers/disponibilites_contoller.rb

class DisponibilitesController < ApplicationController
  before_action :set_disponibilite, only: [:show, :edit, :update, :destroy]

  # GET /disponibilites
  # GET /disponibilites.json
  def index
    @disponibilites = Disponibilite.all
    @date = params[:date] ? Date.parse(params[:date]) : Date.today
  end

  # GET /disponibilites/1
  # GET /disponibilites/1.json
  def show
  end

  # GET /disponibilites/new
  def new
    @disponibilite = Disponibilite.new
  end

  # GET /disponibilites/1/edit
  def edit
  end

  # POST /disponibilites
  # POST /disponibilites.json
  def create
    @disponibilite = Disponibilite.new(disponibilite_params)

    respond_to do |format|
      if @disponibilite.save
        format.html { redirect_to @disponibilite, notice: 'Disponibilité créée avec succés.' }
        format.json { render :show, status: :created, location: @disponibilite }
      else
        format.html { render :new }
        format.json { render json: @disponibilite.errors, status: :unprocessable_entity }
      end
    end
  end

  # PATCH/PUT /disponibilites/1
  # PATCH/PUT /disponibilites/1.json
  def update
    respond_to do |format|
      if @disponibilite.update(disponibilite_params)
        format.html { redirect_to @disponibilite, notice: 'Disponibilité bien mise à jour.' }
        format.json { render :show, status: :ok, location: @disponibilite }
      else
        format.html { render :edit }
        format.json { render json: @disponibilite.errors, status: :unprocessable_entity }
      end
    end
  end

  # DELETE /disponibilites/1
  # DELETE /disponibilites/1.json
  def destroy
    @disponibilite.destroy
    respond_to do |format|
      format.html { redirect_to disponibilites_url, notice: 'Disponibilité bien supprimée.' }
      format.json { head :no_content }
    end
  end

  private
    # Use callbacks to share common setup or constraints between actions.
    def set_disponibilite
      @disponibilite = Disponibilite.find(params[:id])
    end

    # Never trust parameters from the scary internet, only allow the white list through.
    def disponibilite_params
      params.require(:disponibilite).permit(:nom, :start_time, :end_time)
    end
end

Disponibilites, sert aux bénévoles et salariés à marquer leurs indisponnibilitées dans un calendrier qui utilise la gem Simple Calendar, cet objet a été crééer avec cette gem.

controllers/iframe_controller.rb

class IframeController < ApplicationController
   layout "layouts/iframe"
   skip_before_action :require_login
   before_action :get_lieu
   after_action :allow_iframe, only: :calendrier_iframe

  def calendrier_iframe
      @seances = Seance.all
      @films = Film.all
      @villages = Village.all
      @date = params[:date] ? Date.parse(params[:date]) : Date.today
  end

  private

    def get_lieu
      @lieu = params[:lieu]
    end

end

Ici ce controlleur me sert juste à éditer une page qui sert de iframe pour avoir une vue de calendrier sur d'autre sites avec un bout de code html.