2012-09-19 30 views
5

我試圖在Rails中以非常快速和骯髒的方式運行查詢,而沒有將模型的其餘部分放在適當位置。我知道這是不好的做法,但我只需要在緊張的時間內得到一個快速結果,直到我已經完成整個解決方案。在Rails中查找_by_sql,訪問產生的數組

我有物品有運費價格,基於重量。重量存儲在該項目中,價格是存儲在表shipping_zone_prices,而我現在要做的就是尋找與第一行,其中所述重量比大減價商品較重的價格:

class Item < ActiveRecord::Base 
    def shipping_price 
    item_id = self.id 
    shipping_price = ShippingZonePrice.find_by_sql(
     "SELECT z.price as price 
     FROM shipping_zone_prices z, items i 
     WHERE i.id = '#{item_id}' 
     AND z.weight_g > d.weight 
     ORDER BY z.weight_g asc limit 1")  
    end 
end 

這種作品。在SQL做這項工作,但是當插入到應用程序如下:

<%= @item.shipping_price %> Shipping 

我得到以下顯示:

[#<ShippingZonePrice price: 12>] Shipping 

在這個例子中,「12」是正在從拉價格數據庫,並且是正確的。 @ item.shipping_price.class返回'Array'。嘗試使用[0](或任何其他整數)訪問數組將返回空白。

是否有另一種方式來訪問此,或者我缺少一些基本的東西?

+1

什麼是'd.weight'? –

+0

那麼發現,應該是i.weight - 我只是想通過使用'item'使數據變得有點泛化,並且我沒有做出所有正確的更改 –

回答

6

由於要定義一個實例方法,我認爲它應該返回price如果存在或nil

嘗試這樣:

def shipping_price 
    ShippingZonePrice.find_by_sql(
    "SELECT z.price as price 
    FROM shipping_zone_prices z, items i 
    WHERE i.id = '#{self.id}' 
    AND z.weight_g > d.weight 
    ORDER BY z.weight_g asc limit 1").first.try(:price) 
end 

那麼這應該爲你工作:需要

@item.shipping_price 

first.try(:price)部分是因爲find_by_sql可能會返回一個空數組。如果您嘗試在空陣列上執行類似first.price的操作,則會沿着NoMethodError: undefined method 'price' for nil:NilClass的行發生異常。

3
@item.shipping_price.first.price 

@item.shipping_price[0].price 

感謝Atastor指出了這一點!

當您在find_by_sql中使用AS price時,price成爲結果的屬性。

1

如果不是你說你嘗試和失敗訪問[0] i'ld說你想要把

@item.shipping_price.first.price # I guess BSeven just forgot the .first. in his solution 

到視圖......奇怪

5

這是因爲find_by_sql返回一個模型,而不是數據。如果你想做一個直接獲取有關數據,使用這樣的:

ShippingZonePrice.connection.select_value(query) 

有可通過connection,可以獲取單個值,單個陣列中的一些直接訪問的實用方法,行數組或行的散列。請登錄ActiveRecord::ConnectionAdapters::DatabaseStatements查看documentation

在直接編寫SQL時,應該非常小心,不要創建SQL注入漏洞。這就是爲什麼通常最好將此方法封裝在安全的地方。例如:

class ShippingZonePrice < ActiveRecord::Base 
    def self.price_for_item(item) 
    self.connection.select_value(
     self.sanitize_sql(
     %Q[ 
      SELECT z.price as price 
      FROM shipping_zone_prices z, items i 
      WHERE i.id=? 
       AND z.weight_g > d.weight 
      ORDER BY z.weight_g asc limit 1 
     ], 
     item.id 
    ) 
    ) 
    end 
end 
0

所以,我有一個hacky的解決方案,但它很好。 創建一個與您的函數具有相同輸出的表並引用它,然後調用一個執行find_by_sql來填充模型的函數。

創建一個虛擬表:

CREATE TABLE report.compliance_year (
id BIGSERIAL, 
year TIMESTAMP, 
compliance NUMERIC(20,2), 
fund_id INT); 

然後,創建一個使用空表的模型:

class Visualization::ComplianceByYear < ActiveRecord::Base 
    self.table_name = 'report.compliance_year' 
    def compliance_by_year(fund_id) 
     Visualization::ComplianceByYear.find_by_sql([" 
      SELECT year, compliance, fund_id 
       FROM report.usp_compliance_year(ARRAY[?])", fund_id]) 
    end 
end 

在你的控制器,你可以填充它:

def visualizations 
    @compliancebyyear = Visualization::ComplianceByYear.new() 
    @compliancefunds = @compliancebyyear.compliance_by_year(current_group.id) 
    binding.pry 
end 

然後,您可以看到它滿足您的需求:

[1] pry(#<Thing::ThingCustomController>)> @compliancefunds 
[ 
[0] #<Visualization::ComplianceByYear:0x00000008f78458> { 
      :year => Mon, 31 Dec 2012 19:00:00 EST -05:00, 
    :compliance => 0.93, 
     :fund_id => 1 
}, 
[1] #<Visualization::ComplianceByYear:0x0000000a616a70> { 
      :year => Tue, 31 Dec 2013 19:00:00 EST -05:00, 
    :compliance => 0.93, 
     :fund_id => 4129 
}, 
[2] #<Visualization::ComplianceByYear:0x0000000a6162c8> { 
      :year => Wed, 31 Dec 2014 19:00:00 EST -05:00, 
    :compliance => 0.93, 
     :fund_id => 4129 
} 
]