IHP - Best way to combine and sort data from two tables into a single view? - ihp

I have two tables:
Table: Posts
Columns: id, title, body, author_id, created_at
Table: Comments
Columns: id, post_id, body, author_id, created_at
I have the following action:
action MyAction:
posts <- query #Post |> fetch
comments <- query #Comment |> fetch
I want to fetch all the posts and comments together, combine and order them by created_at in a single view. What's the best way to do it in IHP inherently?

You can use collectionFetchRelated for that:
posts <-
query #Post
|> fetch
>>= pure . map (modify #comments (orderBy #createdAt))
>>= collectionFetchRelated #comments
You can find this code in the IHP documentation here.

EDIT: See Marc's answer: collectionFetchRelated is the correct function here
For a has many relationship such as this, the comments for a post can be queried using fetchRelated #comments. To do this for each post we can use mapM as follows.
postsWithComments <- query #Post
|> fetch
>>= mapM (fetchRelated #comments)
To order by created_at, use the orderByDesc function for the QueryBuilder. We can apply this to the top level query directly, then modify the internal #comments query building using modify.
postsWithComments <- query #Post
|> orderByDesc #createdAt
|> fetch
>>= mapM (fetchRelated #comments . modify #comments (orderByDesc #createdAt))
See the IHP "relationships" docs for more info.

Related

Order comments from certain users first, then by comment score

In a Rails 4 project I have articles which has_many comments.
I’m trying to show a list of comments under an article ordered by the comment score (an integer column in the comment table), but where comments from a small number of users are shown first (eg. admins & mods). These users would be passed as an array of user_ids to the query (user_id is also a column in the comment table):
class Comment < ActiveRecord::Base
# article_id :integer
# user_id :integer
# score :integer
belongs_to :article
belongs_to :user
scope :by_users, -> (user_ids) { order("user_id IN (?), score DESC", user_ids) }
Thus:
some_article.comments.by_users([1,2,3])
This scope gives a syntax error, and I can’t quite work out a query that orders comments so that ALL an article’s comments are returned, shown first from users with a user_id in the passed array, then secondarily ordered by comment score.
Update:
mahemoff’s great conditional idea doesn’t work for Postgres, but lead to this non-working attempt:
scope :by_users, -> (user_ids) { order("case when (user_id IN (?)) then 0 else 1 end, score DESC", user_ids) }
Which gives me a PostgreSQL syntax error relating to the placeholder ? that I haven’t been able to fix.
With mahemoff & mu is too short’s help and suggestions, this is my working scope:
scope :by_users, -> (user_ids) { order([sanitize_sql("case when (user_id IN (#{user_ids.join(',')})) then 0 else 1 end"), "score DESC" ]) }
It’s somewhat imperfect, as Rails’ order apparently doesn’t support the ? placeholder, so instead I’ve used string interpolation and (even though user_ids isn’t user exposed) sanitized the sql.
...a frustrating problem with this though, is that it’s impossible to use .last to retrieve the final record. some_article.by_users[1,2].last now throws an error as AR can’t work out where to reverse the ordering:
ORDER BY case when (user_id IN (1 DESC, 2)) then 0 else 1 end DESC, score ASC LIMIT 1
sigh...
Try this:
class Comment < ActiveRecord::Base
scope :by_users, -> (user_ids) { order("if(user_id IN (#{ids.join ','}), 0, 1), score DESC") }

How to check if record does NOT exist in Rails 5 with active record querying?

I have an article model and comments model. How do i get a list of articles that does not have any comments using active record?
Model Columns:
Article: body:string (has many comments)
Comment: body:string, article_id:integer (belongs to article)
If you want to get the result using single query and want the result to be an activerecord relation, use:
Article.where('id NOT IN (SELECT DISTINCT(article_id) FROM comments)')
This is same but would be more rails way
Article.where.not('id IN (SELECT DISTINCT(article_id) FROM comments)')
try below code to fetch all articles with no comments:
Article.includes(:comments).where.not(comments: {article_id: nil})
OR
data = []
Article.all.each do |a|
data << a if a.comments.blank?
end
puts data
OR
ids = Comment.all.pluck(:article_id)
data = Article.where.not(id: ids)

Rails SQL Where Count

I have a Posts Table and A Sec_photo table :
class Post < ActiveRecord::Base
has_many :sec_photos
I am trying to do an advanced search form where it finds posts based on their sum of sec_photos :
#posts = #posts.where(:sec_photos.count > 2) is failing and I have looked online and attempted many other solutions but none seem to work.
Does anyone have any ideas?
Ps: It's a necessity for the line to be in the form #posts = #posts.where as that coincides with other conditions.
The advanced search from searches for other fields like category_id and location_id as such
#posts = Post.all
#posts = #posts.where('category_id = ?', #advanced_search.category_search) if #advanced_search.category_search.present?
#posts = #posts.where('location_id = ?', #advanced_search.location_search) if #advanced_search.location_search.present?
#posts = #posts.where("strftime('%m %Y', created_at) = ?", #advanced_search.newdate_search.strftime('%m %Y')) if #advanced_search.newdate_search.present?
The last option would be to show posts with sec_photos either more than 2 or less than 2
You can do as following:
#posts = Post.whatever_scopes_you_use
#posts = #posts.joins(:sec_photos).group('posts.id').having('COUNT(sec_photos.id)
> 2')
This last line will select all posts having strictly more than 2 sec_photos associated. Of course, you can easily make a scope from this, accepting a count variable to make it dynamic ;-)
From my previous answer: How to return results filtered on relations count to a view in RAILS?

Rails .where(.... and ....) Searching for two values of the same attribute

I've tried
#users = User.where(name: #request.requester or #request.regional_sales_mgr)
and
#users = User.where(name: #request.requester).where(name: #request.regional_sales_mgr).all
This doesn't seem to work. What I want is to find the user whose name matches #request.requester, and the user whose name matches #request.regional_sales_mgr, and save them both into the variable #users.
In the general case "OR" queries can be written as:
User.where("users.name = ? OR users.name = ?", request.requester, request.regional_sales_mgr)
Note: Rails 5 will support OR using:
User.where(name: request.requester).or(User.where(name: request.regional_sales_mgr))
For this specific case as state in other answers an IN query is simpler:
User.where(name: [request.requester, request.regional_sales_mgr])
You want to use the SQL IN clause. Activerecord provides a shortcut to this:
#users = User.where(name: [#request.requester, #request.regional_sales_mgr]).all
Giving an array of values to name: will generate the following SQL statement:
SELECT * FROM users WHERE name IN (value1, value2, and so on...);
This should find all the users whose names are #request.requester or #request.regional_sales_mgr
Diego's answer should solve Sabrams' question without the all in rails 4.x (query for user with a name of x or y)
#users = User.where(name: [#request.requester, #request.regional_sales_mgr])
For those who find this post like I did actually looking for a rails all, you can chain where's. For instance to only get users that have both of two skills through a join table. (This will returns users with both x and y skill, will not return user with only x)
#users = User.joins(:skillable_joins).where(skillable_joins: { skill_id: 1 }).where(skillable_joins: { skill_id: 2 })

Query: getting the last record for each member

Given a table ("Table") as follows (sorry about the CSV style since I don't know how to make it look like a table with the Stack Overflow editor):
id,member,data,start,end
1,001,abc,12/1/2012,12/31/2999
2,001,def,1/1/2009,11/30/2012
3,002,ghi,1/1/2009,12/31/2999
4,003,jkl,1/1/2012,10/31/2012
5,003,mno,8/1/2011,12/31/2011
If using Ruby Sequel, how should I write my query so I will get the following dataset in return.
id,member,data,start,end
1,001,abc,12/1/2012,12/31/2999
3,002,ghi,1/1/2009,12/31/2999
4,003,jkl,1/1/2012,10/31/2012
I get the most current (largest end date value) record for EACH (distinct) member from the original table.
I can get the answer if I convert the table to an Array, but I am looking for a solution in SQL or Ruby Sequel query, if possible. Thank you.
Extra credit: The title of this post is lame...but I can't come up with a good one. Please offer a better title if you have one. Thank you.
The Sequel version of this is a bit scary. The best I can figure out is to use a subselect and, because you need to join the table and the subselect on two columns, a "join block" as described in Querying in Sequel. Here's a modified version of Knut's program above:
require 'csv'
require 'sequel'
# Create Test data
DB = Sequel.sqlite()
DB.create_table(:mytable){
field :id
String :member
String :data
String :start # Treat as string to keep it simple
String :end # Ditto
}
CSV.parse(<<xx
1,"001","abc","2012-12-01","2999-12-31"
2,"001","def","2009-01-01","2012-11-30"
3,"002","ghi","2009-01-01","2999-12-31"
4,"003","jkl","2012-01-01","2012-10-31"
5,"003","mno","2011-08-01","2011-12-31"
xx
).each{|x|
DB[:mytable].insert(*x)
}
# That was all setup, here's the query
ds = DB[:mytable]
result = ds.join(ds.select_group(:member).select_append{max(:end).as(:end)}, :member=>:member) do |j, lj, js|
Sequel.expr(Sequel.qualify(j, :end) => Sequel.qualify(lj, :end))
end
puts result.all
This gives you:
{:id=>1, :member=>"001", :data=>"abc", :start=>"2012-12-01", :end=>"2999-12-31"}
{:id=>3, :member=>"002", :data=>"ghi", :start=>"2009-01-01", :end=>"2999-12-31"}
{:id=>4, :member=>"003", :data=>"jkl", :start=>"2012-01-01", :end=>"2012-10-31"}
In this case it's probably easier to replace the last four lines with straight SQL. Something like:
puts DB[
"SELECT a.* from mytable as a
join (SELECT member, max(end) AS end FROM mytable GROUP BY member) as b
on a.member = b.member and a.end=b.end"].all
Which gives you the same result.
What's the criteria for your result?
If it is the keys 1,3 and 4 you may use DB[:mytable].filter( :id => [1,3,4]) (complete example below)
For more information about filtering with sequel, please refer the sequel documentation, especially Dataset Filtering.
require 'csv'
require 'sequel'
#Create Test data
DB = Sequel.sqlite()
DB.create_table(:mytable){
field :id
field :member
field :data
field :start #should be date, not implemented in example
field :end #should be date, not implemented in example
}
CSV.parse(<<xx
id,member,data,start,end
1,001,abc,12/1/2012,12/31/2999
2,001,def,1/1/2009,11/30/2012
3,002,ghi,1/1/2009,12/31/2999
4,003,jkl,1/1/2012,10/31/2012
5,003,mno,8/1/2011,12/31/2011
xx
).each{|x|
DB[:mytable].insert(*x)
}
#Create Test data - end -
puts DB[:mytable].filter( :id => [1,3,4]).all
In my opinion, you're approaching the problem from the wrong side. ORMs (and Sequel as well) represent a nice, DSL-ish layer above the database, but, underneath, it's all SQL down there. So, I would try to formulate the question and the answer in a way to get SQL query which would return what you need, and then see how it would translate to Sequel's language.
You need to group by member and get the latest record for each member, right?
I'd go with the following idea (roughly):
SELECT t1.*
FROM table t1
LEFT JOIN table t2 ON t1.member = t2.member AND t2.end > t1.end
WHERE t2.id IS NULL
Now you should see how to perform left joins in Sequel, and you'll need to alias tables as well. Shouldn't be that hard.