Is it possible to write an ActiveRecord query that sorts by an association field if the association exists, and otherwise sorts by an attribute on the object itself?
Example: I have a Discussion object which has_many :comments. I'd like to display a list of discussions sorted by discussion.latest_comment.created_at. However, some discussions may not have any comments, in which case I would like use their discussion.created_at attribute instead.
The catch is that I need the result to be an ActiveRecord::Relation (for performance reasons, and also because we are using Kaminari [which requires a Relation object]).
The only thing I could think of is to actually create a new field like discussion.latest_comment_at which would be initially populated by discussion.created_at and then updated every time a new comment is posted. However, this doesn't seem very straight-forward from a maintenance perspective (e.g. what happens when a comment gets deleted?).
I don't know of a way to do this through SQL, so I cheated and I have my code set a last_post_at attribute whenever a topic is created.
That way, I can then sort the topics by last_post_at rather than having to query two tables at once.
I've seen other forum systems do it this way too, and it seems like what you're designing is exactly a forum-like system.
Related
I have a Invoice model which accepts_nested_attributes_for :line_items with allow_destroy: true. In my model, in a before_save callback, I can reference the data like this:
self.line_items
Some of the items will be deleted upon save. I want to be able to grab only the items that won't be deleted like this:
self.line_items.where(_destroy: false)
However, this obviously won't work since I am dealing with an unsaved object. So, my question is how do I get the list of items that won't be deleted? I know I could technically iterate through the list and add each applicable item to a new array, but I figure there is something more intuitive. For example, currently I use .sort_by(&:line_number) rather than the SQL .sort(:line_number) for sorting which allows me to sort in memory rather than from SQL. I need the same thing except for a .where clause.
Thanks in advance.
As far as I'm aware, where is aimed specifically at building SQL queries, so you can't use it to deal with in-memory criteria like whether something will be destroyed on save. For that, you can use the methods in the Enumerable module, which is included in the collection object. The sort_by call you mentioned is using that module. In this case, you'd probably use line_items.reject(&:marked_for_destruction?). See the documentation for marked_for_destruction? for more details.
Disclaimer: I really spent time thinking about names of models and variables. If you also do, this question is for you.
I have a Rails project which contains two models: User and Project.
They are connected by the model ProjectsUser, which is a connection model in a many-to-many relationship. This model also holds the role of a user in the given project, along with other attributes such as tier and departments. So this is a has_many :through relationship.
Given this scenario, here is everything that always bothered me on all my rails projects since I started developing on it:
Should I use a ProjectsUserController or better add the relevant actions on UserController and ProjectController? At some point, I want to assign users to a project, or even changing the role of a user in a given project. Is it a better practice to leave those actions on the connection controller, or use the model controllers?
Should I write a method to get the role of a user for a given project? This is basically if I should have a method User#role_for(project) or not. Since this method basically is getting the information from the projects_user object it could make more sense to always let this explicity on the code, since most of the times I'll have the project and the user, but not the projects_user. Is this line of thinking correct, or maybe the problem is that I'm should have more project_user on my code than I really do? Are there good caveats for this?
Should I try to rename my table to a non-standard name if it is not obvious? Ok, I got that if I have the models User and NewsSite I should use has_many :subscriptions, but the thing is that naming those models in real life cases are usually harder, by my experience. When the name ends up not being that obvious (for exemple, in my case, maybe project_participation as #wonderingtomato suggested) is for the best, or in those cases it is better to fall back to the ProjectsUser approach?
One extra cookie for pointing beautiful open source Rails code, or by book indications that might help with my kind of questions.
I would use a specific controller. Even if now the interaction sounds simple, you can't know if in the future you'll need to add more advanced features.
I've been handling these kind of relationships in several projects, and using a controller for the join model has always paid off.
You can structure it this way, for example:
index should expect a params[:project_id], so that you can display only the index of users for a specific project.
create is where you add new users, that is where you create new join models.
update is to modify a value on an existing join model, for example when you want to update the role of a user in a project.
destroy is where you remove users from the project, that is where you delete the corresponding join models.
You might not need a show and edit actions, if you decide to manage everything in the index view.
Also, I'd suggest to choose a different name. Rails relies heavily on naming conventions, and projects_users is the default name for the join_table you would use with a has_and_belongs_to_many association. In theory you can use it for an independent model (and a has_many through:), but it's not immediately clear and you might break something. In addiction, it will confuse the hell out of any new programmer that could join the project in the future (personal experience).
What about calling the model something like project_participation?
If you haven't built a lot of functionality yet, and don't have yet that table in production, changing it now will save you a lot of headaches in the future.
update
1) I stand by what I said earlier: your join model is a full fledged record, it holds state, can be fetched, modified (by the user) and destroyed.
A dedicated controller is the way to go. Also, this controller should handle all the operations that modify the join model, that is that alter its properties.
2) You can define User#role_for(project), just remember that it should properly handle the situation where the user is not participating to the project.
You can also make it explicit with something like:
#user.project_participations.where(project_id: #project.id).first.try(:role)
# or...
ProjectParticipation.find_by(project_id: #project.id, user_id: #user.id).try(:role)
But I'd say that encapsulating this logic in a method (on one of the two models) would be better.
3) You are already using a non standard name for your table. What I mean is that it's the default name for a different kind of association (has_and_belongs_to_many), not the one you are using (has_many through:).
Ask yourself this: is the table backing an actual model? If yes, that model represents something in the real world, and thus should have an appropriate name. If, on the other hand, the table is not backing a model (e.g. it's a join table), then you should combine the names of the tables (models) it's joining.
In my mind, REST doesn't always have to map directly to DB records. A conceptual resource here is the association of Projects to Users. Implementation would be different depending on your persistence layer, but a RESTful API would be standard.
Convention over Configuration in Rails is a great helper, but it isn't necessarily applicable to every case 100% of the way through the stack. There doesn't need to be a 1-to-1 mapping between controllers, models, and their respective names. At the app-level, particularly, I want my routes/controllers to represent the public view of the API, not the internal implementation details of the persistence and domain layers.
You might have a UserProjectsController which you can perform CRUD on to add/remove project associations to users, and it will do the appropriate record manipulation without being overly bound to the DB implementation. Note the naming, where the route might be /user/:id/projects, so it's clear you are manipulating not Users or Projects, but their associations.
I think thinking about this sort of thing (both before and after the fact) is what leads to better designs.
I too start with the model and think about the application structurally. The next step in my oppinion is to build the user interface to make sense based on what makes it easy and useful for the user (spending more effort on things that matter more). So if it makes sense for the user to separately edit the ProjectsUser objects then the ProjectsUsersController is the way to go. More likely editing the join model objects as part of the Project (or User depending on the structure of you app) will be a better fit for the user. In that case using a nested form and editing via the controller (and model) that's the main model referenced by the form is better. The controller really lives to serve the UI, so decisions about it should be dependent on the UI.
Yes, if it makes your code simpler or more readable. If you use role more than once I suspect it will.
I would actually name that model something like Member, or ProjectMember (or Membership). It defines a relationship between a user and a project, so its name should reflect what relationship that is. In the occasions where such a name is too unwieldly or too hard to define then falling back to something like ProjectUser is reasonable (but not ProjectsUser). But I definitely like finding a more meaningful name when possible.
According to the present Rails documentation, regarding the << operator on a has_many relationship on an existing object:
collection<<(object, …)
Adds one or more objects to the collection by setting their foreign keys
to the collection’s primary key.
(This is the interesting bit)
Note that this operation instantly fires update sql without waiting for
the save or update call on the parent object.
I didn't realize this would happen, I was quite surprised; I could have sworn this was not the case in the past, though I admit I could be wrong.
In either case, I haven't been able to find any additional documentation regarding this, however I wonder if there is a way to prevent this update?
My situation is simple, I merely have an object which exists in the database, which is being prepared for an "edit" page. I append one or multiple related objects before the page is render. C'est tout.
Update:
Apparently the same update-scenario also occurs if you set the has_many relationship directly from an array using the = operator.
Use the collection's build method. That won't immediately fire a SQL statement like the others do.
foo.bars.build(attributes)
foo.save
Lots of good information can be found here: http://api.rubyonrails.org/classes/ActiveRecord/Associations/ClassMethods.html
Note: This method assumes you have the flexibility to create your objects through the build method rather than using Bar.new.
I have many legacy databases from which I need to pull raw data. Each of the tables in the database have arbitrary names, and an arbitrary collection of fields. I have been getting access to these fields with the following class:
class Frt < ActiveRecord::Base
establish_connection :legacy
set_primary_key "point"
end
When I reach the point in my code where I know the table name, I can call:
Frt.set_table_name "table"
t = Frt.find_by_sql("blah")
something = t.field_name + t.other_field_name
etc...
The problem is that I've realized that this locks the accessible field names to whatever table I select first. If I try to change the table with another call to the `set_table_name' method, it changes the attribute for the class, but any new instances will still have the same set of fields as the first one. So far, in my app, I've not needing anything else, but I'm expanding the program in a way in which I know it will bite me in the butt down the road.
I've tried `Frt.send :set_table_name "new_table"', hoping that it would cause ActiveRecord to do it's magic again. It doesn't.
Can anyone suggest how I might be able to keep the convenience of ActiveRecord, but get it to dynamically remap its fields for whatever table I need loaded?
I've never used it, but Magic Model Generator claims to create models for tables automatically.
Sorry to answer my own post, but I guess I didn't explain it very well. In case someone else comes along after this, what I needed to do was issue a call to the "Frt.reset_column_information" method. I was revisiting this issue, and just dumped all the methods on the ActiveRecord class, and found that one lurking in the list.
A better way to solve this would be to create a model for each legacy table you need to interact with - no dynamic table name remapping required, and it works right out of the box.
When working with a nested model in a form, is there a way to sort the nested models in the view or in the controller? The closest answer I could find was here, but both of those solutions involve putting sort orders in the model. I'd rather avoid changing things fundamentally like that and keep this particular sort order to just one page.
You can always sort with ruby: parent.children.sort{|a,b| a.field <=> b.field} or something like that
Or you can add a find method to parent model, like def self.find_ordered_by_field
Can't think of another options...
Putting the sort in the models is absolutely the way to go. You shouldn't have any more ruby code than necessary in your views themselves, because it's much harder to test that your sorting is working the way you think it should.
When you add the sort at the model level (and either answer in the link you posted works well) you can add an automated test to verify that it is, in fact, sorting the way you'd like. This is business logic, and it belongs in the model.
Sometimes, however, you're looking to sort in a way that is NOT what you would normally want for this model. Maybe you normally want them sorted by name, but in this one view you want them sorted newest to oldest. At the very least, do the sort in the controller so once again it can be fully tested. But I would still put it in the model, personally.