How to use AND condition in an ActiveRecord query - ruby-on-rails

I currently have these three tables:
create_table "cocktail_ingredients", force: :cascade do |t|
t.integer "cocktail_id"
t.integer "ingredient_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["cocktail_id"], name: "index_cocktail_ingredients_on_cocktail_id"
t.index ["ingredient_id"], name: "index_cocktail_ingredients_on_ingredient_id"
create_table "cocktails", force: :cascade do |t|
t.string "title"
t.string "ingredients"
t.text "method"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
create_table "ingredients", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
I'm trying to do a seach that returns all the cocktails that contain ALL of the ingredients in my search.
So far i've managed to come up with this:
CocktailIngredient.where(ingredient_id: [1, 4]).map(&:cocktail).uniq
Which returns all the cocktails that include the ingredient_id of 1 or 4. I am trying to return only the cocktails that contain BOTH ingredients with the id 1 AND 4.
Any help would be much appreciated.

Here is a generalised version, if you have an ingredient_ids array:
cocktail_ingredients = [1, 4]
cocktail_ingredients = CocktailIngredient.where(ingredient_id: ingredient_ids).select(:cocktail_id)
cocktail_ingredients = cocktail_ingredients.group(:cocktail_id).having('COUNT(ingredient_id) >= ?', ingredient_ids.count)
cocktails = Cocktail.where(id: cocktail_ingredients.select(:cocktail_id))
Here is a tested scope for your Cocktail model (with Rspec and FactoryGirl). You'll just have to call Cocktail.with_ingredients([1,4]).
In cocktail.rb:
scope :with_ingredients, (lambda do |ingredient_ids|
cocktail_ingredients = CocktailIngredient.where(ingredient_id: ingredient_ids).select(:cocktail_id)
cocktail_ingredients = cocktail_ingredients.group(:cocktail_id).having('COUNT(ingredient_id) >= ?', ingredient_ids.count)
where(id: cocktail_ingredients.select(:cocktail_id))
end)
And cocktail_spec.rb: https://gist.github.com/ArnoHolo/54b9259fbaa067d7abbf04a73d94ec40

The best I found for now is:
Cocktail.joins(:cocktail_ingredients).where(cocktail_ingredients: { ingredient_id: 1, cocktail_id: CocktailIngredient.where(ingredient_id: 4).select(:cocktail_id) })
I don't think it's the best we can do, but it's MUCH MORE optimized then previous solution

Related

Make a class method to count the comments

I am learning the Ruby on Rails and I would like to make a class methods to count the article comments.
I am creating this class method
class_methods do
def comment_count
end
end
And this is my schemas
create_table "articles", force: :cascade do |t|
t.string "title"
t.text "body"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "status", default: "public"
end
create_table "comments", force: :cascade do |t|
t.string "commenter"
t.text "body"
t.integer "article_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "status"
t.index ["article_id"], name: "index_comments_on_article_id"
end
add_foreign_key "comments", "articles"

Rails 6: How to use generate_series to show if an action was taken on a given day

I'm building a simple Habit Tracker. A User has many Habits and I need to track if they've completed a Habit on a given day across time.
user.rb
has_many :habits
has_many :completed_habits
habit.rb
belongs_to :user
has_many :completed_habits
completed_habit.rb
belongs_to :user
belongs_to :habit
I have a method on my Habit model that I would like to create an array of dates with either a 1 or 0 value, and then I'll just loop through that array in the view and show an X or a Check.
def sequence
query = <<-SQL
SELECT
series_date,
COUNT(habit_id) AS completed
FROM generate_series(
( SELECT created_at::date FROM habits
WHERE habits.id = :user_id
),
CURRENT_DATE,
'1 day'
) AS series_date
LEFT JOIN completed_habits ON completed_habits.created_at::date = series_date
WHERE habit_id = :habit_id
AND user_id = :user_id
GROUP BY series_date
ORDER BY series_date DESC;
SQL
Habit.find_by_sql([query, { user_id: self.user, habit_id: self.id } ])
end
When I run that query in Postico, I get the following, which is almost there (I'd rather there be a row for the dates that don't have a completed_habit on that day with a completed value of 0):
However, when I render #habit.sequence in my Rails view, I get this:
I don't understand why it isn't returning the series_date, habit_id, and completed values I'm seeing in Postico.
UPDATE:
Using Model.connection.exec_query I've now got this working, but it still feels pretty clunky to me. Is there a cleaner, more "Railsy" way to get this done?
def sequence
query = <<-SQL
SELECT
series_date,
COUNT(habit_id) AS completed
FROM generate_series(
( SELECT created_at::date FROM habits
WHERE habits.id = $1
),
CURRENT_DATE,
'1 day'
) AS series_date
LEFT JOIN completed_habits ON completed_habits.created_at::date = series_date
GROUP BY series_date
ORDER BY series_date ASC;
SQL
binds = [
ActiveRecord::Relation::QueryAttribute.new("habit_id", self.id, ActiveRecord::Type::Integer.new)
]
Habit.connection.exec_query(query, 'sql', binds).to_a
end
schema.rb
create_table "completed_habits", force: :cascade do |t|
t.integer "user_id"
t.integer "habit_id"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
end
create_table "habits", force: :cascade do |t|
t.string "name"
t.integer "user_id"
t.string "frequency"
t.boolean "is_active"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.text "description"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.string "name"
t.index ["email"], name: "index_users_on_email", unique: true
t.index ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end

