I want to use constant of an array in SQL heredoc like:
ROLES = ['admin', 'staff', 'guest']
query = <<-SQL
SELECT * FROM users
WHERE role IN (#{ROLES})
SQL
ActiveRecord::Base.connection.execute(query)
but getting syntax error
ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near "["
LINE 2: WHERE role IN (["admin", "staff", "guest"])
^
Instead of building your own Ruby to SQL converter you could also make use of the arel gem which already does the necessary escaping and quoting.
users = User.arel_table
ApplicationRecord.connection.execute(<<~SQL.squish)
SELECT *
FROM users
WHERE #{users[:role].in(ROLES).to_sql}
SQL
Or if you'd like to go full arel.
ApplicationRecord.connection.execute(
users.project(users[Arel.star])
.where(users[:role].in(ROLES))
.to_sql
)
Instead of passing the result of ROLES.inspect, you should make the string with roles yourself, I would use map and join for that purpose:
query = <<-SQL
SELECT * FROM users
WHERE role IN (#{ROLES.map { |role| "'#{role}'" }.join(',')})
SQL
ApplicationRecord.connection.execute(query)
Related
Following is my setups:
Ruby version: 3.0.1
Rails Version: 6.1.3.2
postgres (PostgreSQL) 9.6.17
I am using gem ros-apartment (2.9.0) to manage tenants.
This is what I have in my apartment.rb
config.tenant_names = -> { Tenant.pluck :app_name }
Problem statement: I have a set of data across tables in the admin tenant. The schema name for the admin tenant is pq-admin. Now whenever a new tenant is created, I need to take all data from selected tables from pq-admin schema and then put in the newly created tenant with schema (say test-tenant1).
To achieve this, the following is what I have written at the tenant.rb
class Tenant < ApplicationRecord
after_create :create_apartment_tenant, :populate_data
after_destroy :delete_apartment_tenant
private
def create_apartment_tenant
Apartment::Tenant.create(app_name)
end
def delete_apartment_tenant
Apartment::Tenant.drop(app_name)
end
def populate_data
%w[
pc_core_packages
pc_core_preorders
pc_core_delivery_times
pc_core_dynamic_columns
pc_core_product_items
pc_core_dynamic_options
pc_core_dynamic_values
pc_core_specifications
pc_core_images
pc_core_prices
pc_core_product_trees
pc_core_read_models_product_trees
pc_core_read_models_product_items
].each do |table|
query = "INSERT INTO #{app_name}.#{table} SELECT * FROM #{ENV['ADMIN_TENANT']}.#{table};"
ActiveRecord::Base.connection.exec_query(query)
# ENV['ADMIN_TENANT'] = 'pq-admin'
# the generated query = "INSERT INTO test-tenant1.pc_core_packages SELECT * FROM pq-admin.pc_core_packages;"
end
end
end
The Following is the error I encountered. Need help to resolve above described problem.
SQL (0.6ms) INSERT INTO test-tenant1.pc_core_packages SELECT * FROM pq-admin.pc_core_packages;
ActiveRecord::StatementInvalid: PG::SyntaxError: ERROR: syntax error at or near "-"
LINE 1: INSERT INTO test-tenant1.pc_core_packages SELECT * FROM pq-...
^
from /Users/bhagawatadhikari/.rvm/gems/ruby-3.0.1/gems/activerecord-6.1.3.2/lib/active_record/connection_adapters/postgresql_adapter.rb:672:in `exec_params'
Caused by PG::SyntaxError: ERROR: syntax error at or near "-"
LINE 1: INSERT INTO test-tenant1.pc_core_packages SELECT * FROM pq-...
^
from /Users/bhagawatadhikari/.rvm/gems/ruby-3.0.1/gems/activerecord-6.1.3.2/lib/active_record/connection_adapters/postgresql_adapter.rb:672:in `exec_params'
Need to delimit the identifier when including a dash (hyphen) in a schema or table name. Updating your query to...
"INSERT INTO \"#{app_name}.#{table}" SELECT * FROM \"#{ENV['ADMIN_TENANT']}.#{table}\";"
Should do the trick.
See... what's the escape sequence for hyphen (-) in PostgreSQL
Though it might be a better practice to just exclude the hyphen from names to begin with.
I have created below join query to fetch group name details.I am using below query in postgresql.
select DISTINCT e.group_id,e.groupname from user_group_table u, execution_groups e where (u.user_id=e.user_id or u.group_id=e.group_id) and u.user_id=12;
How to write above join query using "options_from_collection_for_select" in rails?
I have tried below code
user_groups = UserGroupTable.where(:user_id => id)
#execution_group_options = ExecutionGroup.where(user_id: user_groups.select(:user_id)).or(ExecutionGroup.where(group_id: user_groups.select(:group_id)))
Getting below error
SELECT "execution_groups".* FROM "execution_groups" WHERE "execution_groups"."user_id" IN (SELECT "user_group_table"."user_id" FROM "user_group_table" WHERE "user_group_table"."user_id" = ?) [["user_id", 6]]
"Threw an exception inside test.new() undefined method `or' for #<ExecutionGroup::ActiveRecord_Relation:0x00007faf9e10ee38>"
Threw an exception inside test.new() undefined method `or' for #<ExecutionGroup::ActiveRecord_Relation:0x00007faf9e10ee38>
Let's start of by writing that query in the Rails query syntax.
I'll assume that user_group_table is available through the UserGroup model and execution_groups is available through the ExecutionGroup model.
# in controller
user_groups = UserGroup.where(user_id: 12)
#execution_group_options =
ExecutionGroup.where(user_id: user_groups.select(:user_id))
.or(ExecutionGroup.where(group_id: user_groups.select(:group_id)))
This produces the following SQL:
SELECT execution_groups.*
FROM execution_groups
WHERE
execution_groups.user_id IN (
SELECT user_group_table.user_id
FROM user_group_table
WHERE user_group_table.user_id = 12
)
OR
execution_groups.group_id IN (
SELECT user_group_table.group_id
FROM user_group_table
WHERE user_group_table.user_id = 12
)
Which should produce the same collection (assuming there are no ExecutionGroup records that have the same group_id and groupname).
With the above in place you can use the following in your view:
options_from_collection_for_select(#execution_group_options, 'group_id', 'groupname')
If there are ExecutionGroup records with both the same group_id and groupname. You can instead use:
options_for_select(#execution_group_options.distinct.pluck(:groupname, :group_id))
If you don't have access to or (introduced in Ruby on Rails 5), things become slightly more complicated. You should be able to use the internal arel interface.
# in controller
user_groups = UserGroup.where(user_id: 12)
exec_groups_table = ExecutionGroup.arel_table
#execution_group_options = ExecutionGroup.where(
exec_groups_table[:user_id].in(user_groups.select(:user_id).arel)
.or(exec_groups_table[:group_id].in(user_groups.select(:group_id).arel))
)
This should produce the exact same query. Other than the controller code nothing changes.
I have a jsonb column in my postgres performances table called authorization where I store the uuid of a user as a key and their authorization level as the value e.g.
{ 'sf4wfw4fw4fwf4f': 'owner', 'ujdtud5vd9': 'editor' }
I use the below Rails query in my Performance model to search for all records where the user is an owner:
class Performance < ApplicationRecord
def self.performing_or_owned_by(account)
left_outer_joins(:artists)
.where(artists: { id: account } )
.or(Performance.left_outer_joins(:artists)
# this is where the error happens
.where("authorization #> ?", { account => "owner" }.to_json)
).order('lower(duration) DESC')
.uniq
end
end
Where account is the account uuid of the user. However, when I run the query I get the following error:
ActiveRecord::StatementInvalid (PG::SyntaxError: ERROR: syntax error at or near "#>")
LINE 1: ..._id" WHERE ("artists"."id" = $1 OR (authorization #> '{"28b5...
The generated SQL is:
SELECT "performances".* FROM "performances"
LEFT OUTER JOIN "artist_performances" ON "artist_performances"."performance_id" = "performances"."id"
LEFT OUTER JOIN "artists" ON "artists"."id" = "artist_performances"."artist_id" WHERE ("artists"."id" = $1 OR (authorization #> '{"28b5fc7f-3a31-473e-93d4-b36f3b913269":"owner"}'))
ORDER BY lower(duration) DESC
I tried several things but keep getting the same error. Where am I going wrong?
The solution as per comment in the original question is to wrap the authorization in double-quotes. Eg:
.where('"authorization" #> ?', { account => "owner" }.to_json)
The ->> operator gets a JSON object field as text.
So it looks you need this query:
left_outer_joins(:artists).
where("artists.id = ? OR authorization ->> ? = 'owner'", account, account).
order('lower(duration) DESC').
uniq
DB - PostgreSQL
Rails - 4
I have next SQL
sql = <<-SQL
LEFT OUTER JOIN (
SELECT SUM(id) AS sum_ids, some_key FROM second_models
WHERE id < 10000
GROUP BY some_key
) AS second_models ON first_models.id = second_models.first_model_id
SQL
record = FModel.joins(sql).last
record.sum_ids # DOESN'T WORK !
and I can see the record as ActiveRecord object, but can I get somehow field sum_ids which was built manually?
The additional field is inside the join section. It is not selected by default and thus can't be read. When executing your statement you get something like the following SQL query:
SELECT first_models.*
FROM first_models
INNER JOIN (
SELECT SUM(id) AS sum_ids, some_key
FROM second_models
WHERE id < 10000
GROUP BY some_key
) AS second_models
ON first_models.id = second_models.first_model_id
The first select statement prevents the sum_ids field from being accessible in your object since it's never returned to Rails. You want to change SELECT first_models.* to SELECT *. This is simply done by specifying the following select:
record = FModel.select(Arel.star).joins(sql).last
record.sum_ids
#=> should now give you your value
You can also add your field specifically using the following method:
f_models = FModel.arel_table
record = FModel.select(f_models[Arel.star]).select('sum_ids').joins(sql).last
This should result in SELECT first_models.*, sum_ids.
Trying to get the following SQL to work in a Rails query.
The Query:
select addr.*
from addresses addr
join users u
on addr.addressable_type = 'User'
and addr.addressable_id = u.id
join customers c
on c.id = u.actable_id
and u.actable_type = 'Customer'
where c.account_id = 1
and c.site_contact = 't'
This is my Rails code:
# Inside my account.rb model
def site_addresses
a = Address.arel_table #Arel::Table.new(:addresses)
u = User.arel_table #Arel::Table.new(:users)
c = Customer.arel_table #Arel::Table.new(:customers)
# trying to debug/test by rendering the sql. Eventually, I want
# to return a relation array of addresses.
sql = Address.
joins(u).
on(a[:addressable_type].eq("User").and(a[:addressable_id].eq(u[:id]))).
joins(c).
on(c[:id].eq(u[:actable_id]).and(u[:actable_type].eq("Customer"))).
where(c[:account_id].eq(self.id).and(c[:site_contact].eq(true))).to_sql
raise sql.to_yaml #trying to debug, I'll remove this later
end
end
I'm getting errors like "unknown class: Arel::Table". Im not using Arel correctly because the SQL code is valid (I can run it on the database just fine)
Try the following:
a.join(u).on(a[:addressable_type].eq("User")... # Using the arel_table and "join" instead
I based my answer from the docs:
users.join(photos).on(users[:id].eq(photos[:user_id]))