简体   繁体   中英

Rails 5.2 Controller for Where Array Doesn't Include

I have a model called hardships with an array value for the users that have voted to approve that hardship ( :approvals => [] in the hardship_params ).

I'm trying to create a list of hardships on the index page for hardships which any given current_user (using devise) has NOT approved.

The array is formed with user ids, so a typical value for @hardship.approvals would be ["2", "3"] if the second and third users had approved it.

I'm having trouble with the logic to call this from the controller. I've looked at SO posts like this but can't glean an answer that works for Rails 5.2 and Postgres.

I'm looking for something like this:

@need_approval = Hardship.where.not(approvals.include?(current_user.id.to_s))

However, I know that doesn't work because approvals isn't a variable and the syntax is all wrong. Can anyone help me Rails-ify this so it gets the info I need?

ADDITIONAL INFORMATION

Here's my hardships table:

  create_table "hardships", force: :cascade do |t|
    t.string "full_name"
    t.date "date"
    t.string "position"
    t.string "branch"
    t.string "email_non_toca"
    t.string "mobile"
    t.string "address"
    t.string "city"
    t.string "state"
    t.string "zip"
    t.string "bank_name"
    t.string "bank_phone"
    t.string "bank_address"
    t.date "start_date"
    t.boolean "accident", default: false
    t.boolean "catastrophe", default: false
    t.boolean "counseling", default: false
    t.boolean "family_emergency", default: false
    t.boolean "health", default: false
    t.boolean "memorial", default: false
    t.boolean "other_hardship", default: false
    t.string "other_hardship_description"
    t.decimal "requested_amount"
    t.text "hardship_description"
    t.decimal "self_fund"
    t.string "intent_signature"
    t.date "intent_signature_date"
    t.string "release_signature"
    t.date "release_signature_date"
    t.string "status", default: "Application Started"
    t.string "final_decision", default: "Not Decided"
    t.bigint "user_id"
    t.datetime "created_at", null: false
    t.datetime "updated_at", null: false
    t.boolean "returned", default: false
    t.text "approvals", default: [], array: true
    t.text "rejections", default: [], array: true
    t.index ["user_id"], name: "index_hardships_on_user_id"
  end

If I were you, I think I would be tempted to create a ApplicationAction model. Something like:

# == Schema Information
#
# Table name: application_actions
#
#  id               :bigint           not null, primary key
#  user_id          :integer
#  application_type :string
#  application_id   :integer
#  action           :integer
#  created_at       :datetime         not null
#  updated_at       :datetime         not null
#

class ApplicationAction < ApplicationRecord
  belongs_to :user 
  belongs_to :application, polymorphic: true

  enum action: {
    approved:             0,
    rejected:             1,
    requested_to_modify:  2
  }  
end

Then, in your User model, do something like:

# == Schema Information
#
# Table name: users
#
#  id         :bigint           not null, primary key
#  created_at :datetime         not null
#  updated_at :datetime         not null
#

class User < ApplicationRecord
  has_many :application_actions

  %i(
    approved
    rejected
    requested_to_modify
  ).each do |action_sym|
    %w(
      hardship
      scholarship
      charity
    ).each do |application_type|

      # Create the associations: 
      #   - hardship_applications
      #   - scholarship_applications
      #   - charity_applications
      has_many "#{application_type}_applications".to_sym,
        -> { distinct },
        through:      :application_actions,
        source:       :application,
        source_type:  application_type.camelize

      # Define the methods:
      #   - approved_application_actions
      #   - rejected_application_actions
      #   - requested_to_modify_application_actions
      define_method("#{action_sym}_application_actions") do 
        application_actions.send(action_sym)
      end

      # Define the methods:
      #   - approved_hardship_applications
      #   - rejected_hardship_applications
      #   - requested_to_modify_hardship_applications
      #   - approved_scholarship_applications
      #   - rejected_scholarship_applications
      #   - requested_to_modify_scholarship_applications
      #   - approved_charity_applications
      #   - rejected_charity_applications
      #   - requested_to_modify_charity_applications
      define_method("#{action_sym}_#{application_type}_applications") do 
        send("#{application_type}_applications").
          joins(:application_actions).
          where(
            application_actions: {
              action: ApplicationAction.actions[action_sym]
            }
          )
      end

      # Define the methods:
      #   - hardship_applications_not_approved
      #   - hardship_applications_not_rejected
      #   - hardship_applications_not_requested_to_modify
      #   - scholarship_applications_not_approved
      #   - scholarship_applications_not_rejected
      #   - scholarship_applications_not_requested_to_modify
      #   - charity_applications_not_approved
      #   - charity_applications_not_rejected
      #   - charity_applications_not_requested_to_modify
      define_method("#{application_type}_applications_not_#{action_sym}") do 
        application_type.
          camelize.
          constantize.
          where.
          not(id: send("#{action_sym}_#{application_type}_applications"))
      end

    end
  end