Ruby on Rails array not returning results

Following were my query in controller:
#cref = Creference.where("lower(name) LIKE ?", "#{#city.downcase}")
if #cref.present?
cities_array = #cref.map {|con| "%#{con.countries}%" }
#cities_array return --> ["%["Kuching", "Kota Kinabalu"]%"]
#products.where("city ILIKE ANY ( array[?] )", cities_array) --> []
end
The product doesn't return any result despite there is city with above name in Product table. Thanks!!
SCHEMA
product:
create_table "products", force: :cascade do |t|
t.integer "user_id"
t.string "name"
t.integer "payment_type", limit: 2
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.integer "product_category_id"
t.integer "location_id"
t.text "description"
t.text "highlight"
t.integer "price_cents"
t.integer "zip"
t.string "country"
t.string "state"
t.string "city"
t.string "address"
t.string "apt"
t.integer "refund_day"
t.string "currency"
t.integer "refund_percent"
t.integer "refundable", limit: 2, default: 0
t.integer "step", limit: 2, default: 0
t.integer "discount", default: 0
t.string "slug"
t.integer "status", limit: 2, default: 1
t.integer "verification", limit: 2, default: 0
end
creference:
create_table "creferences", force: :cascade do |t|
t.string "name"
t.string "countries", default: [], array: true
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
You can do something like that:
#cref = Creference.where("lower(name) LIKE ?", "#{#city.downcase}")
if #cref.present?
cities_array = #cref.collect {|con| con.countries }.flatten
#cities_array will return --> ["Kuching", "Kota Kinabalu"]
#products.where("city IN (?)", cities_array)
# the IN query will find the city names that are in the cities_array
# but in that case your product tables city name should be in lowercase
# as you are fetching lowercase city names from Creference
end
N.B. I've not tested the code above. But it should work. :)
First, check the value of cities_array to see what is returns.
Second, try to append to_sql at the end of the statement and to print it to see what the sql looks like:
puts #products.where("city ILIKE ANY ( array[?] )", cities_array).to_sql
Last but not least, Product.count to see that you actually have products in your table :).

How can I compare an attribute of a instance, which is a date, to the current date?

