I'm working in a RoR application using Spreecommerce, but my application is not a simple ecommerce is a plataform with many tenants, and each tenant have a single store. So, it's a single application with many stores. I'm using rails 4.2.2 and spree 3-0-stable branch.
To create my tenancy structure i'm based in this gem:
spree_multi_tenant
And using this gem too: acts_as_tenant
My code:
module SpreeMultiTenant
def self.tenanted_models
[
Spree::Address,
Spree::User,
Spree::Promotion,
Spree::PromotionRule,
Spree::PromotionAction,
Spree::PromotionActionLineItem,
...
]
end
def self.tenanted_controllers
[
Spree::BaseController,
Spree::Api::BaseController,
...
]
end
def self.with_tenant(tenant, &block)
ActsAsTenant.with_tenant tenant do
SpreeMultiTenant.init_preferences
yield
end
end
end
To controllers:
class Spree::Preferences::StoreInstance
def set_without_persist(key, value)
#cache.write(key, value)
end
end
module SpreeMultiTenant
def self.init_preferences
Spree::Preference.all.each do |preference|
Spree::Preferences::Store.instance.set_without_persist(preference.key, preference.value)
end
end
end
SpreeMultiTenant.tenanted_controllers.each do |controller|
controller.class_eval do
set_current_tenant_through_filter
prepend_around_filter :tenant_scope
private
def tenant_scope
tenant = Spree::Tenant.find_by_domain(request.host)
if tenant
set_current_tenant(tenant)
SpreeMultiTenant.with_tenant tenant do
yield
end
end
end
def set_current_tenant_through_filter
self.class_eval do
helper_method :current_tenant
private
def set_current_tenant(current_tenant_object)
ActsAsTenant.current_tenant = current_tenant_object
end
def current_tenant
ActsAsTenant.current_tenant
end
end
end
end
end
To Models:
SpreeMultiTenant.tenanted_models.each do |model|
model.class_eval do
belongs_to :tenant
acts_as_tenant :tenant
end
end
All others areas of the store it's working properly, but when the customer click to buy a product, i've got this error:
Mysql2::Error: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'AND (spree_promotions.starts_at IS NULL OR spree_promotions.starts_at < '2016-02' at line 1: SELECT * FROM ( SELECT `spree_promotions`.* FROM `spree_promotions` INNER JOIN `spree_orders_promotions` ON `spree_promotions`.`id` = `spree_orders_promotions`.`promotion_id` WHERE `spree_promotions`.`tenant_id` = 1 AND `spree_orders_promotions`.`order_id` = 2 AND (spree_promotions.starts_at IS NULL OR spree_promotions.starts_at < '2016-02-19 19:22:01.592902') AND (spree_promotions.expires_at IS NULL OR spree_promotions.expires_at > '2016-02-19 19:22:01.593065') UNION SELECT `spree_promotions`.* FROM `spree_promotions` WHERE `spree_promotions`.`tenant_id` = AND (spree_promotions.starts_at IS NULL OR spree_promotions.starts_at < '2016-02-19 19:22:01.593151') AND (spree_promotions.expires_at IS NULL OR spree_promotions.expires_at > '2016-02-19 19:22:01.593601') AND `spree_promotions`.`code` IS NULL AND `spree_promotions`.`path` IS NULL ) `spree_promotions`
Yes, the error is here: WHERE spree_promotions.tenant_id = AND (spree_promotions.starts_at
But why my tenant_id is empty? Why my scope is not working in this case?
I saw others spree_promotions mysql queries and it's normal:
SELECT `spree_promotions`.* FROM `spree_promotions` WHERE `spree_promotions`.`tenant_id` = 1 AND `spree_promotions`.`id` = 1 LIMIT 1
So i have no ideia to fix it :(
Related
I'm trying to access our database with some fairly simple CGI/Ruby and DBI:
#!/usr/bin/ruby -w
require "dbi"
dbh = DBI.connect("dbi:Mysql:my:mydb", "XXXX", "XXXX")
...
query = "select ETA from mydb.mytable where ID = #{someval}"
rows = dbh.execute(query)
while row = rows.fetch() do
# Do some stuff
...
end
That works fine most of the time, but I hit a record which broke it with the error:
/usr/lib/ruby/vendor_ruby/dbd/Mysql.rb:120:in `parse': invalid date (ArgumentError)
from /usr/lib/ruby/vendor_ruby/dbd/Mysql.rb:120:in `parse'
from /usr/lib/ruby/vendor_ruby/dbi/row.rb:66:in `block in convert_types'
from /usr/lib/ruby/vendor_ruby/dbi/row.rb:65:in `each'
from /usr/lib/ruby/vendor_ruby/dbi/row.rb:65:in `each_with_index'
from /usr/lib/ruby/vendor_ruby/dbi/row.rb:65:in `convert_types'
from /usr/lib/ruby/vendor_ruby/dbi/row.rb:75:in `set_values'
from /usr/lib/ruby/vendor_ruby/dbi/handles/statement.rb:226:in `fetch'
from /usr/lib/cgi-bin/test:39:in `block in <main>'
from /usr/lib/cgi-bin/test:36:in `each'
from /usr/lib/cgi-bin/test:36:in `<main>'
After a bit of detective work I found that it had a date of 0000-00-00 which fetch() doesn't like. Undefined dates are OK, and DBI in Perl can handle all zero dates, it's just DBI in Ruby.
I can fix the database, and I'll try to get the app which wrote the value to the database fixed too, but I think that my Ruby should be resilient to such things. Is there a way to work around this, maybe using rescue somehow?
This is the solution I came up with:
query = "select ETA from mydb.mytable where ID = #{someval}"
rows = dbh.execute(query)
begin
while row = rows.fetch() do
# Do some stuff
...
end
rescue Exception => e
puts "#{e}<br>\n"
retry
end
This works quite nicely as though the retry is starting a new while loop, rows maintains its state so the fetch resumes on the next record.
The only problem is that it's hard to identify the bad record(s). To fix that I issued more queries without the offending field. My somewhat ugly solution:
results = Hash.new
hit_error = false
query = "select ETA,UNIQUE_ID from mydb.mytable where ID = #{someval}"
rows = dbh.execute(query)
begin
while row = rows.fetch() do
# Do some stuff
...
results[row[1]] = row[0]
end
rescue Exception => e
hit_error = true
retry
end
if hit_error
query = "select UNIQUE_ID from mydb.mytable where ID = #{someval}"
rows = dbh.execute(query)
while row = rows.fetch() do
id = row[0]
unless results.has_key?(id)
begin
query = "select ETA for mydb.mytable where UNIQUE_ID = #{id} limit 1"
error = dbh.execute(query)
error.fetch() # expect this to hit same error as before
puts "Unexpected success for UNIQUE_ID #{id}<br>\n"
rescue Exception => e
puts "#{e} at UNIQUE_ID #{id}<br>\n"
end
end
end
end
Finally I'm not sure how valid it is to use DBI/Ruby, it seems deprecated. Same for MySQL/Ruby. I also tried Sequel with little success.
An alternative solution is to use fetch_hash. This fetches all data as strings, which is a little awkward as you need to convert to dates, integers, etc., but does give you the opportunity of trapping the error on the explicit conversion. This also makes it much easier to identify the bad record(s):
query = "select ETA from mydb.mytable where ID = #{someval}"
rows = dbh.execute(query)
while row = rows.fetch_hash() do
begin
eta = Date.parse row["ETA"]
# Do something with the ETA
...
rescue Exception => e
puts "Error parsing date '#{row["ETA"]}': #{e}<br>\n"
# Make do without the ETA
...
end
end
Sequel can handle invalid dates if you use the mysql adapter:
DB = Sequel.connect('mysql://user:password#host/database')
DB.convert_invalid_date_time = :string # or nil
DB.get(Sequel.cast('0000-00-00', Date))
# => "0000-00-00"
This is my python code. It seems that if the raw SQL contains IF NOT EXISTS, sqlalchemy will not execute it. There is no exception thrown either.
db.execute(text(
"""
IF NOT EXISTS ( select 1 from agent_assignment where exception_id = :exception_id )
BEGIN
insert into agent_assignment(exception_id, [user], work_status, insert_date, insert_user)
values (:exception_id, :user, 'pending', :date, :insert_update_user)
END
ELSE
update agent_assignment
set
[user] = :user,
update_date = :date,
update_user = :insert_update_user
where exception_id = :exception_id
"""),
exception_id = exception_id,
user = assignee,
date = datetime.now(),
insert_update_user = insert_update_user
)
If I remove the IF..ELSE part, the SQL will execute correctly. So I guess technically it is impossible to execute the raw SQL with IF..ELSE or EXISTS being a part of the statement?
What is the proper way to run raw SQL?
Thanks in advance.
I need to add COMMIt at the end of the script since the query is kinda complex and somehow sqlalchemy can't auto commit it.
I have the following code and would like to convert the request into a mysql query. Right now I achieve the desired result using a manual .select (array method) on the data. This should be possibile with a single query (correct me if I am wrong).
Current code:
def self.active_companies(zip_code = nil)
if !zip_code
query = Company.locatable.not_deleted
else
query = Company.locatable.not_deleted.where("zip_code = ?", zip_code)
end
query.select do |company|
company.company_active?
end
end
# Check if the company can be considered as active
def company_active?(min_orders = 5, last_order_days_ago = 15)
if orders.count >= min_orders &&
orders.last.created_at >= last_order_days_ago.days.ago &&
active
return true
else
return false
end
end
Explanation:
I want to find out which companies are active. We have a company model and an orders model.
Data:
Company:
active
orders (associated orders)
Orders:
created_at
I don't know if it is possible to make the company_active? predicate a single SQL query, but I can offer an alternative:
If you do:
query = Company.locatable.not_deleted.includes(:orders)
All of the relevant orders will be loaded into the memory for future processing.
This will eliminate all the queries except for 2:
One to get the companies, and one to get all their associated orders.
This is the code I am using
# update db
client = Mysql2::Client.new(:host => "localhost", :username => "jo151", :password => "password", :database => "jo151")
details.each do |d|
if d[:sku] != ""
price = d[:price].split
if price[1] == "D"
currency = 144
else
currency = 168
end
cost = price[0].gsub(",", "").to_f
if d[:qty] == ""
qty = d[:qty2]
else
qty = d[:qty]
end
results = client.query("SELECT * FROM jos_virtuemart_products WHERE product_sku = '#{d[:sku]}' LIMIT 1;")
if results.count == 1
product = results.first
client.query("UPDATE jos_virtuemart_products SET product_sku = '#{d[:sku]}', product_name = '#{d[:desc]}', product_desc = '#{d[:desc]}', product_in_stock = '#{qty}' WHERE virtuemart_product_id =
#{product['virtuemart_product_id']};")
client.query("UPDATE jos_virtuemart_product_prices SET product_price = '#{cost}', product_currency = '#{currency}' WHERE virtuemart_product_id = '#{product['virtuemart_product_id']}';")
else
client.query("INSERT INTO jos_virtuemart_products( product_sku, product_name, product_s_desc, product_in_stock) VALUES('#{d[:sku]}','#{d[:desc]}','#{d[:desc]}','#{d[:qty]}');")
last_id = client.last_id
client.query("INSERT INTO jos_virtuemart_product_prices(virtuemart_product_id, product_price, product_currency) VALUES('#{last_id}', '#{cost}', #{currency});")
end
end
end
`query': Duplicate entry '' for key 3 (Mysql2::Error) on line 35:
client.query("INSERT INTO jos_virtuemart_products( product_sku, product_name, product_s_desc, product_in_stock) VALUES('#{d[:sku]}','#{d[:desc]}','#{d[:desc]}','#{d[:qty]}');")
last_id = client.last_id
Putting in raw SQL statements with arbitrary strings inlined like this is extremely dangerous. You absolutely must escape any values put into them for your application to work at all. The first description you get with an apostrophe will cause your SQL to fail.
In this case you would use client.quote on each and every one of the strings. No exceptions. You have probably seen tons of press about Sony getting hacked, and it's because of mistakes like this that serious breaches happen.
You should investigate using an ORM to help with this, even something as simple as Sequel or DataMapper, as they provide facilities to make this easy.
The reason you are getting a duplicate key is because you have a unique index on one of the columns you're inserting into, or one of the columns is not specified and has a default value that collides with an existing row.
How can i fetch all the table name and row count for the specific table from the specific database ?
Result
Table Name , Row Count , Table Size(MB)
---------------------------------------
table_1 , 10 , 2.45
table_2 , 20 , 4.00
ActiveRecord::Base.connection.tables.each do |table|
h = ActiveRecord::Base.connection.execute("SHOW TABLE STATUS LIKE '#{table}'").fetch_hash
puts "#{h['Name']} has #{h['Rows']} rows with size: #{h['Data_length']}"
end
The question is tagged mysql but you can do it in a DB-agnostic manner via ORM.
class DatabaseReport
def entry_counts
table_model_names.map do |model_name|
entity = model_name.constantize rescue nil
next if entity.nil?
{ entity.to_s => entity.count }
end.compact
end
private
def table_model_names
ActiveRecord::Base.connection.tables.map(&:singularize).map(&:camelize)
end
end
Note that this will skip tables for which you don't have an object mapping such as meta tables like ar_internal_metadata or schema_migrations. It also cannot infer scoped models (but could be extended to do so). E.g. with Delayed::Job I do this:
def table_model_names
ActiveRecord::Base.connection.tables.map(&:singularize).map(&:camelize) + ["Delayed::Job"]
end
I came up with my own version which is also db agnostic.
As it uses the descendants directly it also handles any tables where the table_name is different to the model name.
The rescue nil exists for cases when you have the class that inherits from ActiveRecord but for some reason don't have a table associated with it. It does give data for STI classes and the parent class.
my_models = ActiveRecord::Base.descendants
results = my_models.inject({}) do |result, model|
result[model.name] = model.count rescue nil
result
end
#temp_table = []
ActiveRecord::Base.connection.tables.each do |table|
count = ActiveRecord::Base.connection.execute("SELECT COUNT(*) as count FROM #{table}").fetch_hash['count']
size = ActiveRecord::Base.connection.execute("SHOW TABLE STATUS LIKE '#{table}'").fetch_hash
#temp_table << {:table_name => table,
:records => count.to_i,
:size_of_table => ((BigDecimal(size['Data_length']) + BigDecimal(size['Index_length']))/1024/1024).round(2)
}
end
end