end

And, in your Hardship model, do something like:

# == Schema Information
#
# Table name: hardships
#
#  id               :bigint           not null, primary key
#  application_type :integer          default(NULL)
#  created_at       :datetime         not null
#  updated_at       :datetime         not null
#

class Hardship < ApplicationRecord
  has_many :application_actions, as: :application

  enum application_type: {
    accident:           0,
    catastrophe:        1,
    counseling:         2,
    family_emergency:   3,
    health:             4,
    memorial:           5,
    other_hardship:     6
  }
end

Then, if I run that through a quick RSpec test:

require 'rails_helper'

RSpec.describe 'Hardship Applications' do
  before(:each) do 
    @user_1               = User.create!
    @user_2               = User.create!
    @hardship_1           = Hardship.create!
    @user_1.
      application_actions.
      create!(application: @hardship_1).
      approved!
    @user_2.
      application_actions.
      create!(application: @hardship_1).
      rejected!
  end
  it "user_1 approved_hardship_applications to include hardship_1" do 
    expect(@user_1.approved_hardship_applications).to include(@hardship_1)
  end
  it "user_1 hardship_applications_not_approved NOT to include hardship_1" do 
    expect(@user_1.hardship_applications_not_approved).not_to include(@hardship_1)
  end
  it "user_1 rejected_hardship_applications NOT to include hardship_1" do 
    expect(@user_1.rejected_hardship_applications).not_to include(@hardship_1)
  end
  it "user_2 approved_hardship_applications NOT to include hardship_1" do 
    expect(@user_2.approved_hardship_applications).not_to include(@hardship_1)
  end
  it "user_2 hardship_applications_not_approved to include hardship_1" do
    expect(@user_2.hardship_applications_not_approved).to include(@hardship_1)
  end
  it "user_2 rejected_hardship_applications to include hardship_1" do 
    expect(@user_2.rejected_hardship_applications).to include(@hardship_1)
  end
end

I get...

Hardship Applications
  user_1 approved_hardship_applications to include hardship_1
  user_1 hardship_applications_not_approved NOT to include hardship_1
  user_1 rejected_hardship_applications NOT to include hardship_1
  user_2 approved_hardship_applications NOT to include hardship_1
  user_2 hardship_applications_not_approved to include hardship_1
  user_2 rejected_hardship_applications to include hardship_1

Finished in 0.13431 seconds (files took 0.90021 seconds to load)
6 examples, 0 failures

So, you can do:

@need_approval = current_user.hardship_applications_not_approved

instead of:

@need_approval = Hardship.where.not(approvals.include?(current_user.id.to_s))

Also you'll note that in the Hardship model, I made all your application types into an enum . This will cut down on empty fields (assuming a Hardship application can be of only one application type).

There appear to be other opportunities for reducing redundancy. For instance, you could create PhysicalAddress , PhoneNumber , and EmailAddress models and associate these with each type of application. You could also create your application status to an enum and default it to application_started . Same with final_decision .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM