No autoincrement primary key ID in Rails Migration - ruby-on-rails-3

Flag -> This is a noob question!
In Rails Migration File is possible to create a column ID primary_key no auto_increment?

I succeeded dropping auto_increment by running following change_column method.
class DropAutoIncrement < ActiveRecord::Migration
def change
change_column :my_table, :id, :integer
end
end
I used activerecord 4.0.2.

Seems, that Yuki´s answer got too old?
It didn´t work for me.
But the following worked:
class CreateNoAutoincrement < ActiveRecord::Migration[4.2]
def up
create_table( :my_table, :id => false ) {|t|
t.integer :id, null: false ;
t.string :token, null: false ;
}
add_index :my_table, :id, unique: true ;
add_index :my_table, :token, unique: true ;
end
def down
drop_table :my_table ;
end
end
On MariaDB the table looks like this:
MariaDB [foo_devel]> describe my_table ;
+-------+--------------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+-------+--------------+------+-----+---------+-------+
| id | int(11) | NO | PRI | NULL | |
| token | varchar(255) | NO | UNI | NULL | |
+-------+--------------+------+-----+---------+-------+
2 rows in set (0.001 sec)
It´s nice, that I still get a primary key, not just two Unique Indexes
(probably because the column name is 'id')

Related

Select objects with negative conditions on association and groups associated

