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

Single File Rails Test App #14

Open
nathancolgate opened this issue Nov 7, 2024 · 2 comments
Open

Single File Rails Test App #14

nathancolgate opened this issue Nov 7, 2024 · 2 comments
Assignees

Comments

@nathancolgate
Copy link
Member

While working on #12 I was able to (easily) test the changes in an existing app.

However, it would be nice if it was super easy to work directly on the repo instead of copy-n-pasting back into this repo.

@willtcarey suggested a Single File Rails App, and that sounds cool!

@nathancolgate
Copy link
Member Author

I tested mine using a fake model that didn't require a database connection:

class ArrayType < ActiveModel::Type::Value
  def cast(value)
    # Ensure it's always an array
    Array(value.reject!(&:empty?))
  end
end

class FakeModel
  include ActiveModel::Model
  include ActiveModel::Attributes
  include ActiveRecord::Reflection::ClassMethods

  # Define attributes with various data types
  attribute :name, :string           # String attribute
  attribute :email, :string           # String attribute
  attribute :website_url, :string           # String attribute
  attribute :telephone_number, :string           # String attribute
  attribute :search_term, :string           # String attribute
  attribute :home_on_the_range, :string           # String attribute
  attribute :views_count, :integer   # Integer attribute
  attribute :description, :string      # Text attribute
  attribute :week_what_now, :string      # Text attribute
  attribute :month_what_now, :string      # Text attribute
  attribute :grouped_color, :string      # Text attribute
  attribute :published_on, :date # DateTime attribute
  attribute :published_at, :datetime # DateTime attribute
  attribute :local_timestamp, :datetime # DateTime attribute
  attribute :password_confirmation, :string # DateTime attribute
  attribute :hex_color, :string # DateTime attribute
  attribute :current_time, :time
  attribute :is_fast, :boolean     # Boolean attribute
  attribute :is_flashy, :boolean     # Boolean attribute
  attribute :color, :string      # Text attribute
  attribute :shape, :string
  attribute :colors, ArrayType.new
  attribute :sizes, ArrayType.new
  attribute :multiple_shape, ArrayType.new
  attribute :file, :string  # String attribute for file (normally you'd use something like ActiveStorage for real files)

  # Simulate associations
  ASSOCIATIONS = {
    comments: {type: :has_many, class_name: "Comment"},
    author: {type: :belongs_to, class_name: "User"}
  }.freeze

  # Custom reflection class to simulate ActiveRecord::Reflection::AssociationReflection
  class AssociationReflection
    attr_reader :name, :macro, :class_name

    def initialize(name, macro, class_name)
      @name = name
      @macro = macro
      @class_name = class_name
    end
  end

  # Method to simulate ActiveRecord's reflect_on_association
  def self.reflect_on_association(association_name)
    association = ASSOCIATIONS[association_name]
    return nil unless association

    AssociationReflection.new(association_name, association[:type], association[:class_name])
  end

  # You can add validations if needed
  validates :name, presence: true
  validates :week_what_now, presence: true
  validates :month_what_now, presence: true
  validates :description, presence: true
  validates :views_count, numericality: {only_integer: true}
  validates :website_url, presence: true
  validates :telephone_number, presence: true
  validates :home_on_the_range, inclusion: {in: ["25"], message: "must be '25'"}
  validates :email, presence: true
  validates :is_fast, inclusion: {in: [true], message: "must be 'Fast'"}
  validates :is_flashy, inclusion: {in: [true], message: "must be 'Flashy'"}
  validates :shape, inclusion: {in: ["triangle"], message: "must be 'Triangle'"}
  validates :grouped_color, inclusion: {in: ["Blue"], message: "must be 'Blue'"}
  validates :color, inclusion: {in: ["blue"], message: "must be 'Blue'"}
  validates :file, presence: true
  validates :password_confirmation, presence: true
  validates :search_term, presence: true
  validates :local_timestamp, presence: true
  validates :hex_color, inclusion: {in: ["#ff0000"], message: "must be '#ff0000'"}
  validate :must_include_blue
  validate :must_include_large
  validate :must_include_triangle
  validate :date_stuff_is_always_invalid

  private

  def date_stuff_is_always_invalid
    errors.add(:published_on, "is invalid")
    errors.add(:published_at, "is invalid")
    errors.add(:current_time, "is invalid")
  end

  def must_include_blue
    errors.add(:colors, "must include 'Blue'") unless colors.include?("blue")
  end

  def must_include_large
    errors.add(:sizes, "must include 'Large'") unless sizes.include?("large")
  end

  def must_include_triangle
    errors.add(:multiple_shape, "must include 'Triangle'") unless multiple_shape.include?("triangle")
  end
end

This in my routes file:

