I'm using Mongoid to build a Rails 4 app.
The problem I'm having right now is how to filter some Mongoid objects through their own relations and have a Mongoid::Criteria at the end instead of an Array.
This is some example code:
class Editor
include Mongoid::Document
has_many :books
def likes
books.collect { |book| book.likes }
end
end
class Book
include Mongoid::Document
belongs_to :editor
end
What I would like to be able to do is something like:
Editor.last.likes.where(:created_at.gt => 10.days.ago)
but of course this doesn't work as Editor.last.likes returns an Array, not a Mongoid::Criteria
I know Mongoid has an aggregation framework but it's not entirely clear to me how to use it, nor if it's the best way to solve my problem.
Suggestions?
TIA,
ngw
The biggest problem you have here is that MongoDB does not do joins like a relational database does. All of the work you are getting for convenience when traversing object properties is being done client side while pulling in the 'related' documents over the wire in a query. But in sending a query, the two collections cannot be joined.
Your workaround solution is to work with the data you can get at in separate queries in order to target the results. One approach is here: rails mongoid criteria find by association
There should be other examples on Stack Overflow. You're not the first to ask.
Related
I have a simple rails application for a competition with these models: User, Team, and Trip. The associations are: team has_many users, and user has_many trips. Trips has a distance_miles field.
I'm am working on a simple view to display a table of teams with their stats that have multiple aggregations.
My initial approach was the following but results in N+1 queries.
#teams.each do |team|
#team.user.joins(:trips).count()
#team.user.joins(:trips).sum(:distance_miles)
end
The following works, but seems ugly as I want to add pagination, sorting and filtering eventually.
#teams = Team.left_joins(users: :trips)
.select('teams.id, teams.name, SUM(trips.distance_miles) as num_miles, COUNT(trips.id) as num_trips')
.group(:id)
I've been reading preload and includes but cant seem to get it to also get multiple aggregations. The following gets me part way there, but it is now missing fields from Team and still need the other aggregation:
#teams = Team.includes(users: :trips).group(:id).sum('trips.distance_miles')
Is there a "rails way" that I'm missing?
ActiveRecord::Calculations which provides .sum, .count is only really useful in very simple cases as it always creates a separate database query.
You're on the right track with .select but it could be cleaned up by extracting the code into the model:
class Team
def self.with_trips
trips = Trip.arel_table
self.left_joins(users: :trips)
.select(
self.arel.projections, # adds the existing select clause
trips[:distance_miles].sum.as(:num_miles),
trips[:id].count.as(:distance_miles)
)
.group(:id)
end
end
Using .eager_load instead of .left_joins will result in a PG::GroupingError on Postgres as the results would be ambiguous. Instead you need use a window function.
I am using FriendlyId across a bunch of different model. For a particular use-case with usernames, I can't use FriendlyId but still want to make sure the username is not being used as a slug in any of those models.
Currently, I have a .exists query for each model using FriendlyId. It doesn't feel super efficient, so I am wondering if there is a more efficient way of doing this by just querying the slugs column in friendly_id_slugs table that the library creates?
I'm on Rails 5.1, Postgres 9.5.x and FriendlyId 5.2.3
The answer by #Ben helped me find the ideal solution. It turns out that FriendlyId has an internal model - FriendlyId::Slug, that is backed by the friendly_id_slugs table.
So, a query like the following worked for me:
FriendlyId::Slug.where(
sluggable_type: ["Animals", "Plants"]
)
.where('lower(slug) = ?', potential_username.downcase)
.exists?
Defining a model map it to its table, simply create a app/models/friendly_id_slug.rb file, containing :
class FriendlyIdSlug < ApplicationRecord
end
You then get a standard model active record querying methods (FriendlyIdSlug.where…)
Alertnatively, running raw sql would apply :
ActiveRecord::Base.connection.execute("select * from friendly_id_slugs etc…")
That is, your .exists? sounds fair too. If you repeat code for each model, you could make a app/models/concerns or app/lib class file you'd reuse in the concerned models
Is there a way in Rails to manipulate database fields and corresponding accessor methods without the „nifty generators” ?
I want users, insofar they are privileged, to be able to manipulate the database structure, that is, at least, to add or delete columns. The privileged user should have the possibility to „Add new” some columns.
Say I have an Object/Table artist and it should “dynamically” receive columns as "date of birth", "has played with", "copies sold"
Not sure if it's a dup. It takes a preliminary decision whether Rails discourages from letting the user do this to begin with or or not. (if that's the case => certainly some noSQL solution)
In pure ruby at least it is easy to dynamically add an attribute to an existing Model/Class like this
Test.class_eval do; attr_accessor "new_attribute"; end
and
Test.new.new_attribute = 2
would return => 2 as expected
In order to create or manipulate a customized input mask / model: can I not manually go the same way the generators go and manually call ActiveRecord::Migration methods like add_column as well as create getter/setter-methods for ORM ?
If yes or no, in both cases, which are they to begin with?
Thanks!
I am not aware of any elegant way to allow an Object to dynamically create new columns. This would not be a good application design and would lead massive inefficiency in your database.
You can achieve a similar type of functionality you seek using ActiveRecord associations in Rails. Here's a simple example for your Artist model using a related Attributes table.
class Artist < ActiveRecord::Base
has_many :attributes
end
class Attribute < ActiveRecord::Base
belongs_to :artist
end
With this association, you can allow your Artist class to create/edit/destroy Attributes. ActiveRecord will use foreign keys in the database to keep track of the relationship between the two models.
If that doesn't work for you, your next best option is to look into NoSQL databases, such as MongoDB, which allow for much more flexibility in the schema. With NoSQL, you can facilitate the insertion of data without a predefined schema.
I want to query many different collections from a model in rails.
For example:
class Statistics
include Mongoid::Document
end
I want to be able to query a statistics or maybe a my_stats collection. Is that possible? How can I do it?
Your question isn't clear. If you want to know how to define your own collection name, instead of having Mongoid auto-generate one via the class name/ActiveSupport, you can do that using the store_in method. Like so:
class Statistics
include Mongoid::Document
store_in :mystats
end
If you're asking how to search multiple collections with one query, that isn't possible in MongoDB, as far as I'm aware.
I think the problem is trivial. I have two models: User and Betting.
User
has_many :bettings
Betting
belongs_to :user
I just want to get the users ordered by who made more bettings.
Are you on Rails2 or Rails3?
On Rails3, you can use the Ruby sort method and something like:
User.includes(:bettings).sort{|x,y| x.bettings.size <=> y.bettings.size}
Of course, in this case the sorting occurs after the SQL request, which is not optimum if you have large tables… I'm trying to figure how to do it at the SQL level with no answer yet…