Rails 3 scope query - ruby

I have a model named "SessRequest", which contains fields "sess_start" (start datetime of session) and "sess_duration" (session duration in hours). So, end time of a session is: sess_start + sess_duration.hours
I need to filter out all sessions whose "end_time" is behind current time.
For this the corresponding model scope looks like following:
SESSION_ASSUMED_PAST_AFTER_HRS = 3
scope :past,
lambda{where('(sess_start < :current_time) AND (sess_start IS NOT NULL)', :current_time => 0.days.from_now - SESSION_ASSUMED_PAST_AFTER_HRS.hours)};
I want to make this scope based on "sess_duration" database field rather than constant SESSION_ASSUMED_PAST_AFTER_HRS.
How can this be done while still keeping it database agnostic?

You can use squeel gem for creating complex SQL queries and various RDBMS systems.
Your code will looks like:
SESSION_ASSUMED_PAST_AFTER_HRS = 3
scope :past, where { (sess_start < SESSION_ASSUMED_PAST_AFTER_HRS.hours.ago) & (sess_start != nil ) }

Related

Rails 4 - Comparing two variables in scope

How do you compare two variables in a custom scope with Rails 4 ?
Documentation and common examples always show basic comparisons with table attributes
Like in this example :
class Post < ActiveRecord::Base
scope :created_before, ->(time) { where("created_at < ?", time) }
end
But in my case, I want to compare a datetime from my Model to a date string coming from a form submission
scope :departure, -> (departure) { where("departure = ?", "%#{departure}%")}
The problem is that I want to do some manipulation to my model variable before the comparison (ex, convert to string or change format)
I tried with different methods like this one
def self.departure(departure)
#date_without_time = flights.departure.to_date.to_s
where(#date_without_time, "= ?", departure)
end
But the condition end up like this (2012-01-03) instead of (departure = "%#{departure}%")
Is there a better way to do it?
To be more general, how do you create methods or scope to compare two variables instead of only comparing one variable to a model attribute?
In this case, I want to compare my model attribute which is a Datetime with a form submitted string date, so before I can compare the two dates I need to do some treatment on my model attribute since I need to ignore the time part and format it to a string
Update :
I ended up making it work using this method, but I'd still like to know if it's possible to call class or instance method on a Model column in a scope or if there is a better way to handle the Datetime/form date comparison in a better way
scope :departure_s, -> (departure) { where("departure > ?", departure.to_datetime.beginning_of_day)}
scope :departure_e, -> (departure) { where("departure < ?", departure.to_datetime.end_of_day)}
scope :departure, -> (departure) { departure_e(departure).departure_s(departure)}
You're taking the wrong approach trying to manipulate models. You're running a query to return models, you don't have any models to manipulate yet.
You can accomplish what you're trying to do on the database level with SQL. I would write the scope like this
if the departure variable is a string that represents a date in the format 'yyyy-mm-dd' you can do
scope :departure, -> (departure) { where("date(departure) = ?", departure )}
This assumes you're using postgres, it might be slightly different for mysql. the date() function converts a datetime to date on the database elevel

Query using EF "Include" and including the related tables on Where

So far, I thought I could do:
var num = db.MyTable.Include(x => x.RelatedTable)
.Count( x.idTenant == CurrentTenantID && x.Active &&
x.RelatedTable.SomeProperty.Value == true);
This always return zero records.
Am I assuming wrongly that Including the RelatedTable I can use it in the where part?
By the way... the "SomeProperty" is Nullable, that is why the ".Value".
I'm using Entity Framework 4.1. (Database first)
Are you trying to get the number of records? If so, why do you even need the Include? Entity Framework will lazy-load the RelatedTable entity set for you when it evaluates your Count condition. Also, if SomeProperty is a bool?, you should check if it has a value before you check the value itself.
var num = db.MyTable.Count(x =>
x.idTenant == CurrentTenantID &&
x.Active &&
(x.RelatedTable.SomeProperty.HasValue &&
x.RelatedTable.SomeProperty.Value));
You don't need to use Include if you only want to access navigation property in Where part. Include is only used to fetch (eager load) related records together with the main record from database to your application but it doesn't make sense if you only want to count records.

NHibernate IQueryable doesn't seem to delay execution

I'm using NHibernate 3.2 and I have a repository method that looks like:
public IEnumerable<MyModel> GetActiveMyModel()
{
return from m in Session.Query<MyModel>()
where m.Active == true
select m;
}
Which works as expected. However, sometimes when I use this method I want to filter it further:
var models = MyRepository.GetActiveMyModel();
var filtered = from m in models
where m.ID < 100
select new { m.Name };
Which produces the same SQL as the first one and the second filter and select must be done after the fact. I thought the whole point in LINQ is that it formed an expression tree that was unravelled when it's needed and therefore the correct SQL for the job could be created, saving my database requests.
If not, it means all of my repository methods have to return exactly what is needed and I can't make use of LINQ further down the chain without taking a penalty.
Have I got this wrong?
Updated
In response to the comment below: I omitted the line where I iterate over the results, which causes the initial SQL to be run (WHERE Active = 1) and the second filter (ID < 100) is obviously done in .NET.
Also, If I replace the second chunk of code with
var models = MyRepository.GetActiveMyModel();
var filtered = from m in models
where m.Items.Count > 0
select new { m.Name };
It generates the initial SQL to retrieve the active records and then runs a separate SQL statement for each record to find out how many Items it has, rather than writing something like I'd expect:
SELECT Name
FROM MyModel m
WHERE Active = 1
AND (SELECT COUNT(*) FROM Items WHERE MyModelID = m.ID) > 0
You are returning IEnumerable<MyModel> from the method, which will cause in-memory evaluation from that point on, even if the underlying sequence is IQueryable<MyModel>.
If you want to allow code after GetActiveMyModel to add to the SQL query, return IQueryable<MyModel> instead.
You're running IEnumerable's extension method "Where" instead of IQueryable's. It will still evaluate lazily and give the same output, however it evaluates the IQueryable on entry and you're filtering the collection in memory instead of against the database.
When you later add an extra condition on another table (the count), it has to lazily fetch each and every one of the Items collections from the database since it has already evaluated the IQueryable before it knew about the condition.
(Yes, I would also like to be the extensive extension methods on IEnumerable to instead be virtual members, but, alas, they're not)

Table with a foreign key

how can I build a table of "orders" containing "IdOrder", "Description" and "User"?... the "User" field is a reference to the table "Users", which has "IdUser" and "Name". I'm using repositories.
I have this repository:
Repository<Orders> ordersRepo = new OrderRepo<Orders>(unitOfWork.Session);
to return all Orders to View, I just do:
return View(ordersRepo.All());
But this will result in something like:
IdOrder:1 -- Description: SomeTest -- User: UserProxy123ih12i3123ih12i3uh123
-
When the expected result was:
IdOrder:1 -- Description: SomeTest -- User: Thiago.
PS: I don't know why it returns this "UserProxy123ih12i3123ih12i3uh123". In Db there is a valid value.
The View:
It is showed in a foreach (var item in Model).
#item.Description
#item.User //--> If it is #item.User.Name doesn't work.
What I have to do to put the Name on this list? May I have to do a query using LINQ - NHibernate?
Tks.
What type of ORM are you using? You mention "repositories" but does that mean LinqToSql, Entity Framework, NHibernate, or other?
It looks like you are getting an error because the User field is not loaded as part of the original query. This is likely done to reduce the size of the result set by excluding the related fields from the original query for Orders.
There are a couple of options to work around this:
Set up the repository (or context, depending on the ORM) to include the User property in the result set.
Explicitly load the User property before you access it. Note that this would be an additional round-trip to the database and should not be done in a loop.
In cases where you know that you need the User information it would make sense to ensure that this data in returned from the original query. If you are using LinqToSql take a look at the DataLoadOptions type. You can use this type to specify which relationships you want to retrieve with the query:
var options = new DataLoadOptions();
options.LoadWith<Orders>(o => o.User);
DataContext context = ...;
context.LoadOptions = options;
var query = from o in context.Orders
select o;
There should be similar methods to achive the same thing whatever ORM you are using.
In NHibernate you can do the following:
using (ISession session = SessionFactory.OpenSession())
{
var orders = session.Get<Order>(someId);
NHibernateUtil.Initialize(orders.User);
}
This will result in only two database trips (regardless of the number of orders returned). More information on this can be found here.
In asp.net MVC the foreign key doesn't work the way you are using it. I believe you have to set the user to a variable like this:
User user = #item.User;
Or you have to load the reference sometimes. I don't know why this is but in my experience if I put this line before doing something with a foreign key it works
#item.UserReference.load();
Maybe when you access item.User.Name the session is already closed so NHib cannot load appropriate user from the DB.
You can create some model and initialize it with proper values at the controller. Also you can disable lazy loading for Orders.User in your mapping.
But maybe it is an other problem. What do you have when accessing "#item.User.Name" from your View?

Ruby: DataMapper and has n, :xyz, :through => Resource

I've encountered following issue:
there are 2 models: X and Y, they're associated with each other like this: has n, :<name>, :through => Resouce; when i'm doing something like x.ys = array_with_500_ys it takes really long time because DataMapper inserts only one association per query (insert into xs_ys(x_id, y_id) values(xid, yid)). This takes really long.
The question is: how to make this faster?
Thanks.
Because DataMapper has abstracted the 'back end', the standard behaviour is to insert one record at a time as SQL (or whatever you are using).
Assuming you are using an SQL backend, such as Postgres, you could drop back to raw SQL, and do the following:
x = X.first
query = "INSERT INTO xs_ys(x_id, y_id) VALUES"
vals = []
array_with_500_ys.each do |y|
vals << "(#{x.id}, #{y.id})"
end
repository.adapter.execute(query + vals.join(','));
This creates one 'insert', passing all records to be inserted. Not sure if this would be any faster, but you could put it into a background job if you need the app not to time out for the user.

Resources