find_by_sql in Rails, accessing the resulting array

后端 未结 5 1936
日久生厌
日久生厌 2020-12-30 17:09

I\'m trying to run a query in a very quick and dirty way in Rails, without putting the rest of the model in place. I know this is bad practice but I just need a quick resul

相关标签:
5条回答
  • 2020-12-30 17:36

    This is because find_by_sql returns a model, not data. If you want to do a direct fetch of the data in question, use something like this:

    ShippingZonePrice.connection.select_value(query)
    

    There are a number of direct-access utility methods available through connection that can fetch single values, a singular array, rows of arrays, or rows of hashes. Look at the documentation for ActiveRecord::ConnectionAdapters::DatabaseStatements.

    As when writing an SQL directly, you should be very careful to not create SQL injection bugs. This is why it is usually best to encapsulate this method somewhere safe. Example:

    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 讨论(0)
  • 2020-12-30 17:38

    If not for you saying that you tried and failed accessing [0] i'ld say you want to put

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

    into the view...strange

    0 讨论(0)
  • 2020-12-30 17:40

    So, I had a hacky solution for this, but it works great. Create a table that has the same output as your function and reference it, then just call a function that does a find_by_sql to populate the model.

    Create a dummy table:

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

    Then, create a model that uses the empty table:

    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 
    

    In your controller, you can populate it:

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

    Then, you can see it populate with what you need:

    [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
    }
    ]
    
    0 讨论(0)
  • 2020-12-30 18:01

    Since you are defining an instance method, I think it should return the price if it exists or nil

    Try something like this:

    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
    

    Then this should work for you:

    @item.shipping_price
    

    The first.try(:price) part is needed because find_by_sql may return an empty array. If you tried to do something like first.price on an empty array, you would get an exception along the lines of NoMethodError: undefined method 'price' for nil:NilClass.

    0 讨论(0)
  • 2020-12-30 18:02
    @item.shipping_price.first.price
    

    or

    @item.shipping_price[0].price
    

    Thanks Atastor for pointing that out!

    When you use AS price in find_by_sql, price becomes a property of the result.

    0 讨论(0)
提交回复
热议问题