我正在尝试构建Rails中的“贷款”模型。有一个相应的“付款”模式。贷款余额是贷款的原始金额减去所有付款的总和。计算余额很容易,但我试图在避免N + 1查询的同时计算大量贷款的余额,同时使“余额”成为“贷款”模型的属性。
当我调用Loans控制器的索引方法时,我可以运行自定义选择查询,这允许我通过直接SQL查询返回“balance”属性。
class LoansController < ApplicationController
def index
@loans = Loan
.joins("LEFT JOIN payments on payments.loan_id = loan.id")
.group("loans.id")
.select("loans.*, loans.amount - SUM(payments.amount) as balance")
end
def index_002
@loans = Loan.includes(:payments)
end
def index_003
@loans = Loan.includes(:payments)
end
end
class Loan < ActiveRecord::Base
has_many :payments
def balance=(value)
# I'd like balance to load automatically in the Loan model.
raise NotImplementedError.new("Balance of a loan cannot be set directly.")
end
def balance_002
# No N+1 query, but iterating through each payment in Ruby
# is grossly inefficient as well
amount - payments.map(:amount).inject(0, :+)
end
def balance_003
# Even with the "includes" in the controller, this is N+1
amount - (payments.sum(:amount) || 0)
end
end
现在我的问题是如何使用我的贷款模型一直这样做。通常,ActiveRecord使用以下查询加载一个或多个模型:
SELECT * FROM loans
--where clause optional
WHERE id IN (?)
有没有办法覆盖Loan模型,以便加载以下查询:
SELECT
loans.*, loans.amount - SUM(payments.amount) as balance
FROM
loans
LEFT JOIN
payments ON payments.loan_id = loans.id
GROUP BY
loans.id
这种方式“平衡”是模型的属性,只需要在一个地方声明,但我们也避免了N + 1查询的低效率。
答案 0 :(得分:1)
我喜欢使用数据库视图,因此rails认为它正在与常规数据库表进行通信(确保正常加载工作正常),而事实上正在进行聚合或复杂连接。在您的情况下,我可能会定义第二个#NoTrayIcon
#include <MsgBoxConstants.au3>
#include <IE.au3>
#include <String.au3>
#include <INet.au3>
#include <GUIConstantsEx.au3>
#Include <GuiEdit.au3>
#include <MsgBoxConstants.au3>
Global $o = 'opparis'
Global $n = 'TweetHollande'
Global $op_sr = 'optools.anonops.com'
Global $showie = True
$ans = MsgBox(4,"Twitter Reporter (#OpParis) - AO Tools (v.1.0.1)","Would you like to show Internet Explorer during operation? (Recommended: Yes)")
Select
Case $ans = 6
$showie = True
Case $ans = 7
$showie = False
EndSelect
$gui = GUICreate("Twitter Reporter (#OpParis) - AO Tools (v.1.0.1)", 310, 340, 200, 150)
Global $editctrl = GUICtrlCreateEdit("", 10, 10, 300, 330)
GUISetState(@SW_SHOW)
Opt("GUIOnEventMode", 1)
GUISetOnEvent($GUI_EVENT_CLOSE, "CLOSEButton")
Func CLOSEButton()
_IEQuit($i)
Exit
EndFunc
loggin("Twitter Reporter (#OpParis) - AO Tools (v.1.0.0)"&@CRLF&@CRLF)
loggin("Starting up! Close this window at anytime to stop."&@CRLF&@CRLF)
Func updateTarget($t)
$tg = _INetGetSource("http://"&$op_sr&"/twUpdateTarget.php?o="&$o&"&t="&$t)
If $tg == "" OR $tg == "0" OR StringInStr($tg,"Website is offline") OR StringInStr($tg,"Checking your browser") Then
If $showie == True Then
$zz = _IECreate("http://"&$op_sr&"/twUpdateTarget.php?o="&$o&"&t="&$t)
Else
$zz = _IECreate ("http://"&$op_sr&"/twUpdateTarget.php?o="&$o&"&t="&$t,0,0)
EndIf
_IELoadWait($zz)
$tg = _IEBodyReadText($zz)
If StringInStr($tg,"Checking your browser") Then
sleep(6000)
$tg = _IEBodyReadText($zz)
EndIf
_IEQuit($zz)
EndIf
If $tg == "Target removed!" Then
loggin("~ Target Removed: "&$t&" ~"&@CRLF)
EndIf
EndFunc
Func loggin($message)
ConsoleWrite($message)
_GUICtrlEdit_AppendText($editctrl,$message)
EndFunc
If $showie == True Then
Global $i = _IECreate ("https://twitter.com/login")
Else
Global $i = _IECreate ("https://twitter.com/login",0,0)
EndIf
_IELoadWait($i)
$url = _IEPropertyGet($i,"locationurl")
If $url == "https://twitter.com/" Then
loggin("****"&@CRLF&"WARNING: Already logged in to Twitter in IE!"&@CRLF&@CRLF&"Dont want to use the logged-in account?"&@CRLF&"Then Please:"&@CRLF&"1.) Close this application"&@CRLF&"2.) Open Internet Explorer"&@CRLF&"3.) Logout of Twitter"&@CRLF&"4.) Restart this Application"&@CRLF&@CRLF&"***"&@CRLF)
loggin(@CRLF&"Sleeping 30 seconds for a chance for an action..."&@CRLF&@CRLF)
Sleep(30000)
Else
Local $lUser = InputBox("Login", "Enter Twitter Username", "")
$n = $lUser
Local $lPass = InputBox("Login", "Enter Twitter Password.", "", "*")
$f = _IEFormGetCollection($i, 2)
$u = _IEFormElementGetCollection ($f, 1)
$p = _IEFormElementGetCollection ($f, 2)
_IEFormElementSetValue ($u, $lUser)
_IEFormElementSetValue ($p, $lPass)
_IEFormSubmit ($f)
_IELoadWait($i)
sleep(1000)
EndIf
$url = _IEPropertyGet($i,"locationurl")
If $url <> "https://twitter.com/" Then
MsgBox($MB_SYSTEMMODAL, "Login", "Twitter Login Invalid")
_IEQuit($i)
Exit
EndIf
While 1
$tg = _INetGetSource("http://"&$op_sr&"/twGetTarget.php?o="&$o&"&n="&$n)
If $tg == "" OR $tg == "0" OR StringInStr($tg,"Website is offline") OR StringInStr($tg,"Error") OR StringInStr($tg,"Checking your browser") OR StringInStr($tg,"Cloudflare") Then
If $showie == True Then
$iz = _IECreate("http://"&$op_sr&"/twGetTarget.php?o="&$o&"&n="&$n)
Else
$iz = _IECreate ("http://"&$op_sr&"/twGetTarget.php?o="&$o&"&n="&$n,0,0)
EndIf
_IELoadWait($iz)
$tg = _IEBodyReadText($iz)
If StringInStr($tg,"Checking your browser") Then
sleep(6000)
$tg = _IEBodyReadText($iz)
EndIf
_IEQuit($iz)
EndIf
; check for cloudflare and other errors
$res = StringRegExp($tg,"%20", 3)
If @error Then
$nbOccurences = 0
Else
$nbOccurences = UBound($res)
Endif
If $nbOccurences > 2 OR $tg == "noassignment" OR $tg == "" OR $tg == "0" OR StringInStr($tg,"Error") OR StringInStr($tg,"Website is offline") OR StringInStr($tg,"Checking your browser") OR StringInStr($tg,"Cloudflare") Then
loggin("No Assignment"&@CRLF)
Sleep(10000)
Else
loggin("* Target: "&$tg)
_IENavigate($i,"https://twitter.com/"&$tg)
_IELoadWait($i)
$txt = _IEBodyReadText ($i)
sleep(1000)
If StringInStr($txt,'Sorry, that page doesn’t exist!') Then
loggin(" - Does not exist"&@CRLF)
updateTarget($tg)
ElseIf StringInStr($txt,'Account suspended') Then
loggin(" - Account suspended"&@CRLF)
updateTarget($tg)
ElseIf StringInStr($txt,'Tweets are protected.') Then
loggin(" - Reporting"&@CRLF)
Local $oInputs = _IETagNameGetCollection($i, "button")
For $oInput In $oInputs
If $oInput.classname == "user-dropdown dropdown-toggle js-dropdown-toggle js-link js-tooltip btn plain-btn" Then _IEAction($oInput, "click")
Next
Local $oInputs = _IETagNameGetCollection($i, "button")
For $oInput In $oInputs
If $oInput.innerHtml == "Report" Then _IEAction($oInput, "click")
Next
sleep(500)
$fr = _IEFrameGetCollection($i, 1)
$fm = _IEFormGetCollection($i, 1)
$oDoc = _IEDocGetObj($fr)
$oArray = $oDoc.getElementsByTagName("input")
For $element In $oArray
If $element.value = "abuse" Then
_IEAction($element, "click")
EndIf
Next
Local $oInputs = _IETagNameGetCollection($i, "button")
For $oInput In $oInputs
If $oInput.innerHtml == "Next" Then _IEAction($oInput, "click")
Next
sleep(500)
$fr = _IEFrameGetCollection($i, 1)
$fm = _IEFormGetCollection($i, 1)
$oDoc = _IEDocGetObj($fr)
$oArray = $oDoc.getElementsByTagName("input")
For $element In $oArray
If $element.value = "harassment" Then
_IEAction($element, "click")
EndIf
Next
Local $oInputs = _IETagNameGetCollection($i, "button")
For $oInput In $oInputs
If $oInput.innerHtml == "Next" Then _IEAction($oInput, "click")
Next
sleep(500)
$fr = _IEFrameGetCollection($i, 1)
$fm = _IEFormGetCollection($i, 1)
$oDoc = _IEDocGetObj($fr)
$oArray = $oDoc.getElementsByTagName("input")
For $element In $oArray
If $element.value = "Someone_else" Then
_IEAction($element, "click")
EndIf
Next
Local $oInputs = _IETagNameGetCollection($i, "button")
For $oInput In $oInputs
If $oInput.innerHtml == "Next" Then _IEAction($oInput, "click")
Next
sleep(500)
$fr = _IEFrameGetCollection($i, 1)
$fm = _IEFormGetCollection($i, 1)
$oDoc = _IEDocGetObj($fr)
$oArray = $oDoc.getElementsByTagName("input")
For $element In $oArray
If $element.value = "violence" Then
_IEAction($element, "click")
EndIf
Next
Local $oInputs = _IETagNameGetCollection($i, "button")
For $oInput In $oInputs
If $oInput.innerHtml == "Next" Then _IEAction($oInput, "click")
Next
Else
loggin(" - Reporting"&@CRLF)
Local $oInputs = _IETagNameGetCollection($i, "button")
For $oInput In $oInputs
If $oInput.classname == "user-dropdown dropdown-toggle js-dropdown-toggle js-link js-tooltip btn plain-btn" Then _IEAction($oInput, "click")
Next
Local $oInputs = _IETagNameGetCollection($i, "button")
For $oInput In $oInputs
If $oInput.innerHtml == "Report" Then _IEAction($oInput, "click")
Next
sleep(500)
$fr = _IEFrameGetCollection($i, 1)
$fm = _IEFormGetCollection($i, 1)
$oDoc = _IEDocGetObj($fr)
$oArray = $oDoc.getElementsByTagName("input")
For $element In $oArray
If $element.value = "abuse" Then
_IEAction($element, "click")
EndIf
Next
Local $oInputs = _IETagNameGetCollection($i, "button")
For $oInput In $oInputs
If $oInput.innerHtml == "Next" Then _IEAction($oInput, "click")
Next
sleep(500)
$fr = _IEFrameGetCollection($i, 1)
$fm = _IEFormGetCollection($i, 1)
$oDoc = _IEDocGetObj($fr)
$oArray = $oDoc.getElementsByTagName("input")
For $element In $oArray
If $element.value = "harassment" Then
_IEAction($element, "click")
EndIf
Next
Local $oInputs = _IETagNameGetCollection($i, "button")
For $oInput In $oInputs
If $oInput.innerHtml == "Next" Then _IEAction($oInput, "click")
Next
sleep(500)
$fr = _IEFrameGetCollection($i, 1)
$fm = _IEFormGetCollection($i, 1)
$oDoc = _IEDocGetObj($fr)
$oArray = $oDoc.getElementsByTagName("input")
For $element In $oArray
If $element.value = "Someone_else" Then
_IEAction($element, "click")
EndIf
Next
Local $oInputs = _IETagNameGetCollection($i, "button")
For $oInput In $oInputs
If $oInput.innerHtml == "Next" Then _IEAction($oInput, "click")
Next
sleep(500)
$fr = _IEFrameGetCollection($i, 1)
$fm = _IEFormGetCollection($i, 1)
$oDoc = _IEDocGetObj($fr)
$oArray = $oDoc.getElementsByTagName("input")
For $element In $oArray
If $element.value = "violence" Then
_IEAction($element, "click")
EndIf
Next
Local $oInputs = _IETagNameGetCollection($i, "button")
For $oInput In $oInputs
If $oInput.innerHtml == "Next" Then _IEAction($oInput, "click")
Next
EndIf
sleep(1000)
EndIf
WEnd
视图:
loan_balances
然后只做常规的铁轨协会:
create view loan_balances as (
select loans.id as loan_id, loans.amount - sum(payments.amount) as balance
from loans
left outer join payments on payments.loan_id = loans.id
group by 1
)
这种方式在您需要平衡的操作中,您可以使用class LoanBalance < ActiveRecord::Base
belongs_to :loan, inverse_of: :loan_balance
end
class Loan < ActiveRecord::Base
has_one :loan_balance, inverse_of: :loan
delegate :balance, to: :loan_balance, prefix: false
end
急切加载它,但是您不会因为违反贷款本身所有标准CRUD内容中的rails约定而陷入棘手的问题。
答案 1 :(得分:0)
看起来我终于回答了我自己的问题。这里是。我超越了默认范围。
class Loan < ActiveRecord::Base
validates :funded_amount, presence: true, numericality: {greater_than: 0}
has_many :payments, dependent: :destroy, inverse_of: :loan
default_scope {
joins("LEFT JOIN payments as p ON p.loan_id = loans.id")
.group("loans.id").select("loans.*, sum(p.amount) as paid")
}
def balance
funded_amount - (paid || 0)
end
end