I am trying to write a scope or a method where I take the attribute (last_eaten) of an instance (line_item) and compare it to the current date. If last_eaten has a date of 1-7 days ago, it gets put in an array that will be called last_week. If last_eaten has a date of 8-14 days ago, it gets put in an array that will be called 2_weeks_ago.
I've tried quite a few things as you can see with the commented out code and several things that I had already erased, but I can't get anything to work. I'm relatively new to rails and any help would be greatly appreciated.
Model
class LineItem < ActiveRecord::Base
belongs_to :recipe
belongs_to :recipe_collection
#scope :last_week, lambda {where("line_item.last_eaten >= ?", 7.days.ago)}
#scope :last_week, lambda { |weeks| where("last_eaten > ?", weeks) }
#scope :three_weeks, lambda { where( #line_item.last_eaten < 21.days.ago.to_date) }
##line_item = LineItem.where(last_eaten: params[:last_eaten]) -- returns nil
##line_item = LineItem.where(last_eaten: params[:last_eaten] < 21.days.ago.to_date)
#def menu
# list = []
# if LineItem.last_eaten.day.to_i > 21.days.ago.day.to_i
# LineItem.last_eaten.each do |recipe_id|
# LineItem.recipe_id << list
# end
# end
# list
#end
end
Schema
ActiveRecord::Schema.define(version: 20151229223926) do
create_table "directions", force: :cascade do |t|
t.text "step"
t.integer "recipe_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "directions", ["recipe_id"], name: "index_directions_on_recipe_id"
create_table "ingredients", force: :cascade do |t|
t.string "name"
t.integer "recipe_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "ingredients", ["recipe_id"], name: "index_ingredients_on_recipe_id"
create_table "line_items", force: :cascade do |t|
t.integer "recipe_id"
t.integer "recipe_collection_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.date "last_eaten"
end
add_index "line_items", ["recipe_collection_id"], name: "index_line_items_on_recipe_collection_id"
add_index "line_items", ["recipe_id"], name: "index_line_items_on_recipe_id"
create_table "recipe_collections", force: :cascade do |t|
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "recipes", force: :cascade do |t|
t.string "title"
t.text "description"
t.integer "user_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.string "image_file_name"
t.string "image_content_type"
t.integer "image_file_size"
t.datetime "image_updated_at"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
t.string "encrypted_password", default: "", null: false
t.string "reset_password_token"
t.datetime "reset_password_sent_at"
t.datetime "remember_created_at"
t.integer "sign_in_count", default: 0, null: false
t.datetime "current_sign_in_at"
t.datetime "last_sign_in_at"
t.string "current_sign_in_ip"
t.string "last_sign_in_ip"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
add_index "users", ["email"], name: "index_users_on_email", unique: true
add_index "users", ["reset_password_token"], name: "index_users_on_reset_password_token", unique: true
end
scope :last_week, lambda {where("line_item.last_eaten >= ?", 7.days.ago)}
should work...
But further reading made me realise that your table is called line_items, not line_item
When you're doing sql-snippets, you need to refer to the name of the table in SQL, rather than treating it like an individual rails object's name. This means always use the pluralised version :)

ActiveResource::ResourceNotFound: Failed. Response code = 404. Response message = Not Found

