I am trying to write a rather complicated query using baby_squeel. The query requires the use of the coalesce SQL function. coalesce is used as an example a couple of times on the baby_squeel home page but when I try to use it in a query I get a no method error.
Example below:
result = Table.selecting { |t| [t.col1,
((coalesce(t.col2, 0) + coalesce(t.col3, 0)).sum.as('column'))] }
.where.has { |t| t.col4 == 'some condition' }
.grouping { |t| t.col1 }
I am using Rails 5 and baby_squeel 1.2. The query is called inside of a private controller function.
Why is this happening? Do I need to require something or define the function myself using arel?
Not sure if this will help anyone, but I found what I was missing. Since I gave arity to the block I had to reference the functions I wanted to use with the airty variable in this case t. The correct code is:
result = Table.selecting { |t| [t.col1,
((t.coalesce(t.col2, 0) + t.coalesce(t.col3, 0)).sum.as('column'))] }
.where.has { |t| t.col4 == 'some condition' }
.grouping { |t| t.col1 }
Related
Introduction
Correcting a legacy code, there is an index of object LandingPage where most columns are supposed to be sortable, but aren't. This was mostly corrected, but few columns keep posing me trouble.
Theses columns are the one needing an aggregation, because based on a count of other documents. To simplify the explanation of the problem, I will speak only about one of them which is called Visit, as the rest of the code will just be duplication.
The code fetch sorted and paginate data, then modify each object using LandingPage methods before sending the json back. It was already like this and I can't modify it.
Because of that, I need to do an aggregation (to sort LandingPage by Visit counts), then get the object as LandingPage instance to let the legacy code work on them.
The problem is the incapacity to transform Mongoid::Document to a LandingPage instance
Here is the error I got:
Mongoid::Errors::UnknownAttribute:
Message:
unknown_attribute : message
Summary:
unknown_attribute : summary
Resolution:
unknown_attribute : resolution
Here is my code:
def controller_function
landing_pages = fetch_landing_page
landing_page_hash[:data] = landing_pages.map do |landing_page|
landing_page.do_something
# Do other things
end
render json: landing_page_hash
end
def fetch_landing_page
criteria = LandingPage.where(archived: false)
columns_name = params[:columns_name]
column_direction = params[:column_direction]
case order_column_name
when 'visit'
order_by_visits(criteria, column_direction)
else
criteria.order_by(columns_name => column_direction).paginate(
per_page: params[:length],
page: (params[:start].to_i / params[:length].to_i) + 1
)
end
def order_by_visit(criteria, order_direction)
def order_by_visits(landing_pages, column_direction)
LandingPage.collection.aggregate([
{ '$match': landing_pages.selector },
{ '$lookup': {
from: 'visits',
localField: '_id',
foreignField: 'landing_page_id',
as: 'visits'
}},
{ '$addFields': { 'visits_count': { '$size': '$visits' }}},
{ '$sort': { 'visits_count': column_direction == 'asc' ? 1 : -1 }},
{ '$unset': ['visits', 'visits_count'] },
{ '$skip': params[:start].to_i },
{ '$limit': params[:length].to_i }
]).map { |attrs| LandingPage.new(attrs) { |o| o.new_record = false } }
end
end
What I have tried
Copy and past the hash in console to LandingPage.new(attributes), and the instance was created and valid.
Change the attributes key from string to symbole, and it still didn't work.
Using is_a?(hash) on any element of the returned array returns true.
Put it to json and then back to a hash. Still got a Mongoid::Document.
How can I make the return of the Aggregate be a valid instance of LandingPage ?
Aggregation pipeline is implemented by the Ruby MongoDB driver, not by Mongoid, and as such does not return Mongoid model instances.
An example of how one might obtain Mongoid model instances is given in documentation.
If I need to query conditionally, I try a way like this:
query = Model.find_something
query = query.where(condition1: true) if condition1 == true
query = query.where(condition2: true) if condition2 == true
query = query.where(condition3: true) if condition3 == true
It's work well.
But I think that it is a way to repeat same code and a look is not good.
Is it possible query does not reassign to variable per every conditional expression? like this:
query.where!(condition1: true) # But it can not be in rails :)
I recently started as Rails5.
What else is the best way to do Rails5?
You can use model scopes:
class Article < ApplicationRecord
scope :by_title, ->(title) { where(title: title) if title }
scope :by_author, ->(name) { where(author_name: name) if name }
end
Just chain scopes anywhere you need:
Article.by_title(params[:title]).by_author(params[:author_name])
If parameter present you get scoped articles, if there is no such parameter - you get all Articles
>scope :a, -> { joins(:b).where('bs.c': false) }
>scope :a, -> { joins(:b).where('bs.c = ?', false) }
Just wanted to ask whether those 2 lines do the same thing? The first one seemed to work fine in development but gave me a syntax error when I tried to push to Heroku. Is the first one deprecated?
I believe these are more interchangeable, without syntax errors:
scope :a, -> { joins(:b).where(bs: { c: false }) }
scope :a, -> { joins(:b).where('bs.c' => false }) }
scope :a, -> { joins(:b).where('bs.c = ?', false) }
scope :a, -> { joins(:b).where('bs.c = :q', { q: false }) }
Personally, the first line is my preferred because you can list several columns within that nested hash without needing to keep repeating the table name/alias.
Only Ruby 2.2 and above allow use the JSON like hash syntax with quoted keys, ie
{'foo' : bar}
Instead of
{foo: bar}
Of course in your case not quoting the key probably won't work either because of the . in the key.
This suggests you are running different Ruby versions locally and on heroku.
Other than that, they should be equivalent.
I have a method that is suppose to iterate over an array and match any items to a string I have in my model. My method looks like this
#new_array = #old_array.find_all { |t| t.fetch('name') == "self.object_name" }
This method should look thru the array of hashes I have and match any items that have the same name as object_name. When I test the name comparison to the object_name in console it shows true but when I run the full method described above it shows no objects found, however I know the array contains multiple objects with the EXACT same name. Any idea of whats wrong?
The array looks like so...
old_array = {"id"=>"123", "account"=>"456", "name"=>"CITY"},
{"id"=>"456", "account"=>"567", "name"=>"CITY DIR DEP"},
{"id"=>"456", "account"=>"567", "name"=>"BUCK"},
{"id"=>"456", "account"=>"567", "name"=>"CITY DIR DEP"},
{"id"=>"456", "account"=>"567", "name"=>"HAPPY"},
{"id"=>"456", "account"=>"567", "name"=>"CIRCLE"}
and the object prints out in console as
self.object_name => "CITY DIR DEP"
You don't need quotes " at all (you literally trying to compare retrieved name with string "self.object_name" instead of the value of self.object_name):
#new_array = #old_array.find_all { |t| t.fetch('name') == self.object_name }
If you are a big fan, you can interpolate with "#{}":
#new_array = #old_array.find_all { |t| t.fetch('name') == "#{self.object_name}" }
Try this:
#new_array = #old_array.find_all { |t| t['name'] == self.object_name }
Is there a way to pull records from activerecord in a hash already indexed by id instead of an array?
This is what im currently doing:
results = {}
Table.select { |current| results[current.id] = current }
Im assuming that there has to be a method that does that?
Table.all.index_by { |t| t.id }
Or:
Table.all.index_by(&:id)
if you're into the whole brevity thing.