Moh*_*qui 12 ruby mysql json ruby-on-rails
我试图使用ruby on rails复制crunchbase的搜索列表样式.我有一系列过滤器,看起来像这样:
[
{
"id":"0",
"className":"Company",
"field":"name",
"operator":"starts with",
"val":"a"
},
{
"id":"1",
"className":"Company",
"field":"hq_city",
"operator":"equals",
"val":"Karachi"
},
{
"id":"2",
"className":"Category",
"field":"name",
"operator":"does not include",
"val":"ECommerce"
}
]
Run Code Online (Sandbox Code Playgroud)
我将这个json字符串发送到我已经实现了这个逻辑的ruby控制器:
filters = params[:q]
table_names = {}
filters.each do |filter|
filter = filters[filter]
className = filter["className"]
fieldName = filter["field"]
operator = filter["operator"]
val = filter["val"]
if table_names[className].blank?
table_names[className] = []
end
table_names[className].push({
fieldName: fieldName,
operator: operator,
val: val
})
end
table_names.each do |k, v|
i = 0
where_string = ''
val_hash = {}
v.each do |field|
if i > 0
where_string += ' AND '
end
where_string += "#{field[:fieldName]} = :#{field[:fieldName]}"
val_hash[field[:fieldName].to_sym] = field[:val]
i += 1
end
className = k.constantize
puts className.where(where_string, val_hash)
end
Run Code Online (Sandbox Code Playgroud)
我所做的是,我遍历json数组并使用键创建一个哈希作为表名,值是具有列名,运算符和应用该运算符的值的数组.在table_names创建哈希之后我会有类似的东西:
{
'Company':[
{
fieldName:'name',
operator:'starts with',
val:'a'
},
{
fieldName:'hq_city',
operator:'equals',
val:'karachi'
}
],
'Category':[
{
fieldName:'name',
operator:'does not include',
val:'ECommerce'
}
]
}
Run Code Online (Sandbox Code Playgroud)
现在我遍历table_names哈希并使用Model.where("column_name = :column_name", {column_name: 'abcd'})语法创建where查询.
所以我会生成两个查询:
SELECT "companies".* FROM "companies" WHERE (name = 'a' AND hq_city = 'b')
SELECT "categories".* FROM "categories" WHERE (name = 'c')
Run Code Online (Sandbox Code Playgroud)
我现在有两个问题:
1.运营商:
我有很多运算符可以应用于像'开头','结束','等于','不等于','包含','不包括','大于','小于".我猜测最好的方法是在运算符上做一个switch case并在构造where字符串时使用适当的符号.因此,例如,如果运营商是'开始',我会where_string += "#{field[:fieldName]} like %:#{field[:fieldName]}"为其他人做类似的事情.
这种方法是否正确,这种类型的通配符语法是否允许.where?
2.超过1张桌子
如您所见,我的方法为超过2个表构建了2个查询.我不需要2个查询,我需要类别名称在类别属于公司的同一查询中.
现在我想要做的是我需要创建一个这样的查询:
Company.joins(:categories).where("name = :name and hq_city = :hq_city and categories.name = :categories[name]", {name: 'a', hq_city: 'Karachi', categories: {name: 'ECommerce'}})
Run Code Online (Sandbox Code Playgroud)
但这不是它.搜索可能变得非常复杂.例如:
公司有很多FundingRound.FundingRound可以有很多投资和投资可以有很多IndividualInvestor.所以我可以选择创建一个过滤器,如:
{
"id":"0",
"className":"IndividualInvestor",
"field":"first_name",
"operator":"starts with",
"val":"za"
}
Run Code Online (Sandbox Code Playgroud)
我的方法会创建一个这样的查询:
SELECT "individual_investors".* FROM "individual_investors" WHERE (first_name like %za%)
Run Code Online (Sandbox Code Playgroud)
这个查询是错误的.我想询问个人投资者对公司融资的投资情况.这是很多连接表.
我使用的方法适用于单个模型,无法解决我上面提到的问题.
我该如何解决这个问题?
您可以根据哈希值创建 SQL 查询。最通用的方法是原始 SQL,它可以由ActiveRecord.
以下是一些概念代码,应该可以为您提供正确的想法:
query_select = "select * from "
query_where = ""
tables = [] # for selecting from all tables
hash.each do |table, values|
table_name = table.constantize.table_name
tables << table_name
values.each do |q|
query_where += " AND " unless query_string.empty?
query_where += "'#{ActiveRecord::Base.connection.quote(table_name)}'."
query_where += "'#{ActiveRecord::Base.connection.quote(q[fieldName)}'"
if q[:operator] == "starts with" # this should be done with an appropriate method
query_where += " LIKE '#{ActiveRecord::Base.connection.quote(q[val)}%'"
end
end
end
query_tables = tables.join(", ")
raw_query = query_select + query_tables + " where " + query_where
result = ActiveRecord::Base.connection.execute(raw_query)
result.to_h # not required, but raw results are probably easier to handle as a hash
Run Code Online (Sandbox Code Playgroud)
这是做什么的:
query_select指定您想要在结果中包含哪些信息query_where构建所有搜索条件并转义输入以防止 SQL 注入query_tables是您需要搜索的所有表的列表table_name = table.constantize.table_name将为您提供模型使用的 SQL table_nameraw_query是上面部分的实际组合 SQL 查询ActiveRecord::Base.connection.execute(raw_query)在数据库上执行sql确保将所有用户提交的输入放在引号中并正确转义以防止 SQL 注入。
对于您的示例,创建的查询将如下所示:
select * from companies, categories where 'companies'.'name' LIKE 'a%' AND 'companies'.'hq_city' = 'karachi' AND 'categories'.'name' NOT LIKE '%ECommerce%'
Run Code Online (Sandbox Code Playgroud)
此方法可能需要额外的逻辑来连接相关的表。就您而言,如果company和category有关联,您必须将类似的内容添加到query_where
"AND 'company'.'category_id' = 'categories'.'id'"
Run Code Online (Sandbox Code Playgroud)
简单方法:您可以为所有可查询的模型/表对创建一个哈希,并在其中存储适当的连接条件。即使对于中型项目,这个哈希也不应该太复杂。
硬方法:has_many如果您有,has_one并且belongs_to在模型中正确定义,这可以自动完成。您可以使用Reflect_on_all_associations获取模型的关联。实现Breath-First-SearchorDepth-First Search算法并从任何模型开始,然后从 json 输入中搜索与其他模型的匹配关联。开始新的 BFS/DFS 运行,直到 json 输入中没有未访问的模型为止。从找到的信息中,您可以导出所有连接条件,然后将它们作为表达式添加到where原始 sql 方法的子句中,如上所述。更复杂但也可行的是读取数据库schema并使用此处定义的类似方法通过查找foreign keys.
使用关联:如果它们全部与has_many/关联,您可以使用“最重要”模型上的方法来has_one处理连接,如下所示:ActiveRecordjoinsinject
base_model = "Company".constantize
assocations = [:categories] # and so on
result = assocations.inject(base_model) { |model, assoc| model.joins(assoc) }.where(query_where)
Run Code Online (Sandbox Code Playgroud)
这是做什么的:
Company.send(:joins, :categories)`Company.categories免责声明您需要的确切语法可能会根据您使用的 SQL 实现而有所不同。
| 归档时间: |
|
| 查看次数: |
1430 次 |
| 最近记录: |