Hi I'm working on a rails project, I updated the development database of my project recently. I had sqlite, now I have Postgres.
I have this method for my Product model:
def self.update_products!
ec_products = ElemetalCapital::Product.all
transaction do
ec_products.each do |ec_product|
product = ElemetalCapitalProduct.where(id: ec_product.id).first_or_initialize
product.spot_id = ec_product.spot
product.goldtrex_markup ||= 1 # default to a 1% markup
product.description = ec_product.description
product.metal = ec_product.metal
product.weight = ec_product.weight
product.elemetal_capital_premium = ec_product.premiumBuy
product.save!
end
end
end
Before the Postgres update, the method was working properly. However, after the update I'm getting this error, how can I fix that problem:
[2] pry(main)> Product.update_products!
(0.5ms) BEGIN
ElemetalCapitalProduct Load (0.5ms) SELECT "products".* FROM "products" WHERE "products"."type" IN ('ElemetalCapitalProduct') AND "products"."id" = $1 ORDER BY "products"."id" ASC LIMIT 1 [["id", "GKILO-OPM"]]
(0.4ms) ROLLBACK
ActiveResource::ResourceNotFound: Failed. Response code = 404. Response message = Not Found.
from /Users/enriquesalceda/.rbenv/versions/2.2.2/lib/ruby/gems/2.2.0/gems/activeresource-4.0.0/lib/active_resource/connection.rb:144:in `handle_response'
Something that is very strange on the is the "products"."id" = $1, it shouldn't be $1.
This app use the API of a supplier elemetal capital, which provides the info about their products, and prices, then after a few calculations we update the shopify database.
Just for the record:
This is my entire Product model:
class Product < ActiveRecord::Base
self.primary_key = :id
monetize :elemetal_capital_premium_cents, allow_nil: true
belongs_to :spot
def to_hash
instance_variables.each_with_object({}) do |var, hash|
hash[var.to_s.delete("#")] = instance_variable_get(var)
end
end
def metal_name
case metal
when "Ag" then "Silver"
when "Au" then "Gold"
when "Pd" then "Palladium"
when "Pt" then "Platinum"
end
end
def price
# return 1300 if spot.nil?
spot_price = spot.ask
ec_price = spot_price + elemetal_capital_premium
total_price = ec_price * weight
gt_price = total_price + (goldtrex_markup / 100 * total_price)
gt_price.exchange_to(:AUD)
end
def shopify_variant_data
{
barcode: id,
price: price.to_s,
weight: weight,
weight_unit: "oz",
grams: weight * 31.1034768
}
end
before_create :shopify_create
def shopify_create
data = {
title: "#{metal_name} - #{description}",
variants: [
shopify_variant_data
]
}
sp = ShopifyAPI::Product.create(data)
self.shopify_id = sp.id
end
before_update :shopify_update
def shopify_update
sp = ShopifyAPI::Product.find(shopify_id)
variant = sp.variants.first
shopify_variant_data.each do |k, v|
instance_variable_set("##{k.to_s}".to_sym, v)
end
variant.save!
end
def self.update_products!
ec_products = ElemetalCapital::Product.all
transaction do
ec_products.each do |ec_product|
product = ElemetalCapitalProduct.where(id: ec_product.id).first_or_initialize
product.spot_id = ec_product.spot
product.goldtrex_markup ||= # default to a 1% markup
product.description = ec_product.description
product.metal = ec_product.metal
product.weight = ec_product.weight
product.elemetal_capital_premium = ec_product.premiumBuy
product.save!
end
end
end
end
This is the schema:
ActiveRecord::Schema.define(version: 20150609085027) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
create_table "deliveries", force: :cascade do |t|
t.string "receiver"
t.datetime "delivery_day"
t.string "tracking_number"
t.text "delivery_notes"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "ticket_id"
end
create_table "elemetal_capital_trades", force: :cascade do |t|
t.string "location"
t.string "currency"
t.string "side"
t.string "elemetal_capital_product_id"
t.integer "quantity"
t.string "elemetal_capital_trade_id"
t.float "price_per_unit"
t.float "weight"
t.float "price_per_weight"
t.float "price_total"
t.string "time_stamp_origin"
t.string "metal"
t.float "spot"
t.integer "line_item_id"
end
add_index "elemetal_capital_trades", ["line_item_id"], name: "index_elemetal_capital_trades_on_line_item_id", using: :btree
create_table "employees", force: :cascade do |t|
t.string "first_name"
t.string "last_name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "items", force: :cascade do |t|
t.integer "quantity"
t.string "item_description"
t.float "unit_price"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "ticket_id"
end
create_table "line_items", force: :cascade do |t|
t.integer "quantity"
t.integer "shopify_line_item_id"
t.integer "order_id"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "shopify_product_id", limit: 8
end
add_index "line_items", ["order_id"], name: "index_line_items_on_order_id", using: :btree
create_table "orders", force: :cascade do |t|
t.integer "order_number"
t.integer "shopify_order_id"
t.integer "total"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
create_table "payments", force: :cascade do |t|
t.datetime "value_date"
t.integer "reference_number"
t.float "contract_rate", default: 0.0
t.string "trade_notes"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "ticket_id"
t.float "usd_payment", default: 0.0
end
create_table "products", id: false, force: :cascade do |t|
t.string "id", null: false
t.string "type", null: false
t.text "description", null: false
t.decimal "weight", null: false
t.string "metal", null: false
t.string "spot_id", null: false
t.integer "elemetal_capital_premium_cents"
t.decimal "goldtrex_markup", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "shopify_id", limit: 8, null: false
end
add_index "products", ["shopify_id"], name: "index_products_on_shopify_id", unique: true, using: :btree
add_index "products", ["spot_id"], name: "index_products_on_spot_id", using: :btree
create_table "spots", id: false, force: :cascade do |t|
t.string "id", null: false
t.integer "bid_cents", null: false
t.integer "ask_cents", null: false
end
create_table "tickets", force: :cascade do |t|
t.integer "goldtrex_employee"
t.string "ticket_number"
t.datetime "elemetal_capital_order_date"
t.string "trader"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.boolean "au"
t.float "au_spot_price"
t.boolean "ag"
t.float "ag_spot_price"
t.boolean "deposit"
t.float "deposit_amount"
end
end
Rails uses ORM, which hides all logic of working with DB into nice methods. Basically that means that if you change the DB – nothing will happen, app will continue to work as expected (should mention, this statement does not applicable in any case as DBs differ, but not in this case). If you get 404 – it means item is missing in the database, nothing wrong about that.
When you said you changed DB from sqlite to Postgres – had you migrated the data? Try run ElemetalCapitalProduct.count from the console to ensure it has anything. If it does, compare data you had in sqlite and the data you receive in Postgres.

Resources