Sort by timestamp on has_many after scope - ruby-on-rails

I have a User object, a Package object (User has_many packages) and then a LocationTracker (User has_many location_trackers), which acts as a join table between User and Package, but just tracks details such as the most recent package delivery.
I'd like to sort my Users based on the most recent package they sent. The LocationTracker has an attribute last_received_from_user
I can easily sort the users from a certain location by ordering by the last_received_from_user attribute, however I'd also like to have a global index page that shows all of the Users, sorted by the last package they delivered.
I'm having trouble grouping the users. I'm attempting to use a DISTINCT ON(last_received_from_user), but then it complains that the attribute isn't in the group, and when I add it to the group, it groups by that timestamp, which is obviously pretty unique, so I get duplicate users showing up.
My current code is as follows:
User.includes(:location_trackers)
.group("location_trackers.user_id, users.id")
.order("location_trackers.last_received_from_user #{order} NULLS LAST")
Any help is greatly appreciated!
EDIT:
I've got the last_received_from_user which allows me to sort users from a SINGLE location well. However, I need to be able to scope based on what could be a number of different options. For example, only show users in a certain area (Which could be compromised of a few locations), or order by ALL users for ALL locations. The attribute works great for a single user-location relationship, but fails when it comes to attempting to perform the search on more than 1 location.

I'd like to sort my Users based on the most recent package they sent
Wouldn't it be easier (and way more efficient) having an attribute like latest_delivery_location and using a callback on the User model like:
class User < ApplicationRecord
after_update :update_latest_delivery_location
private
def update_latest_delivery_location
update_attributes(
latest_delivery_location: location_trackers.last.last_received_from_user
)
end
end
Or updating such attribute after an order has been placed / dispatched. I'd go for this approach because is easier to maintain and, if you want it more performing you could always add an index on users.latest_delivery_location for sorting operations.

Related

Rails includes method when to add