I guess I wasn't very clear in the title, but I find it hard to describe exactly what I'm after. So it's better to start with an example:
I have the following models:
class Song < ActiveRecord::Base
has_many :learnt_items, as: :learnable
has_many :trained_items, as: :learnable
end
class LearntItem < ActiveRecord::Base
belongs_to :learnable, polymorphic: true
end
class TrainedItem < LearntItem
end
If, for example, I select all songs that have trained items:
Song.joins(:trained_items)
I would see (roughly) records returned like:
learnable_type | learnable_id | type | label
Song | 1 | TrainedItem | happy
Song | 1 | TrainedItem | sad
Song | 1 | TrainedItem | lively
Song | 2 | TrainedItem | lively
If I would like to select all songs that have trained items with specific labels, I'd do:
Song.joins(:trained_items).where(learnt_items: { label: [:happy, :sad] })
Now, I need to get all songs that don't have trained items for given labels. One would think the following would suffice:
Song.joins(:trained_items).where.not(learnt_items: { label: [:happy, :sad] })
But this will still produce the following records:
learnable_type | learnable_id | type | label
Song | 1 | TrainedItem | lively
Song | 2 | TrainedItem | lively
which is indeed not what I intended. You can see that the query filtered out records with the given labels, but the one with label = 'lively' is still there, hence returning the song with id = 1. I would need only the song with id = 2 to be returned from this query.
How can I build a query with ActiveRecord so that my scenario is fulfilled?
Use a subquery to find the ids you don't want and use those in the where.not condition:
Song.joins(:trained_items)
.where.not(learnable_id: Song.select(:learnable_id).where(learnt_items: { label: [:happy, :sad] })

Single-table inheritance in Rails - How can I get a record within my table to have multiple sub-types while still using single-table inheritance?

I'm attempting to use single-table inheritance in Rails as a means of allowing a user have multiple user sub-types (e.g., faculty, vendor, etc.). I've ended up with a user table with records containing only a single user type. While still using single-table inheritance, how do I get my users to have multiple types? (I know this is essentially a many-to-many relationship; I'm just not sure of how to accomplish this using STI.)
id | first_name | last_name | birth_date | city | zip_code | email | type | created_at | updated_at
----+------------+-----------+------------+------+----------+-------+---------+----------------------------+----------------------------
1 | Akira | Yamaoka | | | | | Vendor | 2014-08-30 14:58:26.917333 | 2014-08-30 14:58:26.917333
2 | Pyramid | Head | | | | | Faculty | 2014-08-30 15:02:04.70209 | 2014-08-30 15:02:04.70209
Here are my models' classes:
user.rb
1 class User < ActiveRecord::Base
2 end
vendor.rb
1 class Vendor < User
2 belongs_to :user
3 belongs_to :event
4 end
faculty.rb
1 class Faculty < User
2 belongs_to :user
3 belongs_to :event
4
5 end
you could write something like:
class User < ActiveRecord::Base
belongs_to :event
end
class Vendor < User
end
class Faculty < User
end
then get records with different types through User model, like User.all
A short answer: no, you can't do that, type column can only hold one value.
A longer answer: a many-to-many relationship requires that an extra model exists (it has to, HABTM just won't do here) that contains references to a User and an Event. I called it a Pass in this answer to your another question.
You should subclass a Pass, not User. And since User has_many :passes, which can possibly be Pass subclasses, a User can participate in an event in multiple ways: as a vendor or as a faculty member.
I've supplied some example code here.

Simple relational query in Rails

Let's say I have the following tables:
product transaction
------------------ ------------------
| id | name | | id | product |
------------------ ------------------
| 1 | Product A | | 1 | 2 |
| 2 | Product B | | 2 | 3 |
| 3 | Product C | | 3 | 2 |
------------------ ------------------
Now, let's say I want to make a listing of the transaction table, but I want to display product names instead of product IDs.
In raw SQL, I would do something like this:
SELECT product.name FROM transaction, product WHERE transaction.product = product.id
I'm having a hard time figuring out how this would be done using Rails' Active Record Query Interface. I'm sure this is trivial to you Rails experts out there.
If you want to solve it the "Rails way":
first: change column product in transactions to product_id, and table names should be in plural, otherwise you must add e.g. set_table_name 'transaction' in the models, you will miss a lot of Rails' futures if you don't change it.
add/change these models:
class Product < ActiveRecord::Base
has_many :transactions
end
class Transaction < ActiveRecord::Base
belongs_to :product
end
If you persist on using column name "product" you must change the belongs_to to:
belongs_to :product, :foreign_key => 'product'
but that is kind of ugly
Finally, using this:
Transaction.includes(:product)
Or if you want only those who has a product
Transaction.joins(:product)
Note:
I don't remember exactly, but I think I had problems with using "transactions" as table name in some application. Somebody else perhaps knows about that.
transaction.joins('LEFT OUTER JOIN product ON transaction.id = product.id')

Efficient sum / group by query in Rails using SQLite

I'm using SQLite3 and would like to get totals by month for a numeric field. Models look like:
# Table name: accounts
# id :integer not null, primary key
# created_at :datetime
# updated_at :datetime
class Account < ActiveRecord::Base
has_many :debitentries, :class_name => "Posting", :foreign_key => "debitaccount_id"
has_many :creditentries, :class_name => "Posting", :foreign_key => "creditaccount_id"
# Table name: postings
# id :integer not null, primary key
# voucherdate :date
# debitaccount_id :integer
# creditaccount_id :integer
# euroamount :decimal(, )
Class Postings < ActiveRecord::Base
belongs_to :debitaccount, :class_name => "Account", :foreign_key => "debitaccount_id"
belongs_to :creditaccount, :class_name => "Account", :foreign_key => "creditaccount_id"
My objective is to query the postings where the voucherdate < 12 month and get a total euroamount-sum of debitentries/creditentries for each account number, i.e.
Account.id| Feb 2012 | Jan 2012 | Dec 2011 | ... | Mar 2011
------------------------------------------------------------
1 | 233.87 | 123.72 | ... | | sum(euroamount)
2 | ... | | | |
I think I will need two queries (one for sume of debitentries and one for creditentries), but I think it will be much more efficient than using rails-functions. Can anyone help me how such a query would have to look like?
Many thanks in advance!
Here's SQL to get credit and debit sums by month for each account:
Select accounts.id,
strftime('%B %Y', voucherdate) month,
sum(credits.euroamount) total_credits,
sum(debits.euroamount) total_debits
from accounts
join postings as credits
on accounts.id = creditaccount_id
join postings as debits
on accounts.id = debitaccount_id
group by accounts.id, strftime('%B %Y', voucherdate)
The result will look like this:
id | month | total_credits | total_debits
-------------------------------------------------------
1 | January 2011 | 243.12 | 123.12
1 | February 2011 | 140.29 | 742.22
1 | March 2011 | 673.19 | 238.11
2 | January 2011 | 472.23 | 132.14
2 | February 2011 | 365.34 | 439.99
To execute arbitrary sql in rails:
account_id_hash = ActiveRecord::Base.connection.select_all("Select accounts.id from accounts")
From here you'll need to cross-tab or pivot the data into the format you need using old-fashioned ruby code. If you need help with that feel free to post a new question.

Rails 3 app with PostgreSQL - Getting the list of messages grouped by converstation

I am running a rails 3 app that models email messages. The app is deployed on on Heroku so the backend db is PostgreSQL. Messages threads are simple modeled by the thread_id field in the Posts table. When the user post a new Post, let's call it p1 then p1.thread_id=p1.id. If the user reply to p1 with p2 then p2.thread_id=p1.thread_id.
I need to write a query to select the messages sent to a certain user. The resulting list must contain only one message per thread and that message must be the latest message in the thread. I also need to know how many messages are in each thread.
Using the following select:
SELECT DISTINCT ON(thread_id) * FROM "posts"
does not work as it does not returns the post sorted by last first.
This one does not work either:
SELECT DISTINCT ON(thread_id) * FROM "posts" ORDER BY thread_id, posts.created_at DESC
as the posts are ordered first by thread_id.
Posts table:
create_table "posts", :force => true do |t|
t.string "title"
t.text "content"
t.string "content_type"
t.text "options"
t.string "type"
t.integer "receiver_id"
t.integer "sender_id"
t.integer "circle_id"
t.text "data_bag"
t.datetime "created_at"
t.datetime "updated_at"
t.integer "parent_id"
t.integer "thread_id"
t.datetime "posted_at"
end
Thanks for any help.
If you don't mind getting your hands dirty with a bit of SQL you can use a window function to get the job done. You can get the post IDs with this SQL:
select id
from (
select id,
rank() over (partition by thread_id order by created_at desc)
from posts
where receiver_id = #{user.id}
) as dt
where rank = 1
If you want more columns add them to both SELECT clauses. The #{user.id} is, of course, the recipient that you're interested in.
The interesting part is the window function:
rank() over (partition by thread_id order by created_at desc)
This will partition the table into groups based on thread_id (sort of a localized GROUP BY), order them by the timestamp (most-recent first), and then rank() yields 1 for the first entry in each group, 2 for the second, etc.
Given a table that looks like this:
=> select * from posts;
id | receiver_id | thread_id | created_at
----+-------------+-----------+---------------------
1 | 1 | 2 | 2011-01-01 00:00:00
2 | 1 | 2 | 2011-02-01 00:00:00
3 | 1 | 2 | 2011-03-01 00:00:00
4 | 1 | 3 | 2011-01-01 00:00:00
5 | 1 | 4 | 2011-01-01 00:00:00
6 | 1 | 3 | 2011-01-01 13:00:00
7 | 2 | 11 | 2011-06-06 11:23:42
(7 rows)
The inner query gives you this:
=> select id, rank() over (partition by thread_id order by created_at desc)
from posts
where receiver_id = 1;
id | rank
----+------
3 | 1
2 | 2
1 | 3
6 | 1
4 | 2
5 | 1
(6 rows)
And then we wrap the outer query around that to peel off just the top ranking matches:
=> select id
from (
select id,
rank() over (partition by thread_id order by created_at desc)
from posts
where receiver_id = 1
) as dt
where rank = 1;
id
----
3
6
5
(3 rows)
So add the extra columns you want and wrap it all up in a Post.find_by_sql and you're done.