set_table_name only works once? - ruby-on-rails

I'm trying to use set_table_name to use one generic model on a couple different tables. However, it seems as though set_table name only works on the class once per application session. For instance in a rails 3 console (ruby 1.8.7) the following happens:
GenericModel.set_table_name "table_a"
puts GenericModel.table_name # prints table_a
pp GenericModel.column_names # prints the columns associated with table_a
GenericModel.set_table_name "table_b"
puts GenericModel.table_name # prints table_b
pp GenericModel.column_names # still prints the columns associated with table_a
Currently the workaround I've found is to also add .from(table_b) so that queries don't error out with 'table_b.id doesn't exist!' because the query still thinks it's FROM table_a.
Can others reproduce the issue? Is this the intended behaviour of set_table_name?
UPDATE
Adding
Model.reset_column_information
after set_table_name forces the model to work as I expect.
Reference found in http://ar.rubyonrails.org/classes/ActiveRecord/Base.html#M000368

This is probably an undocumented limitation. Once the SHOW FIELDS FROM has been executed, which is where the results from column_names comes from, it is usually cached, at least for the duration of the request. If you must, try using the console reload! method to reset things.

your choice
rename_table
more info at AR TableDefinition

Related

Rails Ruby easiest way to get table names of my database [duplicate]

How do I get a list of all the tables defined for the database when using active record?
Call ActiveRecord::ConnectionAdapters::SchemaStatements#tables. This method is undocumented in the MySQL adapter, but is documented in the PostgreSQL adapter. SQLite/SQLite3 also has the method implemented, but undocumented.
>> ActiveRecord::Base.connection.tables
=> ["accounts", "assets", ...]
See activerecord/lib/active_record/connection_adapters/abstract/schema_statements.rb:21, as well as the implementations here:
activerecord/lib/active_record/connection_adapters/mysql_adapter.rb:412
activerecord/lib/active_record/connection_adapters/postgresql_adapter.rb:615
activerecord/lib/active_record/connection_adapters/sqlite_adapter.rb:176
Based on the two previous answers, you could do:
ActiveRecord::Base.connection.tables.each do |table|
next if table.match(/\Aschema_migrations\Z/)
klass = table.singularize.camelize.constantize
puts "#{klass.name} has #{klass.count} records"
end
to list every model that abstracts a table, with the number of records.
An update for Rails 5.2
For Rails 5.2 you can also use ApplicationRecord to get an Array with your table' names. Just, as imechemi mentioned, be aware that this method will also return ar_internal_metadata and schema_migrations in that array.
ApplicationRecord.connection.tables
Keep in mind that you can remove ar_internal_metadata and schema_migrations from the array by calling:
ApplicationRecord.connection.tables - %w[ar_internal_metadata schema_migrations]
It seems like there should be a better way, but here is how I solved my problem:
Dir["app/models/*.rb"].each do |file_path|
require file_path # Make sure that the model has been loaded.
basename = File.basename(file_path, File.extname(file_path))
clazz = basename.camelize.constantize
clazz.find(:all).each do |rec|
# Important code here...
end
end
This code assumes that you are following the standard model naming conventions for classes and source code files.
Don't know about active record, but here's a simple query:
select table_name
from INFORMATION_SCHEMA.Tables
where TABLE_TYPE = 'BASE TABLE'

ActiveRecord: update_all shows query in debug log, but doesn't execute in DB?

