2015-12-03 4 views
-1

У меня есть следующий метод в модели с именем CashTransaction.Извлечение записей, которые удовлетворяют функции модели в Rails

def is_refundable? 
    self.amount > self.total_refunded_amount 
end 

def total_refunded_amount 
    self.refunds.sum(:amount) 
end 

Теперь мне нужно, чтобы извлечь все записи, которые удовлетворяют указанным выше функции записи, которые т.е. возвращают true.

я получил, что работает, используя следующее заявление:

CashTransaction.all.map { |x| x if x.is_refundable? }

Но результат является Array. Я ищу ActiveRecord_Relation объект, так как мне нужно выполнить join на результат.

Я чувствую, что здесь что-то не хватает, поскольку это не выглядит так сложно. В любом случае, это заставило меня застрять. Конструктивные предложения были бы замечательными.

Примечание: amount является колонкой CashTransaction.

EDIT

После SQL делает работу. Если я могу изменить это на ORM, он все равно выполнит эту работу.

SELECT `cash_transactions`.* FROM `cash_transactions` INNER JOIN `refunds` ON `refunds`.`cash_transaction_id` = `cash_transactions`.`id` WHERE (cash_transactions.amount > (SELECT SUM(`amount`) FROM `refunds` WHERE refunds.cash_transaction_id = cash_transactions.id GROUP BY `cash_transaction_id`)); 

Sharing Прогресс

мне удалось получить его работу, следуя ОРМ:

CashTransaction 
    .joins(:refunds) 
    .group('cash_transactions.id') 
    .having('cash_transactions.amount > sum(refunds.amount)') 

Но что я на самом деле ищет что-то вроде:

CashTransaction.joins(:refunds).where(is_refundable? : true) 

где is_refundable? является модельной функцией. Первоначально я думал, что установка is_refundable? будет работать attr_accesor. Но я был неправ.

Просто подумайте, можно ли устранить проблему элегантным способом, используя Arel.

+0

@ AndreyDeineko, я нашел работу вокруг. Можете ли вы посмотреть мое редактирование и поделиться своим мнением? – abhinavmsra

+0

эй, был ли мой ответ полезен? вы его работали? –

ответ

3

Существует два варианта.

1) Готово, что вы начали (что крайне неэффективно, когда дело доходит до большего количества данных, так как все это берется в память перед обработкой):

CashTransaction.all.map(&:is_refundable?) # is the same to what you've written, but shorter. 

SO получить идентификаторы:

ids = CashTransaction.all.map(&:is_refundable?).map(&:id) 

и теперь, чтобы получить ActiveRecord Relation:

CashTransaction.where(id: ids) # will return a relation 

2) Переместить Calculat ion к SQL:

CashTransaction.where('amount > total_refunded_amount') 

Второй вариант - все возможное, быстрое и эффективное.

Когда вы имеете дело с базой данных, попробуйте обработать ее на уровне базы данных с минимальным вовлечением Ruby.

EDIT

Согласно отредактированной вопрос здесь, как вы бы достичь желаемого результата:

CashTransaction.joins(:refunds).where('amount > SUM(refunds.amount)') 

EDIT # 2

Как обновлениям в вопросе - я не понимаю , почему вы зафиксировали на is_refundable? в качестве метода экземпляра, который может быть использован в запросе, что в принципе невозможно в AR, но ..

Мое предложение заключается в создании области is_refundable:

scope :is_refundable, -> { CashTransaction 
    .joins(:refunds) 
    .group('cash_transactions.id') 
    .having('cash_transactions.amount > sum(refunds.amount)') 
} 

Теперь доступна в качестве краткой нотации

CashTransaction.is_refundable 

, который короче и понятнее, чем направлены

CashTransaction.where('is_refundable = ?', true) 
+0

Я отредактировал вопрос. 'total_refunded_amount' не является столбцом таблицы. – abhinavmsra

+0

@abhinavmsra, если 'total_refunded_amount' не является фактическим столбцом в базе данных, тогда вы не можете сделать это с помощью SQL. См. Мой ответ, пожалуйста. –

+0

@abhinavmsra, так что вы ожидаете от меня сейчас? Как я могу предоставить вам больше информации? Я показал, как сделать это по-своему. Если вы хотите увидеть решение SQL, покажите мне метод 'total_refunded_amount', возможно, вычисления возможны в SQL. –

1

Вы можете сделайте это так:

cash_transactions = CashTransaction.all.map { |x| x if x.is_refundable? } # Array 
CashTransaction.where(id: cash_transactions.map(&:id)) # ActiveRecord_Relation 

Но это эффективный способ сделать это, как упомянули другие ответчики.

Вы можете сделать это с помощью SQL, если amount и total_refunded_amount являются столбцы cash_transactions таблицы в базе данных, которая будет гораздо более эффективным и производительным:

CashTransaction.where('amount > total_refunded_amount') 

Но, если amount или total_refunded_amount не фактический столбцов в базе данных, то вы не можете сделать это таким образом. Тогда, я думаю, вы сделали это другим способом, который неэффективен, чем использование необработанного SQL.

0

Я думаю, вы должны предварительно вычислить is_refundable результат (в новом столбце), когда CashTransaction и его возвраты обновляется с использованием функции обратного вызова (предполагается has_many?):

class CashTransaction 
    before_save :update_is_refundable 
    def update_is_refundable 
    is_refundable = amount > total_refunded_amount 
    end 
    def total_refunded_amount 
    self.refunds.sum(:amount) 
    end 
end 

class Refund 
    belongs_to :cash_transaction 
    after_save :update_cash_transaction_is_refundable 
    def update_cash_transaction_is_refundable 
    cash_transaction.update_is_refundable 
    cash_transaction.save! 
    end 
end 

Примечание: Приведенный выше код должен конечно, быть оптимизированы, чтобы предотвратить некоторые запросы

Они вы можете запросить is_refundable колонки:

CashTransaction.where(is_refundable: true) 
0

Я думаю, что это не плохо сделать это на двух запросов вместо таблицы объединения, что-то вроде этого

def refundable 
    where('amount < ?', total_refunded_amount) 
end 

Это будет сделать один запрос сумма затем использовать сумму во втором запросе, когда таблицы становятся больше вы можете обнаружить, что это быстрее, чем выполнение соединения в базе данных.