Comparing datetimes does not work - mysql

I'm creating a Rails application which uses MySQL. I have a table in my DB like this:
create_table "pastes", :force => true do |t|
t.string "title"
t.text "body"
t.string "syntax"
t.boolean "private"
t.datetime "expire"
t.string "password"
t.datetime "created_at"
t.datetime "updated_at"
end
I want to show only the non-expired pastes to people, so I do this:
#pastes = Paste.find(:all, :conditions => "expire < '#{Time.now.to_s(:db)}'")
However, even that returns ALL pastes. Not just those that are not expired yet. Can anyone help me? Thanks
Oh, changing < to > returns no pastes, not even the non-expired ones :(

I would create a named scope within your Paste model:
class Paste < ActiveRecord::Base
named_scope :expired, lambda {
{ :conditions => ["expire < ?", Time.zone.now] }
}
end
Rails 3 version:
class Paste < ActiveRecord::Base
scope :expired, lambda {
where("expire < ?", Time.zone.now)
}
end
Note that the lambda is required to delay the evaluation of Time.zone.now until when the (named) scope is actually invoked. Otherwise, the time that the class was evaluated would be used.
Now you can get all the expired pastes with a simple:
#pastes = Paste.expired
—Pretty clean, huh?

I solved it already. It had to do with timezones. This works:
#pastes = Paste.find(:all, :conditions => "expire > '#{Time.now.utc.to_s(:db)}'")
# ^^^ this does it

Related

Is there a way to preload an arbitrary number of parent associations in Rails?

TL;DR: I have a model that belongs_to :group, where group is another instance of the same model. That "parent" group can also have a parent, and so on up the chain. Is there a way to includes this structure as far up as it goes?
I have a Location model, which looks like this (abridged version):
create_table "locations", force: :cascade do |t|
t.string "name"
t.decimal "lat", precision: 20, scale: 15
t.decimal "long", precision: 20, scale: 15
t.bigint "group_id"
t.string "type"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["group_id"], name: "index_locations_on_group_id"
end
class Location < ApplicationRecord
belongs_to :group, class_name: 'Location', required: false
has_many :locations, foreign_key: 'group_id', dependent: :destroy
end
In other words, it can optionally belong to a "parent" instance of itself, referenced as group.
That parent instance can also belong to a parent instance another level up, as can its parent, etc etc. Elephants, all the way down.
What I'd like to do is string the names of a Location and all its parent instances together, so I end up with something like "Top-level group > Mid-level group > Lowest group > Location". This is fine, and I've implemented that in the model already:
def parent_chain
Enumerator.new do |enum|
parent_group = group
while parent_group != nil
enum.yield parent_group
parent_group = parent_group.group
end
end
end
def name_chain
(parent_chain.map(&:name).reverse + [name]).join(" \u00BB ")
end
The only problem with this, however, is that it will query individually for each parent instance as it gets there (the N+1 problem). Once I'm including several Locations in a single page, this is a lot of queries slowing the load down. I'd like to preload (via includes) this structure as I would for a normal belongs_to association, but I don't know if there's a way to include an arbitrary number of parents like this.
Is there? How do I do it?
Using includes? No. Recursive preloading could be achieved this way though:
Solution #1: True recursion
class Location
belongs_to :group
# Impure method that preloads the :group association on an array of group instances.
def self.preload_group(records)
preloader = ActiveRecord::Associations::Preloader.new
preloader.preload(records, :group)
end
# Impure method that recursively preloads the :group association
# until there are no more parents.
# Will trigger an infinite loop if the hierarchy has cycles.
def self.deep_preload_group(records)
return if records.empty?
preload_group(records)
deep_preload_group(records.select(&:group).map(&:group))
end
end
locations = Location.all
Location.deep_preload_group(locations)
The number of queries will be the depth of the group hierarchy.
Solution #2: Accepting a hierarchy depth limit
class Location
# depth needs to be greather than 1
def self.deep_preload_group(records, depth=10)
to_preload = :group
(depth - 1).times { to_preload = {group: to_preload} }
preloader = ActiveRecord::Associations::Preloader.new
preloader.preload(records, to_preload)
end
end
The number of queries will be the minimum of depth and the actual depth of the hierarchy

Rails active record object save action fails because of a malformed query

I have a problem in Rails in the part of my app that handles landing pages for registration emails. When a user is invited to the app, an Invitation active record instance is created, and user is sent an email with a link containing the id of the invitation (a random string token).
Then, when the link is clicked, I store that token in session, and at one point in the service layer, I update it's 'status' attribute to 'clicked', something like this:
#invitation = Invitation.find_by_id(session[:registration][:invitation_token])
unless #invitation.blank?
session[:registration][:account_details]['referer'] = #invitation.promoter.username
unless #invitation.status == APP_CONFIG['invitation_status']['clicked']
#invitation.status = APP_CONFIG['invitation_status']['clicked']
#invitation.save
end
end
Upon executing the #invitation.save line, I get an active record error:
!! #<ActiveRecord::StatementInvalid: Mysql2::Error: Unknown column 'invitations.' in 'where clause': UPDATE `invitations` SET `status` = 'clicked', `updated_at` = '2015-11-11 11:07:24' WHERE `invitations`.`` = 'fd05ee5a-e790-48cc-9e7e-d30d3e88919b'>
The Invitation's id column name seems to be ommited from the query for some reason, and I can't figure out why. Not sure what is wrong or what to do.
Invitation migration:
class CreateInvitations < ActiveRecord::Migration
def change
create_table :invitations, id: false do |t|
t.string :id, limit: 36, :auto_increment => false
t.string :promoter_id
t.string :email
t.string :status
t.timestamps
end
end
end
Invitation model:
class Invitation < ActiveRecord::Base
#region Callbacks
before_create :set_uid
#endregion
belongs_to :promoter, class_name: 'User', foreign_key: 'promoter_id'
private
def set_uid
self.id = SecureRandom.uuid unless self.id
end
end
You should mark the id column as the primary_key:
create_table :invitations, id: false do |t|
t.string :id, limit: 36, primary_key: true, auto_increment: false
...
I think that in your case the best way to solve this problem is to drop the table and create it again with correct migration. But if there is data that you don't want to loose, you can also try to do it like this:
class MarkIdAsPrimaryKey < ActiveRecord::Migration
def change
change_column :invitations, :id, :string, limit: 36, primary_key: true, auto_increment: false
end
end
Keep in mind that this migration is not reversible, it remains under your responsibility if you will use it ) Good luck !

How to Show Table Entry Name Instead of id on Show or Index

Ok. Asked this question yesterday and since started a whole new rails app to see if starting from scratch helps.
Here's how this app works. A user will create a new countertop and enter their zip code, the countertop size and the type of countertop. Possible countertop types are stored in a model called "Countertype". Users select the countertype through a collection_select method, which lists all the entries in the Countertype table. The responses to this Countertop form are saved in a Countertop table, which has a "countertop_id" integer column.
When the user lands on the Show and then the Index page, I'd like the name of the countertype to be visible instead of the integer.
How do I do this? It's killing me.
Here's my schema:
create_table "countertops", force: :cascade do |t|
t.string "counterzip"
t.string "countersize"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.integer "countertype_id"
end
create_table "countertypes", force: :cascade do |t|
t.string "name"
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
end
Here's my index and show
def index
#countertops = Countertop.all
#countertops = Countertop.includes(:countertype).all
end
def show
#countertops = Countertop.all
#countertops = Countertop.includes(:countertype).all
end
Countertop.rb:
class Countertop < ActiveRecord::Base
has_one :countertype
end
Countertype.rb
class Countertype < ActiveRecord::Base
belongs_to :countertop
end
Show:
<p>
<strong>Counter Type:</strong>
<%= #countertop.countertype.name %>
</p>
Index:
<% #countertops.each do |countertop| %>
<tr>
<td><%= countertop.counterzip %></td>
<td><%= countertop.countertype.name %>
Here's a readout from my console for both tables.
Countertop.last
Countertop Load (0.2ms) SELECT "countertops".* FROM "countertops" ORDER BY "countertops"."id" DESC LIMIT 1
=> #<Countertop id: 1, counterzip: "19111", countersize: "100", created_at: "2015-10-01 20:44:29", updated_at: "2015-10-01 20:44:29", Countertype_Id: 1>
2.2.1 :029 >
Countertype.last
Countertype Load (0.7ms) SELECT "countertypes".* FROM "countertypes" ORDER BY "countertypes"."id" DESC LIMIT 1
=> #<Countertype id: 1, name: "Granite", created_at: "2015-10-01 20:15:12", updated_at: "2015-10-01 20:15:12">
Heres's the error message:
SQLite3::SQLException: no such column: countertypes.countertop_id: SELECT "countertypes".* FROM "countertypes" WHERE "countertypes"."countertop_id" = ? LIMIT 1
Changing the show to <%= #countertops.countertype_id %> displays a "1".
What do I need to fix to have it display "Granite" instead of "1" ??
Thanks!!
It looks like you have your associations backwards in the two models. belongs_to should be listed in the model whose table contains the association column (countertype_id in this case). has_one relies on the associated model to have this column.
A couple additional observations:
According to the output from Countertop.last, Countertype_Id has uppercase characters. This might not cause immediate problems with sqlite, but it may be worth changing to avoid any future problems, as Rails will assume column names are always downcased unless explicitly told otherwise.
Your #show controller method has #countertops defined (plural), but the show view uses just #countertop.

How to save data directly from the migration file in ruby on rails

In my ROR project, I came across an issue. Previous developer of the project has save an array of data into a single column for a particular field. But now I have to get those data and save it to another separate table with the current table id. Following is my code for get the details from the db:
#ar = AResponse.select("id, selected_barriers");
#ar.each do |p|
p.selected_barriers.each do |barrier|
end
end
What I wanted is I have to save the p.id and barrier to a new table and I have to do it in a migration file. So when the migration runs, it will get all the existing details from AResponse and save it to the new one. Can I do it only with migration file? If so how can I do it?
This is my full code
I tried but its not working: Following is my code
class AnalysisBarriers < ActiveRecord::Migration
def self.up
end
def change
create_table :analysis_barriers do |t|
t.integer :analysis_response_id, :null => false
t.string :barrier
end
#analysis_response = AnalysisResponse.select("id, selected_barriers");
#analysis_response.each do |p|
p.selected_barriers.each do |barrier|
AnalysisBarriers.create(:analysis_response_id => p.id, :barrier => barrier)
end
end
end
def self.down
drop_table :analysis_barriers
end
end
Thanks
Yes you can do data changes after migrations. Whatever data changes you want to do, you can do in change function or up function. Take the following example
class Event < ActiveRecord::Migration
def change
create_table events do |t|
t.datetime :starts_at
t.datetime :ends_at
t.timestamps
end
Event.create(:starts_at => Time.now, :ends_at => Time.now+1)
end
end
Above statement will add data to your table just after its creation.

Create new ruby model instance with associations

This is perhaps a basic question, but it's currently driving me nuts...Perhaps i'm missing something, since i'm diving so deep in the code, but the question is:
How can I make my object with associations?
I have the following migration scripts:
class CreateConcepts < ActiveRecord::Migration
def self.up
create_table :concepts do |t|
t.integer :language_id, :null => false
t.string :uri
t.integer :isco_code
t.timestamps
end
end
def self.down
drop_table :concepts
end
end
class CreatePrefLabels < ActiveRecord::Migration
def self.up
create_table :pref_labels do |t|
t.integer :language_id
t.integer :concept_id
t.string :value
t.timestamps
end
end
def self.down
drop_table :pref_labels
end
end
class CreateLanguages < ActiveRecord::Migration
def self.up
create_table :languages do |t|
t.string :code
t.timestamps
end
end
def self.down
drop_table :languages
end
end
The classes for these objects look like the following:
class Concept < ActiveRecord::Base
belongs_to :language
has_one :pref_label
validates_uniqueness_of :isco_code
end
class PrefLabel < ActiveRecord::Base
belongs_to :language
belongs_to :concept
validates_uniqueness_of :value
end
class Language < ActiveRecord::Base
has_many :concepts
has_many :pref_labels
validates_uniqueness_of :code
end
So if I remember my Ruby lessons correctly, the following code should be perfectly fine:
concept = Concept.first
concept.pref_label
language = Language.find(:code => "en")
language.pref_labels
language.concepts
So i've written the following line of code. The internals are a bit dark, but I'm 100% sure the hashes generated from the JSON data are correct. Checked this with the debugger:
# This function will retrieve all the top most concepts from the ESCO API in the
# specified language as an array of Ruby objects, each represented by the relevant class.
# If the relevant concept does not exist in the database, the entry is automatically created.
# The function will fall back to English by default if no language is specified
def self.top_concepts(lang = Language.find_by_code('en') || Language.create(:code => 'en'))
concepts = []
json = HTTParty.get "#{Setting[:api]}/getTopmostConcepts?language=#{lang.code}"
collection = JSON.parse json.parsed_response
collection.each do |c|
language = Language.find_by_code c['language'] || Language.create(:code => c['language'])
concept = Concept.create(:language => language, :uri => c['uri']['uri'], :isco_code => c['iscoCode'].to_i)
label = PrefLabel.find_by_concept_id(concept.id) || PrefLabel.create(:language_id => language.id, :concept_id => concept.id, :value => c['prefLabel']['string'])
concept.pref_label= label
concept.save
concepts << concept
end
return concepts
end
The problem I'm having now is that my PrefLabels are :
1) not beeing created all the time
2) are never linked to my concept objects.
What am I missing?
First, I would suggest simplifying this snippet of your code a bit like so:
language = Language.find_or_create_by_code(c['language'])
concept = langauge.concepts.create(:uri => c['uri']['uri'], :isco_code => c['iscoCode'].to_i)
concept.pref_label = PrefLabel.find_or_create_by_concept_id(:concept_id => concept.id, :language_id => language.id, :value => c['prefLabel']['string'])
That's untested so it might not be quite right.
Second, check to see if validation is passing or not. valid? will return false if there is a validation issue of some sort -- for example, if the PrefLabel value isn't unique.
I suspect you need to scope your validations, although that's just a guess of course.