Rails 4 .joins() issue -- not working - mysql

Model for Plugin:
class Plugin < ActiveRecord::Base
has_many :vulns
end
Model for Vuln:
class Vuln < ActiveRecord::Base
belongs_to :plugin
end
Table for Plugin:
create_table "plugins", force: true do |t|
t.string "name"
end
Table for Vuln:
create_table "vulns", force: true do |t|
t.string "title"
t.integer "plugin_id"
t.string "vulnerability_type"
t.string "fixed_in"
end
When I use rails console using rails c and enter Plugin.select("*").joins(:vulns) it only grabs data from the Plugin table
#<ActiveRecord::Relation [#<Plugin id: 1, name: "xxx">, #<Plugin id: 2, name: "xxx">
This is the query:
SELECT * FROM `plugins` INNER JOIN `vulns` ON `vulns`.`plugin_id` = `plugins`.`id`
However, when I execute this query in mysql, it shows all content from vulns and plugins like its supposed to. For every plugin there is atleast one or more vuln in the database.
So here's the question: How do I get it to fetch content from both tables (plugins and vulns) instead of just plugins?

The vulns values are there, it's just not shown because you are using Plugin model to select, i.e. Plugin.select("*").joins(:vulns).
If you did the following, you will get the value:
> plugin = Plugin.select("*").joins(:vulns)
> plugin.first.title
=> "mytitle"
Because you are querying through Plugin model, you'll see Plugin object.
Another way to test this would be by doing the following:
> plugin = Plugin.select([:name, :title]).joins(:vulns)
=> #<ActiveRecord::Relation [#<Plugin id: 1, name: "xxxx">]>
# It won't show you title even though it's there
> plugin.title
=> "mytitle"

Related

Migration - How to add a UNIQUE constraint to an already populated table?

I have a technicians table which consists only of name and timestamps columns in production, but now I need to use it as a devise model.
class Technician < ActiveRecord::Base
validates_presence_of :name
validates_uniqueness_of :name
end
Using 'rails generate devise technician' led me to a migration with all that is necessary to make my technician model a devise model.
class AddDeviseToTechnicians < ActiveRecord::Migration
def change
change_table(:technicians) do |t|
## Database authenticatable
t.string :email, null: false, default: ""
t.string :encrypted_password, null: false, default: ""
end
end
end
But I can't migrate since email is a 'unique: true' column, nor drop my technicians table. What should I do?
I was thinking of setting the email field of each technician as: name_attribute + "#email.com", but what's the best way to do this?
I'm using a MYSQL database.

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.

Comparing datetimes does not work

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