I've got to produce a json feed for an old mobile phone app and some of the labels need to be different from my database column names.
I think the most efficient way of doing this would be to do a create an alias at the database level. So I'm doing things like
Site.where( mobile_visible: true ).select("non_clashing_id AS clientID")
which produces the SQL
SELECT non_clashing_id AS clientID FROM `sites` WHERE `sites`.`mobile_visible` = 1 ORDER BY site_name
If I run this query in MYSQL workbench it produces a column with the heading ClientID as I expect, with the required values.
But if I show the object in a rails view I get {"clientID":null},{"clientID":null},{"clientID":null}
What am I doing wrong? Is there a better way of doing this?
This shows how to access the variable
sites = Site.where( mobile_visible: true ).select("non_clashing_id AS clientID")
sites.each do |site|
puts site.clientID
end
I think by default, activerecord loads column definitions from the database. And, it should load value into existing columns only.
Site.columns
I guess you could add one more item to that array. Or you could use the normal query without alias column name, then add alias_attribute like MurifoX did and overwrite as_json method:
class Site < ActiveRecord::Base
alias_attribute :client_id, :non_clashing_id
def as_json(options={})
options[:methods] = [:client_id]
options[:only] = [:client_id]
super
end
end
Try putting this in your model in addition to the database alias:
class model < ActiveRecord::Base
alias_attribute :non_clashing_id, :client_id
...
end
Related
I found a problem in one of legacy applications (outdated rails-3.0.20).
This application has lots of components and nested models. Problem existed only on one of production servers (same environments like other productions and mine dev).
There was model with name space which looks like
module Great
class Item
end
end
Table name was named great_items.
When i debug/open it on server with fault i've found that calculated table name was items istead of great_items.
$ Great::Item.all
#=> ActiveRecord::StatementInvalid: No attribute named `name` exists for table `items`
So i thought mby there is simmilar class with same namespace, I've checked it and it wasn't. My 2nd thought was to set table name explicit i tried
self.table_name = 'great_items'
# &
set_table_name 'great_items'
After this changes i run rails c and table name was setted fine:
$ Great::Item.table_name
#=> 'great_items'
But when i tried to obtain some items there was a freak error, which i could not understand till now!
$ Great::Item.all
#=> ActiveRecord::StatementInvalid: Mysql2::Error: Table 'db.items' doesn't exist: SELECT `great_items`.* FROM `items` WHERE `great_items`.`some_default_scope` = 0
As you can see in above example table has correct name for select values and in where statement, but in from value is incorrect.
I was curious so I've checked ActiveRecord::Base mysql adapter and there was some kind of catching table name so i tried to reset_table_name. Reset helped to setup expected name('great_items') but above errors didn't missed.
Problem dissapeared when i turn of class catching in production enviroment - but it wasn't solution.
Finally I kinda 'solved' this using reset_column_information after set_table_name, but i think it isn't good solution either.
My question is do you know what really could cause this issue and how to solve it without reloading class cache?
Assumed table names dont take into account the modules, as you noticed.
But as you already know, you can set it yourself, using:
self.table_name = 'great_items'
According to this doc, because you use 3.0.x, you have to use:
set_table_name "great_items"
This must be put on top of your class definition
Try this
class RenameOldTableToNewTable< ActiveRecord::Migration
def self.up
rename_table :old_table_name, :new_table_name
end
def self.down
rename_table :new_table_name, :old_table_name
end
end
I have these models:
teacher
class Teacher < ActiveRecord::Base
has_many :days
end
day
class Day < ActiveRecord::Base
belongs_to :teacher
end
And running these query:
active_teachers = Teacher.joins(:days).where("teacher.id" => found_teachers.pluck(:teacher_id).uniq, "days.day_name" => selected_day)
What the query (should) does: found_teachers is an array of all teachers with duplications, remove the duplicity and chose only those teachers that have classes on a respective day (selected_day contains a string, Monday for example).
Because the amount of data in the variable active_teachers is so big that I can't manually go record by record (and I am not sure that I built this query properly and it does exactly what I need), I am trying to find out how is this query translated to SQL from ActiveRecord.
Usually I see everything in the terminal where is running server for the Rails app, but as of now, I don't see there this query stated.
So the question is, how can I see how the ActiveRecord query is translated to SQL?
Thank you in advance.
To get details from a query you're typing, you can do:
query.to_sql
query.explain
You can use
ActiveRecord::Base.logger = Logger.new STDOUT
and run your query in rails console. So it prints out the sql queries in the console
Switching a rails app from MySQL to Postgres gives the following error:
ERROR: column "contacts.id" must appear in the GROUP BY clause or be used in an aggregate function
Here is the scope in question:
class User < MyModel
def self.top_contacts(timeframe = 1.week.ago, limit = 5)
Contact.unscoped
.where('created_at between ? and ?', timeframe, Time.now)
.group(:user_id)
.order('sum(score) DESC')
.limit(limit)
.includes(:user)
.collect{|x| x.user}
end
end
How to fix this?
Isn't using Rails as the database abstraction layer ensure switching the database should work seamlessly?
The problem is on the level of the SQL, which is invisible from your ORM layer. The problem is exactly with the RoR ORM, because it seems to generate a MySQL-friendly query which uses an extraordinary feature of the MySQL, which postgresql don't have.
The quick solution: give contacts.id to the columns by which you are GROUP-ing as well:
.group("user_id, contacts.id");
I am using this method for search engine friendly URLs in Rails - http://jroller.com/obie/entry/seo_optimization_of_urls_in
My model looks like this.
class Listing < ActiveRecord::Base
def to_param
"#{id}-#{title.parameterize}"
end
end
This works with MySQL but not Postgresql.
This is the error I get:
ActiveRecord::StatementInvalid (PGError: ERROR: invalid input syntax for integer: "16- college-station-apartments"
: SELECT * FROM "floorplans" WHERE ("floorplans"."listing_id" = E'16-college-station-apartments') ORDER BY beds ASC, baths ASC, footage ASC, price ASC):
Is ActiveRecord not doing a .to_i on the find for Postgresql?
Rails will automatically call to_i on your parameter for some methods, mainly those where an integer is expected as a parameter, like Listing.find(params[:id]).
However, for other types of search methods that can accept strings as parameters, you'll need to manually call to_i
Listing.find_by_id(params[:id].to_i)
Listing.find(:conditions => ["id = ?", params[:id].to_i])
The reason you're not having a problem with MySQL is that MySQL does what would in effect be a to_i on its end (i.e. it's not a database-adapter issue, but rather a capability of the actual database server).
I need to create an AR migration for a table of image files. The images are being checked into the source tree, and should act like attachment_fu files. That being the case, I'm creating a hierarchy for them under /public/system.
Because of the way attachment_fu generates links, I need to use the directory naming convention to insert primary key values. How do I override the auto-increment in MySQL as well as any Rails magic so that I can do something like this:
image = Image.create(:id => 42, :filename => "foo.jpg")
image.id #=> 42
Yikes, not a pleasant problem to have. The least-kludgy way I can think of to do it is to have some code in your migration that actually "uploads" all the files through attachment-fu, and therefore lets the plugin create the IDs and place the files.
Something like this:
Dir.glob("/images/to/import/*.{jpg,png,gif}").each do |path|
# simulate uploading the image
tempfile = Tempfile.new(path)
tempfile.set_encoding(Encoding::BINARY) if tempfile.respond_to?(:set_encoding)
tempfile.binmode
FileUtils.copy_file(path, tempfile.path)
# create as you do in the controller - may need other metadata here
image = Image.create({:uploaded_data => tempfile})
unless image.save
logger.info "Failed to save image #{path} in migration: #{image.errors.full_messages}"
end
tempfile.close!
end
A look at attachment-fu's tests might be useful.
Unlike, say Sybase, in MySQL if you specify the id column in the insert statement's column list, you can insert any valid, non-duplicate value in the id. No need to do something special.
I suspect the rails magic is just to not let rails know the id is auto-increment. If this is the only way you'll be inserting into this table, then don't make the id auto_increment. Just make in an int not null primary key.
Though frankly, this is using a key as data, and so it makes me uneasy. If attachment_fu is just looking for a column named "id", make a column named id that's really data, and make a column named "actual_id" the actual, synthetic, auto_incremented key.
image = Image.create(:filename => "foo.jpg") { |r| r.id = 42 }
Here's my kluge:
class AddImages < ActiveRecord::Migration
def self.up
Image.destroy_all
execute("ALTER TABLE images AUTO_INCREMENT = 1")
image = Image.create(:filename => "foo.jpg")
image.id #=> 1
end
def self.down
end
end
I'm not entirely sure I understand why you need to do this, but if you only need to do this a single time, for a migration, just use execute in the migration to set the ID (assuming it's not already taken, which I can't imagine it would be):
execute "INSERT INTO images (id, filename) VALUES (42, 'foo.jpg')"
I agree with AdminMyServer although I believe you can still perform this task on the object directly:
image = Image.new :filename => "foo.jpg"
image.id = 42
image.save
You'll also need to ensure your id auto-increment is updated at the end of the process to avoid clashes in the future.
newValue = Images.find(:first, :order => 'id DESC').id + 1
execute("ALTER TABLE images AUTO_INCREMENT = #{newValue}")
Hope this helps.