So I've read a lot about the rails includes method but I'm still a bit confused about what's the best situation to use it.
I have a situation where I have a user record and then this user is related to multiple models like client, player, game, team_player, team, server and server_center.
I need to display specific attributes from the related models in a view. I only need around 1-2 attributes from a specific model and I don't use the others.
I already added delegates for example to get the server.name from player I can use server_name but in this situation do I include all of the tables from which I need the attributes or is there something else I do because I only need a couple of attributes from the model.
My query is as follows at the moment:
#user_profile = User
.includes({:client => [:player, :team_player => [:team]]},
:game,
{:server_center => :server})
.where(game_id: #master.admin.games)
Includes ensures that all of the specified associations are loaded using the minimum possible number of queries.
Let say we have 2 models named User and Profile :
class User < ActiveRecord::Base
has_one :profile
end
class Profile < ActiveRecord::Base
belongs_to :user
end
If we are iterating through each of the users and display the name of each user were name field resides in Profile model which has a association with User model, we would normally have to retrieve the name with a separate database query each time. However, when using the includes method, it has already eagerly loaded the associated person table, so this block only required a single query.
without includes:
users = User.all
users.each do |user|
puts user.profile.name # need extra database query for each time we call name
end
with includes
# 1st query to get all users 2nd to get all profiles and loads to the memory
users = User.includes(:profile).all
users.each do |user|
puts user.profile.name # no extra query needed instead it loads from memory.
end
Eager Loading is used to prevent N+1 query problems. basically it does left outer join and this plays an important role in speeding up request response or optimizing the queries. eg: if we are having huge amount users and if we want to iterate through those users and their corresponding profile. no of time which we will be hitting database will be equals to number of users. but if we are using includes it will keep all profile into memory later when we iterate through the users it will fetch from this memory instead of querying.
Eager loading may not always be the best the cure for our N+1 queries for eg: if you are dealing with some complex queries preferably looks for some caching solutions like Russian Doll caching etc.. still both method has his own pros & cons end of the day it's up to you to determine the best approach.
one useful gem which helps to detect N+1 query is bullet

Record changes pend approval by a privileged user; Its like versioning combined with approvals

I have a requirement that certain attribute changes to records are not reflected in the user interface until those changes are approved. Further, if a change is made to an approved record, the user will be presented with the record as it exists before approval.
My first try...
was to go to a versioning plugin such as paper_trail, acts_as_audited, etc. and add an approved attribute to their version model. Doing so would not only give me the ability to 'rollback' through versions of the record, but also SHOULD allow me to differentiate between whether a version has been approved or not.
I have been working down this train of thought for awhile now, and the problem I keep running into is on the user side. That is, how do I query for a collection of approved records? I could (and tried) writing some helper methods that get a collection of records, and then loop over them to find an "approved" version of the record. My primary gripe with this is how quickly the number of database hits can grow. My next attempt was to do something as follows:
Version.
where(:item_type => MyModel.name, :approved => true).
group(:item_type).collect do |v|
# like the 'reify' method of paper_trail
v.some_method_that_converts_the_version_to_a_record
end
So assuming that the some_method... call doesn't hit the database, we kind of end up with the data we're interested in. The main problem I ran into with this method is I can't use this "finder" as a scope. That is, I can't append additional scopes to this lookup to narrow my results further. For example, my records may also have a cool scope that only shows records where :cool => true. Ideally, I would want to look up my records as MyModel.approved.cool, but here I guess I would have to get my collection of approved models and then loop over them for cool ones would would result in the very least in having a bunch of records initialized in memory for no reason.
My next try...
involved creating a special type of "pending record" that basically help "potential" changes to a record. So on the user end you would lookup whatever you wanted as you normally would. Whenever a pending record is apply!(ed) it would simply makes those changes to the actual record, and alls well... Except about 30 minutes into it I realize that it all breaks down if an "admin" wishes to go back and contribute more to his change before approving it. I guess my only option would be either to:
Force the admin to approve all changes before making additional ones (that won't go over well... nor should it).
Try to read the changes out of the "pending record" model and apply them to the existing record without saving. Something about this idea just doesn't quite sound "right".
I would love someone's input on this issue. I have been wrestling with it for some time, and I just can't seem to find the way that feels right. I like to live by the "if its hard to get your head around it, you're probably doing it wrong" mantra.
And this is kicking my tail...
How about, create an association:
class MyModel < AR::Base
belongs_to :my_model
has_one :new_version, :class_name => MyModel
# ...
end
When an edit is made, you basically clone the existing object to a new one. Associate the existing object and the new one, and set a has_edits attribute on the existing object, the pending_approval attribute on the new one.
How you treat the objects once the admin approves it depends on whether you have other associations that depend on the id of the original model.
In any case, you can reduce your queries to:
objects_pending_edits = MyModel.where("has_edits = true").all
then with any given one, you can access the new edits with obj.new_version. If you're really wanting to reduce database traffic, eager-load that association.

How to keep track of model history with mapping table in Ruby on Rails?

dream
I'd like to keep record of when a user changes their address.
This way, when an order is placed, it will always be able to reference the user address that was used at the time of order placement.
possible schema
users (
id
username
email
...
)
user_addresses (
id
label
line_1
line_2
city
state
zip
...
)
user_addresses_map (
user_id
user_address_id
start_time
end_time
)
orders (
id
user_id
user_address_id
order_status_id
...
created_at
updated_at
)
in sql, this might look something like: [sql]
select ua.*
from orders o
join users u
on u.id = o.user_id
join user_addressses_map uam
on uam.user_id = u.id
and uam.user_address_id = o.user_address_id
join user_addresses ua
on ua.id = uam.user_address_id
and uam.start_time < o.created_at
and (uam.end_time >= o.created_at or uam.end_time is null)
;
edit: The Solution
#KandadaBoggu posted a great solution. The Vestal Versions plugin is a great solution.
snippet below taken from http://github.com/laserlemon/vestal_versions
Finally, DRY ActiveRecord versioning!
acts_as_versioned by technoweenie was a great start, but it failed to keep up with ActiveRecord’s introduction of dirty objects in version 2.1. Additionally, each versioned model needs its own versions table that duplicates most of the original table’s columns. The versions table is then populated with records that often duplicate most of the original record’s attributes. All in all, not very DRY.
vestal_versions requires only one versions table (polymorphically associated with its parent models) and no changes whatsoever to existing tables. But it goes one step DRYer by storing a serialized hash of only the models’ changes. Think modern version control systems. By traversing the record of changes, the models can be reverted to any point in time.
And that’s just what vestal_versions does. Not only can a model be reverted to a previous version number but also to a date or time!
Use the Vestal versions plugin for this:
Refer to this screen cast for more details.
class Address < ActiveRecord::Base
belongs_to :user
versioned
end
class Order < ActiveRecord::Base
belongs_to :user
def address
#address ||= (user.address.revert_to(updated_at) and user.address)
end
end
Thought I'd add an updated answer. Seems the paper_trail gem has become the most popular one for versioning in Rails. It supports Rails 4 as well.
https://github.com/airblade/paper_trail
From their readme:
To setup and install:
gem 'paper_trail', '~> 3.0.6'
bundle exec rails generate paper_trail:install
bundle exec rake db:migrate
Basic Usage:
class Widget < ActiveRecord::Base
has_paper_trail
end
For a specific instance of the Widget class:
v = widget.versions.last
v.event # 'update' (or 'create' or 'destroy')
v.whodunnit # '153' (if the update was via a controller and
# the controller has a current_user method,
# here returning the id of the current user)
v.created_at # when the update occurred
widget = v.reify # the widget as it was before the update;
# would be nil for a create event
I've only played with it but I'm about to start a pretty ambitious site which will require good versioning of certain classes and I've decided to use paper_trail.
===EDIT====
I have implemented the paper_trail gem in production at www.muusical.com and it has worked well using the above. The only change is that I am using gem 'paper_trail', '~> 4.0.0.rc' in my Gemfile.
From a data architecture point of view, I suggest that to solve your stated problem of
...when an order is placed, it will
always be able to reference the user
address that was used at the time of
order placement.
... you simply copy the person's address into an Order model. The items would be in OrderItem model. I would reformulate the issue as "An order happens at a point in time. The OrderHeader includes all of the relevant data at that point in time."
Is it non-normal?
No, because the OrderHeader represents a point in time, not ongoing "truth".
The above is a standard way of handling order header data and removes a lot of complexity from your schema as opposed to tracking all changes in a model.
--Stick with a solution that solves the real problem, not possible problems--does anyone need a history of the user's changes? Or do you just need the order headers to reflect the reality of the order itself?
Added: And note that you need to know which address was eventually used to ship the order/invoice to. You do not want to look at an old order and see the user's current address, you want to see the address that the order used when the order was shipped. See my comment below for more on this.
Remember that, ultimately, the purpose of the system is to model the real world. In the real world, once the order is printed out and sent with the ordered goods, the order's ship-to isn't changing any further. If you're sending soft goods or services then you need to extrapolate from the easier example.
Order systems are an excellent case where it is very important to understand the business needs and realities--don't just talk with the business managers, also talk with the front-line sales people, order clerks, accounts receivable clerks, shipping dept folks, etc.
You're looking for the acts_as_audited plugin. It provides an audits table and model to be used in place of your map.
To set it up run the migration and add the following to your user address model.
class UserAddress < ActiveRecord::Base
belongs_to :user
acts_as_audited
end
Once you've set it up, all you need to do is define an address method on order. Something like this:
class Order < ActiveRecord::Base
belongs_to :user
attr_reader :address
def address
#address ||= user.user_address.revision_at(updated_at)
end
end
And you can access the users' address at the time of order completion with #order.address
revision_at is a method added to an audited model by acts_as_audited. It takes a timestamp and reconstructs the model as it was in that point of time. I believe it pieces the revision together from the audits up on that specific model before the given time. So it doesn't matter if updated_at on the order matches a time exactly.
I think this would be as simple as:
Users:
id
name
address_id
UserAddresses:
id
user_id
street
country
previous_address_id
Orders
id
user_id #to get the users name
user_address_id #to get the users address
Then when a user changes their address, you do a sort of "logical delete" on the old data by creating a new UserAddress, and setting the "previous_address_id" field to be the pointer to the old data. This removes the need for your map table, and creates a sort of linked list. In this way, whenever an order is placed, you associate it to a particular UserAddress which is guaranteed never to change.
Another benefit to doing this is that it allows you to following the changes of a users address, sort of like a rudimentary logger.

Ruby on Rails - Optional Associations?

I would like to allow users to write comments on a site. If they are registered users their username is displayed with the comment, otherwise allow them to type in a name which is displayed instead.
I was going to create a default anonymous user in the database and link every non-registered comment to that user. Would there be a better way to do it?
Any advice appreciated.
Thanks.
The problem with creating an anonymous user is then you need to check if a comment was made by a "real" user, or an anonymous one when displaying the name, so that introduces complexity. Plus, if you have a way of viewing their profile page, which may include posting history, you'd need to exclude the anonymous user with an exception.
Generally it's better to have a column on your comments which represents the user's visible name, and just show that if provided, or the registered user's name otherwise. For instance, your view helper might look like this:
class Comment < ActiveRecord::Base
belongs_to :user
def user_name
self.anonymous_name or (self.user and self.user.name) or 'Anonymous'
end
end
This will display the contents of the anonymous_name field of the Comment record, or the user's name if a user is assigned, or 'Anonymous' as a last-ditch effort to show something.
Sometimes it's advantageous to actually de-normalize a lot of the database when dealing with large numbers of comments so you don't have to load in the user table via a join simply to display a name. Populating this field with the user's name, even if they're not anonymous, may help with this, though it does mean these values need to be updated when a username changes, presuming that's even possible.
I think you can make user_id on your comment model nullable since you want to allow non registered users to add comments as well. As far as adding names for the non registered users are concerned, there are two options for that
option 1. Add a column on Comment model and name it like anonymous_user where you will store names of non registered users
option 2. Create a another model AnonymousCommentor with name and comment_id attributes.
If you are going to use anonymous users for other things as well apart from comment in your application then you can make it polymorphic and use a suitable name like AnonymousUser instead of AnonymousCommentor

Live Search / auto_complete + HABTM = possible?

I am attempting to add in a form field that should allow me to add a record into a join table. The table name contains the ids of the following:
log_id
node_id
So naturally, my models is setup as follows:
class Log
has_and_belongs_to_many :nodes
end
class Node
has_and_belongs_to_many :nodes
end
The objective is that when I create a log, I should be able to associate it with an number of nodes (ergo, servers). And since there is a lot of nodes on hand, it seems to make sense to have a textfield where when you enter a node name, it will pop-up a list of nodes to choose from. However, I am having some difficulty getting that accomplished.
I know how to use the autocomplete plugin, (that had came with Rails), but it seems to only accept a string and not with the id - and apparently not across models. I know how to do an AJAX search (though I am not that familiar with Javascript), but again, getting that ID becomes an issue.
I think that in either case, I may be able to figure how to get that value and put that in - the uncertainty is whether one or the other is the correct approach to getting that value. Which one should I concentrate on? Or is HABTM even appropriate in this?

Resources