Can't seem to wrap my head around this problem. I have a message model below
Message
# content:string
# original_id:integer
# sender_id:integer
# receiver_id:integer
has_one :reply, class_name: "Message", foreign_key: "original_id"
belongs_to :original, class_name: "Message"
Each message can only have one reply and the reply message will have its corresponding original message.
What I'd like to do is create a scope or a class method that allows me to pull replied messages in one batch and unreplied messages in another.
Something like
# return messages that have a reply present
def self.replied
where(reply.present?)
end
# return messages that have no reply
def self.unreplied
where(reply.nil?)
end
so I can chain the methods and ultimately pull messages with
user1.messages.replied
It doesn't currently work because I can't use the where clause unless it's a DB column...so I was thinking about adding a "replied" boolean column into the DB so I could use the where clause but there's probably a solution to this that I'm just not thinking about. A scope with a lambda? I'm stuck right now.
Any help much appreciated
To find those that have been replied is fairly straightforward:
scope :replied, joins(:reply)
as anything without a reply will be filtered out with an INNER JOIN. To find those without replies is a bit more complex - you can either use a LEFT JOIN or an EXISTS subquery to accomplish this. includes is a simple way to force a LEFT JOIN:
scope :unreplied, includes(:reply).
where(replies_messages: {id: nil}).
where(original_id: nil)
An EXISTS subquery may be somewhat more efficient, but more complex to write (at this time), as it would involve invoking Arel tables (or Squeel). For most cases a LEFT JOIN would be 'good enough', and includes is a quick-and-dirty way to force the API to use one.
Related
I'm trying to delete all the organizations that no longer have any users.
Using the below code, I can find all the records I wish to delete:
Organization.includes(:users)
.where(users: { id: nil })
.references(:users)
When I add delete_all, I get the same error I would get if I didn't include references:
PG::UndefinedTable: ERROR: missing FROM-clause entry for table "users"
I could probably write the solution in pure SQL, but I don't understand why Rails isn't keeping the reference to users when I add the delete_all statement.
Here are some more details:
Organization:
has_many :users
User:
belongs_to :organization
I've found the includes useful only for eager loading (and it can rarely handle my cases), and when coupled with references it generates something completely insane (aliasing every single field with something like tN_rM) even though it actually does a LEFT OUTER JOIN... Which could help if it didn't vanish once delete_all appears!
I've found that it's much clearer and simpler just to use exists. It's Arel (and there's no point in avoiding it, its under the hood of ActiveRecord anyway), but it's such a tiny portion that it's barely noticeable:
Organization.where(
User.where('users.organization_id = organizations.id').exists.not
)
Or, if this string of SQL doesn't look nice to you, use a bit more Arel, so it gets noticeable:
Organization.where(
User.where(organization_id: Organization.arel_table[:id]).exists.not
) # I tend to extract these ^^^^^^^^^^^^^^^^^^^^^^^ into local variables
That handles chaining .delete_all on top just fine, since it's not (syntactically) a join, even though it's effectively equivalent to one.
The magic behind this
SQL has an EXISTS operator that is similar in functionality to a join, except for inability of selecting fields from a joined table. It forms a valid boolean expression which can be negated and thrown into WHERE-conditions.
In the "SQL-free" form I'm using an expression "column of a table", which turns out to be usable in Rails' hash-conditions. It's an accidental discovery, one of the few uses of Arel that does not make code too bulky.
I'm not sure how you plan to implement this in the MVC framework, but it seems clean to do the organization purge via model action. Whenever a user is deleted, check to see of the organization has any remaining members.
in the User.rb
class User < ActiveRecord::Base
before_destroy :close_user
...
def user_organization
Organization.where(user_id: id)
end
private
def close_user
unless user_organization.users.any?
user_organization.destroy
end
end
end
Added To apply callback delete solution to users being member of many organizations
If the user has multiple organizations
class User < ActiveRecord::Base
before_destroy :close_user
...
def user_organizations
Organization.where(user_id: id)
end
private
def close_user
user_organization.find_each do |organization|
unless organization.users.any?
organization.destroy
end
end
end
Caveat: this is not tested, didn't fail syntax. I don't have the data to test it fully but I think it will work. But it means running this action after every user delete, which is a system architecture decision. If it's an option, it might be worth a try.
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.
I've been reading and re-reading the Rails associations guide:
http://guides.rubyonrails.org/association_basics.html
This were close but not quite the same:
Ruby on rails active record associations
I'm not sure how to setup the following scenario.
Events has a status of either pending, open, or close.
I thought this would be simple enough to just have:
event has_one status
status belongs_to event
But this really isn't a one-to-one relationship since a status can belong to many events.
So then I thought I would do something like:
status has_many events
event belongs_to status
But this seems funny, because a status doesn't own an event. An event owns a status, right?
I had tried using enumerations and not have a status model. But that got tricky since it seems like ActiveRecord doesn't really support enumerations. I also figured that having a separate model might be good in case someone wants to expand on the number of options for status, like adding 'awaiting approval' or something.
This post suggests that my latter setup is okay, even though it reads funny:
Really easy Rails Active-Record Associations question
But I'm just wondering if I'm not aware of a better Ruby/Rails way of handling this simple scenario.
Thanks in advance!
Doing this as an active record association is overkill. Think about it, create a whole table just to store 3 values in it that will never change?
What you really need is an enum. But of course, ruby doesn't have an enum.
Luckily, you can fake it.
module StatusCodes
pending = 0
open = 1
closed = 2
end
Then you can do something like this
if #event.status == StatusCodes::open
# do something
end
It's a much simpler solution and your code stays very readable.
Ignore that voice in your head: you're doing it fine. The real important of which model has belongs_to is where the foreign key is stored. It's clear in this example that the foreign key should be stored in the Event model, which means it should belongs_to :status.
I also agree with the other posts, though - if you have got a small and fixed number of potential Status records, consider creating an constant hash to store them instead of creating a whole database table for them.
Why not add a status column to Event (as an integer), and have something like this:
class Event < ActiveRecord::Base
STATUS_TYPES = {1 => "active", 2 => "inactive", 3 => "closed"}
def status
STATUS_TYPES[self[:status]]
end
def status=(new_status)
new_status = STATUS_TYPES.invert[new_status] if new_status.class == "String"
self[:status] = new_status
end
end
You might want to consider using state_machine and a simple string column to implement the status, instead of using an association or a hand-rolled enum.
You are wrong.
If you do
Event
has_one :status
Status
belongs_to :event
Rails will make sure it is a one-to-one association, so status will only belong to one event
I'm pretty sure this is what happens if you try to assign a event status to a different event
e1 = Event.first
status = e1.status
e2 = Event.new
e2.status = status
e2.save
Event.first.status #=> nil
Suppose I have posts, which have many categories through categorizations. Suppose that I add a boolean column primary to categorizations in order to determine the primary category of a post. But now I'm stuck dealing with the join model, when what I'd really like to do is something like this:
post = Post.first
primary_cat = post.categories.where(:primary => true)
post.categories.first.primary = true
post.save # would actually update the categorization, setting primary = true
There are all sorts of examples I could give you to show why this would be useful, but essentially I want to be able to interact with a model as though it is somehow merged with its join model. Being able to say "What's the primary category?" or "OK this category will be the primary one" without ever touching the join model is the intuitive for me the think of this.
Is this even possible with Rails? Has anyone seen a effort to do this sort of thing before?
I like quest's solution except that setting it should just be post.primary_category= and should take a category object. Just setup a has_one :primary_category on post and you're golden.
Short answer is to create a set_primary(post) method on category that takes as its argument the post and boolean.
def set_primary(post)
categorization = post.categorization.where('your opts hash here')
categorization.primary = true
categorization.save!
end
post.categories.first.set_primary(post)
Charlie Bowman does have the first piece of the puzzle with the set_primary logic on the Category model, however his setter doesn't unset the previous active category... In addition, the expensive part of Steve's problem, is actually just getting the primary category on each request. To get around that, I would also save the primary category id on the Post itself. That way you don't need to access the join model to figure out the primary category.
def set_primary_category(post)
post.categorizations.each do |cat|
if cat.post == post
cat.primary = true
cat.save!
post.update_attribute(:primary_category_id, cat.category_id)
else
cat.update_attribute(:primary, false) if cat.primary
end
end
end
TO SET:
post.categories.first.set_primary(post)
TO ACCESS:
post.primary_category
Unfortunately I don't think anyone has made a gem that makes this easier for you to accomplish, but the logic is pretty straightforward. It also has the benefit of having access to the primary state in both the categorization and the post, so that you always have a fast way to access the data.
Also, I think its good to remember. You have a many to many relationship for posts/categories. But only a has_one relationship for post/primary_category. Whenever you have an access pattern like that, I like to try and stay away from the join model for the has_one, since it just isn't needed.
this question is probably a duplicate of Ruby on Rails: attr_accessor for submodels, but basically delegate should be what you're looking for, with an additional callback on save.
I think there are a lot of places where my design may be screwing this up. I have very limited experience with Rails though. This is happening in Rails 2.3.2 with Postgres 8.3.
We've got two tables in our DB. One called "survey" and one called "survey_timepoint". A survey can have multiple time points so in the survey_timepoint table there is a column called "survey_id" with an fk constraint on it.
I also think I should mention that the tables were not created with a rails migration although they do follow the rails naming conventions. I suspect AR isn't anticipating a constraint on that column and it doesn't know how to handle the situation.
In my rails models I have:
has_many :survey_timepoint
and
belongs_to :survey
If I do something like:
s = Survey.new
s.survey_timepoint.push SurveyTimepoint.new
s.save!
I get:
ActiveRecord::StatementInvalid: PGError: ERROR: insert or update on table "survey_timepoints" violates foreign key constraint "survey_timepoints_fk"
DETAIL: Key (survey_id)=(59) is not present in table "surveys"
I'm assuming that if I delete that fk constraint on survey_timepoint.survey_id it'll work ok. It seems like I shouldn't have too though. Am I going to be stuck creating and saving each of the objects separately and wrapping the whole process in a transaction? It seems rather un-railsy. Apologies for any necessary information that I may have omitted.
You might want to check the SQL commands being sent. It looks like it is adding the survey_timepoint record before the survey record. Note that you are already dealing with two database changes — the survey and the survey_timepoint — so you should be using a transaction.
You can fix the immediate problem by doing s.save! before adding the timepoint (and then calling it again). My knowledge of Rails functionality is not deep enough to know if there is a more "railsy" way of doing this then wrapping it in a transaction.
I just experimented and found that this works with MySQL:
s = Survey.new()
s.survey_timepoints << SurveyTimepoint.new # Note "survey_timepoints" (plural)
s.save!
I think it would work equally well with PostgreSQL.
It does two inserts, first the Survey, then the timepoint, and wraps them in a transaction.
You can also do it all on one line:
Survey.create!({:name=>'New Survey', :survey_timepoints => [SurveyTimepoint.new]})
Incidentally, for ActiveRecord to work right you have to make sure of your singulars and plurals. (If you want to break the expected forms, you'll need to tell AR you're doing that -- a whole other topic.)
Your tables should be:
surveys
-------
# ...
survey_timepoints
-----------------
survey_id
# ...
And in your models you'd have:
class Survey < ActiveRecord::Base
has_many :survey_timepoints
# etc...
end
class SurveyTimepoint < ActiveRecord::Base
belongs_to :survey
end