resources :fake_models

A controller. Note the ability to change the options so that I was able to see what the form looked like with different sizes/colors/labels/hints etc. The one thing I forgot to do was include some kind of option to disable the inputs. It would be good to know what that looks like, and make sure it passes all the way through:

class FakeModelsController < ApplicationController
  before_action :set_options

  def new
    @fake_model = FakeModel.new
  end

  def create
    @fake_model = FakeModel.new(fake_model_params)
    if @fake_model.valid?
      redirect_to root_path
    else
      render :new, status: :unprocessable_entity
    end
  end

  private

  def set_options
    @color = "accent"
    @size = "lg"
    @hint = (false) ? "This is a hint!" : nil
    @label = (true) ? nil : false
  end

  def fake_model_params
    params.fetch(:fake_model, {}).permit(
      :name,
      :description,
      :is_fast, :is_furious,
      :is_flashy, :is_fun,
      :color,
      :shape,
      :email,
      :website_url,
      :telephone_number,
      :hex_color,
      :password_confirmation,
      :search_term,
      :week_what_now,
      :month_what_now,
      :local_timestamp,
      :home_on_the_range,
      :grouped_color,
      :views_count,
      colors: [],
      sizes: [],
      multiple_shape: []
      # We don't test this because it's hard to get a non-rails model
      # to quack like a date/time object and accept the (1i) style params
      # :current_time,
      # :published_on,
      # :published_at,
    )
  end
end

And this view:

.container
  %h1.text-4xl.font-black.tracking-tighter
    DaisyUI Test Form
  = form_with model: @fake_model, html: {novalidate: true} do |f|
    %h3.text-2xl.font-black.tracking-tighter.my-6.text-base-300 Checkbox
    .my-3= f.input :is_fast, as: :boolean, label: @label, hint: @hint, input_html: {class: "checkbox-#{@color} checkbox-#{@size}"}

    %h3.text-2xl.font-black.tracking-tighter.my-6.text-base-300 Checkboxes
    .my-3= f.input :colors, as: :check_boxes, label: @label, hint: @hint, collection: ["Red","Green","Blue"], value_method: :downcase, text_method: :to_s, input_html: {class: "checkbox-#{@color} checkbox-#{@size}"}

    %h3.text-2xl.font-black.tracking-tighter.my-6.text-base-300 File Input
    .my-3= f.input :file, as: :file, label: @label, hint: @hint, input_html: {class: "file-input-#{@color} file-input-#{@size}"}

    %h3.text-2xl.font-black.tracking-tighter.my-6.text-base-300 Radio
    .my-3= f.input :color, as: :radio_buttons, label: @label, hint: @hint, collection: ["Red","Green","Blue"], value_method: :downcase, text_method: :to_s, input_html: {class: "radio-#{@color}"}

    %h3.text-2xl.font-black.tracking-tighter.my-6.text-base-300 Range
    .my-3= f.input :home_on_the_range, as: :range, label: @label, hint: @hint, input_html: {class: "range-#{@color} range-#{@size}", min: 0, max: 100, step: 5}, measure: 4
    -# %h3.text-2xl.font-black.tracking-tighter.my-6.text-base-300 Rating
    -# %p Not going to implement
    %h3.text-2xl.font-black.tracking-tighter.my-6.text-base-300 Select
    .my-3= f.input :shape, as: :select, label: @label, hint: @hint, collection: ["Circle","Square","Triangle"], value_method: :downcase, text_method: :to_s, input_html: {class: "select-#{@color} select-#{@size}"}
    .my-3= f.input :multiple_shape, as: :select, label: @label, hint: @hint, collection: ["Circle","Square","Triangle"], multiple: true, value_method: :downcase, text_method: :to_s, input_html: {class: "select-#{@color} select-#{@size}"}
    .my-3= f.input :grouped_color, as: :grouped_select, label: @label, hint: @hint, collection: { Primary: ["Red","Yellow","Blue"], Secondary: ["Green","Purple","Orange"]}, value_method: :downcase, text_method: :to_s, input_html: {class: "select-#{@color} select-#{@size}"}

    %h3.text-2xl.font-black.tracking-tighter.my-6.text-base-300 Text Input
    .my-3= f.input :name, label: @label, hint: @hint, input_html: {class: "input-#{@color} input-#{@size}"}
    .my-3= f.input :email, as: :email, label: @label, hint: @hint, input_html: {class: "input-#{@color} input-#{@size}"}
    .my-3= f.input :views_count, as: :integer, label: @label, hint: @hint, input_html: {class: "input-#{@color} input-#{@size}"}
    .my-3= f.input :website_url, as: :url, label: @label, hint: @hint, input_html: {class: "input-#{@color} input-#{@size}"}
    .my-3= f.input :telephone_number, as: :telephone, label: @label, hint: @hint, input_html: {class: "input-#{@color} input-#{@size}"}
    .my-3= f.input :password_confirmation, as: :password, label: @label, hint: @hint, input_html: {class: "input-#{@color} input-#{@size}"}
    .my-3= f.input :search_term, as: :search, label: @label, hint: @hint, input_html: {class: "input-#{@color} input-#{@size}"}

    %h3.text-2xl.font-black.tracking-tighter.my-6.text-base-300 Color
    .my-3= f.input :hex_color, as: :color, label: @label, hint: @hint, input_html: {class: "input-#{@color} input-#{@size}"}


    %h3.text-2xl.font-black.tracking-tighter.my-6.text-base-300 Date/Time Inputs
    .my-3= f.input :published_on, as: :date, label: @label, hint: @hint, input_html: {class: "select-#{@color} select-#{@size}"}
    .my-3= f.input :published_at, as: :datetime, label: @label, hint: @hint, input_html: {class: "select-#{@color} select-#{@size}"}
    .my-3= f.input :current_time, as: :time, label: @label, hint: @hint, input_html: {class: "select-#{@color} select-#{@size}"}
    .my-3= f.input :week_what_now, as: :week, label: @label, hint: @hint, input_html: {class: "select-#{@color} select-#{@size}"}
    .my-3= f.input :month_what_now, as: :month, label: @label, hint: @hint, input_html: {class: "select-#{@color} select-#{@size}"}
    .my-3= f.input :local_timestamp, as: :datetime_local, label: @label, hint: @hint, input_html: {class: "input-#{@color} input-#{@size}"}

    %h3.text-2xl.font-black.tracking-tighter.my-6.text-base-300 Textarea
    .my-3= f.input :description, as: :text, label: @label, hint: @hint, input_html: {class: "textarea-#{@color} textarea-#{@size}"}

    %h3.text-2xl.font-black.tracking-tighter.my-6.text-base-300 Toggle
    .my-3= f.input :is_flashy, as: :toggle, label: @label, hint: @hint, input_html: {class: "toggle-#{@color} toggle-#{@size}"}

    %h3.text-2xl.font-black.tracking-tighter.my-6.text-base-300 Toggles
    .my-3= f.input :sizes, as: :toggles, label: @label, hint: @hint, collection: ["Small","Medium","Large"], value_method: :downcase, text_method: :to_s, input_html: {class: "toggle-#{@color} toggle-#{@size}"}

    .my-3= f.submit class: "btn btn-primary"

    %div
      .file-input-accent.file-input-info.file-input-lg.file-input-sm.file-input-xs
      .select-accent.select-info.select-lg.select-sm.select-xs
      .checkbox-accent.checkbox-info.checkbox-lg.checkbox-sm.checkbox-xs
      .radio-accent.radio-info.radio-lg.radio-sm.radio-xs
      .input-accent.input-info.input-lg.input-sm.input-xs
      .textarea-accent.textarea-info.textarea-lg.textarea-sm.textarea-xs
      .toggle-accent.toggle-info.toggle-lg.toggle-sm.toggle-xs
      .range.range-accent.range-info.range-lg.range-sm.range-xs

