ruby-on-railsdatabase-designactiveadminformtastic

How to design a database for police fines?


Using Rails 6 I am designing an application to manage police fines. A user can violate many articles, an article can have many letters and a letter can have many commas.

This is my implementation:

#models/fine.rb
class Fine < ApplicationRecord
  has_many :violations
  has_many :articles, through: :violations
  has_many :letters, through: :violations
  has_many :commas, through: :violations
end
#models/article.rb
class Article < ApplicationRecord
  has_many :letters
  has_many :violations
  has_many :fines, through: :violations
end
#models/letter.rb
class Letter < ApplicationRecord
  belongs_to :article
  has_many :commas

  has_many :violations
  has_many :fines, through: :violations

end
#models/comma.rb
class Comma < ApplicationRecord
  belongs_to :letter

  has_many :violations
  has_many :fines, through: :violations
end
#models/violation.rb
class Violation < ApplicationRecord
  belongs_to :fine

  belongs_to :article
  belongs_to :letter, optional: true
  belongs_to :comma, optional: true
end

When I print the fine in PDF I need to show violations: articles, letters and commas. I have difficulty creating a form to compile the fine because it is too deep. I am using Active Admin, when I create a new fine I want to associate many violations.

Violation example:

Violation.new
 => #<Violation id: nil, article_id: nil, fine_id: nil, letter_id: nil, comma_id: nil, note: nil, created_at: nil, updated_at: nil> 

How can I create a form (using Active Admin, which uses Formtastic) to associate many violations to a fine? Example form:

enter image description here

Example (with sample data):

Violation.new fine: fine, article: a, letter: a.letters.last, comma: a.letters.second.commas.last
 => #<Violation id: nil, article_id: 124, fine_id: 66, letter_id: 10, comma_id: 4, note: nil, created_at: nil, updated_at: nil> 

Solution

  • Solved:

      f.has_many :violations do |vf|
        vf.input :article, as: :select, include_blank: false, collection: options_for_select(Article.all.map {|article| [article.number, article.id, { :'data-article-id' => article.id, :'data-letters' => article.letters.map(&:id).to_json }]})
        vf.input :letter, as: :select, collection: options_for_select(Letter.all.map {|letter| [letter.letter, letter.id, { :'hidden' => true, :'data-letter-id' => letter.id, :'data-article-id' => letter.article.id, :'data-commas' => letter.commas.map(&:id).to_json }]})
        vf.input :comma, as: :select, collection: options_for_select(Comma.all.map {|comma| [comma.number, comma.id, { :'hidden' => true, :'data-comma-id' => comma.id, :'data-letter-id' => comma.letter.id }]})
      end
    
    

    And with a bit of javascript:

    $(document).on('has_many_add:after', '.has_many_container', function (e, fieldset, container) {
        selects = fieldset.find('select');
        article_select = selects[0];
        letter_select = selects[1];
        comma_select = selects[2];
    
        $(article_select).on("change", function () {
            $(letter_select).prop('selectedIndex', 0);
            $(comma_select).prop('selectedIndex', 0);
            $("#" + letter_select.id + " option").prop("hidden", true);
            $("#" + comma_select.id + " option").prop("hidden", true);
    
            letters = $(this).find(':selected').data('letters');
    
            $.each(letters, function (index, id) {
                $("#" + letter_select.id + " option[data-letter-id='" + id + "']").removeAttr("hidden");
            });
        });
    
        $(letter_select).on("change", function () {
            $(comma_select).prop('selectedIndex', 0);
            $("#" + comma_select.id + " option").prop("hidden", true);
    
            commas = $(this).find(':selected').data('commas');
    
            $.each(commas, function (index, id) {
                $("#" + comma_select.id + " option[data-comma-id='" + id + "']").removeAttr("hidden");
    
            });
        });
    });
    

    I show all Articles, Letters and Commas in the selectbox. Initially Commas and Letters are hidden, then when a User click an Article the Letter's selectbox show only the related Letters. The Commas code works same as Letters. After I can add some validations in the Violation model.