I've got the following update query running in a function called by a before_destroy callback in a Rails model:
Annotation.joins(:annotation_groups)
.where({'annotation_groups.group_id' => self.id})
.update_all({qc_approved: false}) if doc.in_qc?`
(I've also tried the following simpler version to see if another angle works: self.annotations.update_all({qc_approved: false}))
Both generate the below SQL query in "Server development log" (debugging in RubyMine):
UPDATE "annotations" SET "qc_approved" = 'f' WHERE "annotations"."id" IN (SELECT "annotations"."id" FROM "annotations" INNER JOIN "annotation_groups" ON "annotation_groups"."annotation_id" = "annotations"."id" WHERE "annotation_groups"."group_id" = 159)
However, as far as I can tell, that SQL never causes a DB update, even though the destroy process afterwards works fine. I can set a breakpoint directly after the statement and look at the database, and the qc_approved fields are still true. However, I can copy and paste the statement into a Postgres console and run it, and it updates the fields correctly.
Is anyone aware as to what would cause this behavior? Does before_destroy exist in its own strange alternate transactional universe that causes odd behavior like this? What scenario would cause the SQL to show up in the server log but not make it to the DB?
Thanks to the quick and helpful comments above confirming the nature of the callback inside the larger transaction, I figured it out.. despite the name, before_destroy was actually executing after dependent destroy calls, so that the joined annotation_group table row was destroyed before the UPDATE statement that relied on it was called in the transaction.
To be more specific, I added :prepend => true to the before_destroy definition so that it ran before the destroys as intended.

Active record create query in multiple steps

I'm a bit confused by active record, it just seems to fire the query at any time you stop, ie.
#model.where( :store_id => #sid )
Which is fine, but what if I want to build a query like this:
query = #model.where( :store_id => #sid )
if(some_condition)
query.offset(50)
and then execute the query (not actually what I'm doing but a very simple example). Is there a way to put together the query in steps and then tell it to execute?
Actually, ActiveRecord will do exactly what you want. It's called lazy loading. You might be getting confused by the rails console, which calls .inspect behinds the scenes on the result of the line.
Check out this question: Lazy loading in Rails 3.2.6
This already works like you want it too.
where() returns an instance of ActiveRecord::Relation.
The relation won't execute it's database call until it needs to. The reason you might be experiencing otherwise is that you're testing it in the console, which prints the output of each statement (thus loading the relation). You can test whether a relation has been loaded via the loaded() method.
Try this on the console:
m = #model.where(:store_id => #sid); # the semicolon will silence the output
m.loaded? # nil
m # executes db call, will print out the contents of the relation
m.loaded? # true

Rails active record querying association with 'exists'

I am working on an app that allows Members to take a survey (Member has a one to many relationship with Response). Response holds the member_id, question_id, and their answer.
The survey is submitted all or nothing, so if there are any records in the Response table for that Member they have completed the survey.
My question is, how do I re-write the query below so that it actually works? In SQL this would be a prime candidate for the EXISTS keyword.
def surveys_completed
members.where(responses: !nil ).count
end
You can use includes and then test if the related response(s) exists like this:
def surveys_completed
members.includes(:responses).where('responses.id IS NOT NULL')
end
Here is an alternative, with joins:
def surveys_completed
members.joins(:responses)
end
The solution using Rails 4:
def surveys_completed
members.includes(:responses).where.not(responses: { id: nil })
end
Alternative solution using activerecord_where_assoc:
This gem does exactly what is asked here: use EXISTS to to do a condition.
It works with Rails 4.1 to the most recent.
members.where_assoc_exists(:responses)
It can also do much more!
Similar questions:
How to query a model based on attribute of another model which belongs to the first model?
association named not found perhaps misspelled issue in rails association
Rails 3, has_one / has_many with lambda condition
Rails 4 scope to find parents with no children
Join multiple tables with active records
You can use SQL EXISTS keyword in elegant Rails-ish manner using Where Exists gem:
members.where_exists(:responses).count
Of course you can use raw SQL as well:
members.where("EXISTS" \
"(SELECT 1 FROM responses WHERE responses.member_id = members.id)").
count
You can also use a subquery:
members.where(id: Response.select(:member_id))
In comparison to something with includes it will not load the associated models (which is a performance benefit if you do not need them).
If you are on Rails 5 and above you should use left_joins. Otherwise a manual "LEFT OUTER JOINS" will also work. This is more performant than using includes mentioned in https://stackoverflow.com/a/18234998/3788753. includes will attempt to load the related objects into memory, whereas left_joins will build a "LEFT OUTER JOINS" query.
def surveys_completed
members.left_joins(:responses).where.not(responses: { id: nil })
end
Even if there are no related records (like the query above where you are finding by nil) includes still uses more memory. In my testing I found includes uses ~33x more memory on Rails 5.2.1. On Rails 4.2.x it was ~44x more memory compared to doing the joins manually.
See this gist for the test:
https://gist.github.com/johnathanludwig/96fc33fc135ee558e0f09fb23a8cf3f1
where.missing (Rails 6.1+)
Rails 6.1 introduces a new way to check for the absence of an association - where.missing.
Please, have a look at the following code snippet:
# Before:
Post.left_joins(:author).where(authors: { id: nil })
# After:
Post.where.missing(:author)
And this is an example of SQL query that is used under the hood:
Post.where.missing(:author)
# SELECT "posts".* FROM "posts"
# LEFT OUTER JOIN "authors" ON "authors"."id" = "posts"."author_id"
# WHERE "authors"."id" IS NULL
As a result, your particular case can be rewritten as follows:
def surveys_completed
members.where.missing(:response).count
end
Thanks.
Sources:
where.missing official docs.
Pull request.
Article from the Saeloun blog.
Notes:
where.associated - a counterpart for checking for the presence of an association is also available starting from Rails 7.
See offical docs and this answer.

Rails: oracle set_sequence_name being ignored

I have a simple model which I have to set the database name manually on.
Also since it is using an oracle database, I'm setting the sequence name so I can have auto incrementing id's.
When I run the rails console and try to create my model, it comes back and says that the sequence cannot be found. The weird part is the sequence it cannot find is not the sequence that I set in set_sequence_name.
Model
class Survey < ActiveRecord::Base
set_sequence_name "SURVEY.SQ_SURVEY_ID"
set_table_name "SURVEY.SURVEYS"
end
Console error
ActiveRecord::StatementInvalid: ActiveRecord::JDBCError: ORA-02289:
sequence does not exist: select SURVEY.SURVEYS_seq.nextval id from dual
It looks like its ignoring my set sequence name line.
Am I just missing something?
FWIW:
Using 11g I got away with:
self.id = ActiveRecord::Base.connection.execute("select SURVEY.SQ_SURVEY_ID.nextval id from dual").fetch
It appears that in my case, the sequence returns a cursor on which I need to do a fetch on.
11g/Rails 3.1
Clarifying, this works for oracle 10g
So as far as I can tell, this is a bug in the jdbc adapter (see here http://kenai.com/jira/browse/ACTIVERECORD_JDBC-133). For a work around I'm setting the id manually with a before create filter like this:
class Survey < ActiveRecord::Base
set_table_name "SURVEY.SURVEYS"
before_create do
#since we can't use the normal set sequence name we have to set the primary key manually
#so the execute command return an array of hashes,
#so we grab the first one and get the nextval column from it and set it on id
self.id = ActiveRecord::Base.connection.execute("select SURVEY.SQ_SURVEY_ID.nextval id from dual")[0]["id"]
end
end

Resources