Note the pile of CSS classes I threw in the last div there so that TailwindCSS watch would pick them up and include them in the stylesheet.

@willtcarey willtcarey self-assigned this Nov 7, 2024
@willtcarey
Copy link
Member

Starting a very basic Single file rails app. I'm excited because the in memory sqlite database means we can do a real record/model setup here.

# frozen_string_literal: true
require 'bundler/inline'

gemfile(true) do
  source 'https://rubygems.org'

  gem 'rails', '~> 8.0'
  gem 'sqlite3'
  gem "rackup"
  gem "puma"
end

require 'rails/all'
database = ':memory:'

ENV['DATABASE_URL'] = "sqlite3:#{database}"
ActiveRecord::Base.establish_connection(adapter: 'sqlite3', database: database)
ActiveRecord::Base.logger = Logger.new(STDOUT)
ActiveRecord::Schema.define do
  create_table :posts, force: true do |t|
  end

  create_table :comments, force: true do |t|
    t.integer :post_id
  end
end

class App < Rails::Application
  config.root = __dir__
  config.consider_all_requests_local = true
  config.secret_key_base = 'i_am_a_secret'
  config.active_storage.service_configurations = { 'local' => { 'service' => 'Disk', 'root' => './storage' } }

  routes.append do
    root to: 'welcome#index'
  end
end

class WelcomeController < ActionController::Base
  def index
    render inline: 'Hi!'
  end
end

App.initialize!

Rackup::Server.start(app: App, Port: 3000)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants