I'm relative new to Rails, so I don't know if my way to solve the problem is the correct, but there's some problem with it
I have installed in my PC MySQL, but Heroku uses PostgreSQL, so I'm designing a solution to work with both DBMS in certain problem.
I have the next code:
begin
#products_with_valid_offers = Product.joins(:variants).active.discount_valid.discount_date_valid_mySQL
rescue ActiveRecord::StatementInvalid
#products_with_valid_offers = Product.joins(:variants).active.discount_valid.discount_date_valid_postgreSQL
end
And the scopes are:
scope :active, includes([:assets, :store]).where(:active => true, :deleted_at => nil, :stores => { :deleted_at => nil, :active => true }).order("products.created_at DESC")
scope :discount_date_valid_mySQL, where('DATE_FORMAT(NOW(),"%Y-%m-%d 23:59:59") + '" BETWEEN discount_from AND discount_to')
scope :discount_date_valid_postgreSQL, where('now()::date BETWEEN discount_from AND discount_to')
As you see, I need 2 different forms to manage the date format, one with each DBMS.
The issue is that the flow never enters in the exception. If #products_with_valid_offers is a SQL Error in MySQL, never enters to the rescue block to execute the PostgreSQL line, and it returns an error.
Some help, please?. :D
Your quotes are messed up in discount_date_valid_mySQL, you want to say this:
scope :discount_date_valid_mySQL, where("DATE_FORMAT(NOW(),'%Y-%m-%d 23:59:59') BETWEEN discount_from AND discount_to")
That at least is valid Ruby code.
That said, your current approach is bad in various ways:
You're ensuring that you always have an exception in production.
You're developing on top of one database but deploying on top of another, this alone will cause you all sorts of problems.
You should be using just date_format(now(), '%Y-%m-%d') in your MySQL version.
There are probably other issues but there's no need to spend more time picking apart your code: there is a better way. Both MySQL and PostgreSQL (and even SQLite) support current_date so you can just use one thing for both:
scope :discount_date_valid, where('current_date between discount_from and discount_to')
Assuming of course that you want everything to assume UTC. If you want things to use some other time zone then:
Store your discount_from and discount_to as date columns, dates don't have timezones in SQL. Presumably you're doing this already but I just want to make sure.
Adjust your scope to get the current date from your client code (which is presumably configured to use the right timezone):
def self.discount_date_valid
where(':current_date between discount_from and discount_to', :current_date => Date.today)
end
You have to use a class method for the scope to ensure that Date.today is evaluated at the right time (i.e. when the scope is used instead of when the class is loaded).
And you really should develop on top of PostgreSQL if you're deploying on top of PostgreSQL. You should even make sure you're developing and deploying on the same version of PostgreSQL. Developing on top of one stack and deploying on another causes all sorts of pointless frustration and confusion.
Related
For fun, I'd like to see the set of users (in a Rails database) who were created during a specific hour range (2AM - 5AM to be specific), but on any day. They all have the typical created_at field. I think I know how to extract the hour from this field for one user, and then see if it falls in a range--but how I do do this for all of them? Should I just loop through them? (Even as I write it, that sounds naive).
The first part of Sontyas answer is the easy solution in rails.
I would however move that part to it's own place inside your class to separate your code from the framework a bit more.
# app/models/user.rb
class User < ActiveRecord::Base
# ...
def self.get_users_created_between(start_time, end_time)
User.where("TIME(created_at) BETWEEN TIME(?) AND TIME(?)", start_time, end_time)
end
# ...
end
And use it like this:
irb> User.get_users_created_between(Time.parse("2pm"), Time.parse("5pm"))
This provides you with a couple of benefits:
You can reuse it all over your code without ever having to worry about the syntax of the where or time ranges.
If for some weird reason rails decides to change the interface for this, you only need to edit one method and not code in a thousand places all over your project.
You can easily move this piece of code out of the user.rb if you feel that user.rb gets to big. Maybe to some dedicated finder or query class. Or to something like a repository pattern.
PS: Time functions may vary between different DBMS like MySQL, Postgresql, MSSQL etc. I don't know, if there is generic way to do this. This answer is for MySQL.
Try this,
User.where(created_at: Time.parse("2pm")..Time.parse("5pm"))
Or something like this
User.select { |user| user.created_at.hour.between?(2, 5) }
To return users that where created between two hours on any given day, use this:
User.where('HOUR(created_at) BETWEEN ? AND ?', 2, 5)
Please note that HOUR(created_at) only works for MySQL. The syntax in Postgresql is extract(hour from timestamp created_at) and strftime('%H' created_at) in SQLite.
TL;DR? See Edit 2
I've got a little Rails application that has a few different sort of games people can play: it's based around sports, so they can pick the winners of each game every week (model PickEm, attribute correct boolean with nil for unfinished games), and predict the outcome of a specific team's game (model Guess, attribute score with integer, nil for unfinished games). Every User has_many PickEms and Guesses. And I'm trying to display standings (correct/total - total being all non-nil, score/total possible).
What I'm finding is that I can gather the users and their associated records, but in trying to display standings I'm discovering that every single User is triggering another query - slow and not sustainable as the user base increases. That's because #user.pick_em_score is pick_ems.where(correct: true).size and #user.guess_Score is guesses.where.not(score: nil).sum(:score). So I call user.pick_em_score and it runs that query. I feel like there should be a way to get every User, as well as these specific counts, at once, rather than buffering a whole bunch of needless extra stuff.
What I need:
User record
User.pick_em_score (calculated by counting correct records)
User.pick_ems count where NOT NULL
User.guesses_score (calculated by guesses.sum(:score))
User.guesses count where NOT NULL
Most of the stuff I find on Rails's ActiveRecord helpers, especially related to calculations, is for retrieving only the calculation. It looks like I'll probably need to delve directly into select() etc. But I can't get it working. Can someone point me in the right direction?
Edit
For clarification: I'm aware that I can write this information to the User model, but this is overly restrictive: next season, I'll need to add a new column to the User for that year's results, etc. In addition, this is a third degree of callback updating related models – the Match model already updates related PickEms and Guesses on save. I'm looking for the simplest ActiveRecord query or queries to be able to work with this information, as indicated by the title. Ideally one query that returns the above information, but if it needs to a few, that's OK.
I used to work directly in MySQL with PHP, but those skills have rusted (in raw MySQL, I imagine, I'd have several sub-select statements to help pull these counts) and I'd also like to be able to use Rails's ActiveRecord helpers and such, and avoid constructing raw SQL as much as possible.
Second Edit:
I seem to have it down to one call that starts to work, but I'm writing a lot of SQL. It's also brittle, IMO, and trying to run with it has failed. It also looks like I'm just pushing the million singular SELECT queries from Rails right into SQL, but that may still be a step up.
User.unscoped.select('users.*',
'(SELECT COUNT(*) FROM pick_ems WHERE pick_ems.user_id = users.id AND pick_ems.correct) AS correct_pick_ems',
'(SELECT COUNT(*) FROM pick_ems WHERE pick_ems.user_id = users.id AND pick_ems.correct IS NOT NULL) AS total_pick_ems',
'(SELECT SUM(guesses.score) FROM guesses WHERE guesses.user_id = users.id AND guesses.score IS NOT NULL) AS guesses_score',
'(SELECT COUNT(*) FROM guesses WHERE guesses.user_id = users.id AND guesses.score IS NOT NULL) AS guesses_count' )
The issue seems to be: is there a way to use Rails, and not raw SQL, to link up users.id that we see there with these subqueries? Or just … a better way to construct this, in general?
In addition, I'm running another set of SELECTs for the WHERE, which would hinge on total_pick_ems and guesses_count being > 0 but since I can't use those aliased columns, I have to call the SELECT one more time.
Welcome to AR. Its really only good for simple CRUD like queries. Once you actually want to query your data in anger it just doesn't have the capababilities to do the queries you want without resorting to wholesale SQL strings and often abandoning the ability to chain as a result.
Its precisely why I moved to Sequel as it does have the features to compose queries using a much fuller SQL feature set, including join conditions, window functions, recursive common table expressions, and advanced eager loading. The author is incredibly responsive and documentation is excellent compared to AR and Arel.
I don't expect you will like this answer but a time will come when you will start to look outside the opinionated components that come with rails which I have to say are hardly best of breed. Sequel also sped my application up many times over what I was able to get with AR as well, it not just developer happiness, it means less servers to run. Yes it will be a learning curve but IMO its better to learn tools that have your back covered.
Joins might work. Smthing like below
User.unscoped.joins(:guesses).joins(:pick_ems).
where("guesses.score IS NOT NULL").
select("users.*,
sum(guesses.score) as guesses_score,
count(guesses.id) as guesses_count,
count(case when pick_ems.correct = True then 1 else null end)
as correct_pick_ems,
count(case when pick_ems.correct != null then 1 else null end)
as total_pick_ems,
").
group("users.id")
If you need this information for a limited number of users at a time then above query or eager loading (User.includes(:guesses, :pick_ems)) with class methods like
def correct_pick_ems
pick_ems.count(&:correct)
end
would work.
However If you need this information for all the users most of the time, cached counters within the users table would be more optimal.
What you need is some sort of custom (smart) counter_cache to count only at certain conditions (e.g correct is true)
You can achive this using conditional after_save & after_destroy triggers to build your own custom counter_cache that looks like this:
class PickEm
belongs_to :user
after_save :increment_finished_counter_cache, if: Proc.new { |pick_em| pick_em.correct }
after_destroy :decrement_finished_counter_cache, if: Proc.new { |pick_em| pick_em.correct }
private
def increment_finished_counter_cache
self.user.update_column(:finished_games_counter, self.user.finished_games_counter + 1) #update_column should not trigger any validations or callbacks
end
def decrement_finished_counter_cache
self.user.update_column(:finished_games_counter, self.user.finished_games_counter - 1) #update_column should not trigger any validations or callbacks
end
end
Notes:
Code not tested (only to show the idea)
Some guys said it's better to avoid naming custom counters as rails name them (foo_counter_cache)
You should benchmark it, but my hunch is that adding all of that data into a single SELECT isn't going to be much faster than breaking it up into separate SELECTs (I've actually had cases where the latter was faster). By breaking it up, you can also stick to more ActiveRecord and less raw SQL, e.g.:
user_ids_to_pick_em_score = User.joins(:pick_ems).where(pick_ems: {correct: true}).group(:user_id).count
user_ids_to_pick_ems_count = User.joins(:pick_ems).where.not(pick_ems: {correct: nil}).group(:user_id).count
user_ids_to_guesses_score = Hash[User.select("users.id, SUM(guesses.score) AS total_score").joins(:guesses).group(:user_id).map{|u| [u.id, u.total_score]}]
user_ids_to_guesses_count = User.joins(:guesses).where.not(guesses: {score: nil}).group(:user_id).count
Edit: To display them, you could do like so:
<%- User.select(:id, :name).find_each do |u| -%>
Name: <%= u.name %>
Picks Correct: <%= user_ids_to_pick_em_score[u.id] %>/<%= user_ids_to_pick_ems_count[u.id] %>
Total Score: <%= user_ids_to_guesses_score[u.id] %>/<%= user_ids_to_guesses_count[u.id] %>
<%- end -%>
I've got a really, really odd problem manifesting on a big Rails e-commerce app and thought I'd see if anyone has good insight. I have an"Order" model with many associations. If I create a new instance, and then set one particular column value and "save!" the "save!" is succeeding without errors, but the change isn't actually persisted to the DB. I'll run through the scenario below:
#order = Order.create!(<some attributes>)
=> true
#order.shipping_method_id
=> 1
#order.shipping_method_id = 203
=> 203
#order.save!
=> true
#order.shipping_method_id
=> 1
To try and debug this I actually prepended a before_save filter and I can see that when this first filter is called after setting the value, it is correct ("203") BUT the very next before_save after the 6-or-so built-in "autosave_foo_bar_quux" filters (for nested associations) it is back to "1".
Oddly, if I just reload the order (#order.reload), change the column value and save! the update does succeed.
In both cases, doing #order.changed shows that ActiveModel recognizes the column value change for shipping_method_id. In the first, though, the SQL logging shows that the order row is not updated.
I feel like I'm going insane. Any ideas? Also, let me know if there's anything else I can post here for more context.
I have a Spring / Hibernate project and I am trying to store a date into the database but it's not working. It must be something stupid but I have no idea what I am doing wrong.
Here is my code:
user.setFailedPasswordAnswerAttemptCount(0);
user.setLastLoginDate(new Date());
user.setIsOnline(true);
The other two variables (failedPasswordAnswerAttemptCount and isOnline) are getting written to the database without issue. I have also tried it with just passing a java.util.Date instead of a java.sql.Timestamp...same result. Here is how the property is defined on the user object:
private Date lastLoginDate;
#Temporal(TemporalType.TIMESTAMP)
#Column(name="last_login_date")
public Date getLastLoginDate() {
return this.lastLoginDate;
}
public void setLastLoginDate(Date lastLoginDate) {
this.lastLoginDate = lastLoginDate;
}
Here is the column definition:
`last_login_date` datetime DEFAULT NULL
Any help? I don't even know what else to look for as this should be working.
Some more detail about the error: No errors or strange messages in the hibernate log. The hibernate log is showing a parameterized query but it isn't telling me what it is actually writing. It looks like it's not updating the column at all. In other words, if there is already a date there it doesn't change, or if it is null it doesn't change.
Update: I have looked at the logs and it looks like hibernate does write the proper data, but then immediately writes the incorrect data again. I see the following entry in the log:
11:15:12.280 [http-bio-8080-exec-26] TRACE o.h.e.def.AbstractSaveEventListener - detached instance of: com.hi.model.User
11:15:12.280 [http-bio-8080-exec-26] TRACE o.h.e.def.DefaultMergeEventListener - merging detached instance
And right after that I see it putting the old value back in for the lastLoginDate.
Why are you using
Date date = new Date();
user.setLastLoginDate(new Timestamp(date.getTime()));
and not just this?
user.setLastLoginDate(new Date());
First - You may not want to use Date and Timestamp at the same time.(e.g. for collections, etc)
There are some classes in the Java platform libraries that do extend an instantiable
class and add a value component. For example, java.sql.Timestamp
extends java.util.Date and adds a nanoseconds field. The equals implementation
for Timestamp does violate symmetry and can cause erratic behavior if
Timestamp and Date objects are used in the same collection or are otherwise intermixed.
The Timestamp class has a disclaimer cautioning programmers against
mixing dates and timestamps. While you won’t get into trouble as long as you
keep them separate, there’s nothing to prevent you from mixing them, and the
resulting errors can be hard to debug. This behavior of the Timestamp class was a
mistake and should not be emulated. (Bloch, Effective Java, 2nd Ed.)
Second - I checked your examples, and it works fine for me on mysql-connector(5.1.21) / hibernate (4.0.1)
I prepared simple test project with arquillian integration test(You need to prepare jboss before running it):
https://github.com/rchukh/StackOverflowTests/tree/master/13803848
If you can provide some more information it might help - hibernate version, mysql version, mysql engine(MyISAM, InnoDB, etc.)
Otherwise it is possible that this is just a misconfiguration.
I found the problem. I am refactoring some code and it looks like I was doing this:
//get user object
User user = getUser();
//call a function which modifies user
functionModifiesUser();
//modify user
user.blah = blah;
entityManager.merge(user);
So the parent function had a stale copy of the user object when I tried to save it. Actually, removing the merge statement was enough to fix it. But I have refactored the code to put all this in one place.
Setting the column last_login_date as timestamp should work, at least works for me.
I've got a Rails 3.1 app running a mysql server for storing data.
90% of the data in the app fits really well in a relational database.
The other 10% is a pretty large hash that I need to pull out, change, and put back fairly quickly. It is a fairly large query in mysql to bring all these data pieces together, across multiple tables, but once I have it once, I figured I would save it as a hash, and the user can interact with the hash and make changes. Those changes never get persisted back to mysql, as mysql doesn't need them.
so, I decided to add redis to my rails application and the redis-objects gem was recommended by a friend.
I have created my active_hash model and controller as so
class ActiveHash < ActiveRecord::Base
include Redis::Objects
end
class ActiveHashesController < ApplicationController
def show
#this is a big query with a bunch of merges, but simplified here as it isn't important
active = Game.find(params[:id])
active_hash_in_redis = ActiveHash.new()
if active_hash_in_redis.save
render :json => active_hash
else
render :text => "didn't save"
end
end
end
when I navigate to active_hashes/id, I get an error that there is no MySQL table active_hashes, which is right, because that is supposed to be my redis db, as defined in the model.
can anybody explain to me how to use both dbs in my app, and/or point me to a tutorial on doing this? I haven't been able to find anything. Is using Redis-Objects the wrong way to go with this?? Any other recommendations?
It turns out this was a bit of confusion on my part, but hopefully this helps somebody else.
I didn't end up using the redis-objects, gem, I installed redis-rb with gem redis.
Then I set-up the config file as
require 'redis'
$redis = Redis.new()
My model is actually blank at the moment, in my controller, I've used
class ActiveHashesController < ApplicationController
def show
#this is a big query with a bunch of merges, but simplified here as it isn't important
active = Game.find(params[:id])
$redis.set params[:id], active.to_json
get_game = $redis.get params[:id]
render :json => get_game
end
end
end