RoR: database level referential integrity - ruby-on-rails

I have this model:
class Book < ApplicationRecord
has_many :pages, dependent: :destroy
end
And this one:
class Page < ApplicationRecord
belongs_to :book
end
The migration for the Book is:
class CreateBooks < ActiveRecord::Migration[5.0]
def change
create_table :books do |t|
end
end
end
And the migration for Page is:
class CreatePages < ActiveRecord::Migration[5.0]
def change
create_table :pages do |t|
t.references :book, index: true, null: false
end
end
add_foreign_key :pages, :books, on_delete: :cascade
end
Additionally I got some seeds:
Book.create!(
pages: [
Page.new,
Page.new,
Page.new
]
)
rake db:migrate, rake db:seed and all that jazz. I jump into rails c:
Book.first
Book Load (0.1ms) SELECT "books".* FROM "books" ORDER BY "books"."id" ASC LIMIT ? [["LIMIT", 1]]
=> #<Book id: 1>
Cool....now?
Page.count
(0.3ms) SELECT COUNT(*) FROM "pages"
=> 3
Makes total sense. Next:
Book.first.destroy
Book Load (0.2ms) SELECT "books".* FROM "books" ORDER BY "books"."id" ASC LIMIT ? [["LIMIT", 1]]
(0.1ms) begin transaction
Page Load (0.1ms) SELECT "pages".* FROM "pages" WHERE "pages"."book_id" = ? [["book_id", 1]]
SQL (0.1ms) DELETE FROM "pages" WHERE "pages"."id" = ? [["id", 1]]
SQL (0.0ms) DELETE FROM "pages" WHERE "pages"."id" = ? [["id", 2]]
SQL (0.0ms) DELETE FROM "pages" WHERE "pages"."id" = ? [["id", 3]]
SQL (0.1ms) DELETE FROM "books" WHERE "books"."id" = ? [["id", 1]]
Yay! Almost there...after seeding again I do this:
Book.first.delete
Book Load (0.1ms) SELECT "books".* FROM "books" ORDER BY "books"."id" ASC LIMIT ? [["LIMIT", 1]]
SQL (144.0ms) DELETE FROM "books" WHERE "books"."id" = ? [["id", 2]]
WTF?
Page.count
(0.1ms) SELECT COUNT(*) FROM "pages"
=> 3
I know delete does not trigger callbacks, so that dependent: :destroy won't help me here. But the foreign key? Hello? I want referential integrity in my database level!! What am I doing wrong? I've tried more things, like moving the on_delete: :cascade to the field definition:
def change
create_table :pages do |t|
t.references :book, index: true, null: false
end
end
But...nope, same result. I've searched and read the ActiveRecord documentation twice, and a few other questions in SO pointed me to my current setup (which is not the project I'm working on, but rather a newly generated one with the same basic configuration to replicate the error - yea, it fails there too), but I just can't put my finger on what's wrong. Perhaps it's just too late and I'm getting too tired. Help? Does Rails even support this? I'm using v5, far, far ahead of 4.2 where the constraints at db level were integrated. My db/schema.rb looks like this:
ActiveRecord::Schema.define(version: 20160218232358) do
create_table "books", force: :cascade do |t|
end
create_table "pages", force: :cascade do |t|
t.integer "book_id", null: false
t.index ["book_id"], name: "index_pages_on_book_id"
end
end
No trace of foreign keys?

For you're testing you are probably using SQLite, here only mysql, mysql2 and postgres are mentioned therefore I think rails does not support foreign keys on SQLite: http://edgeguides.rubyonrails.org/4_2_release_notes.html#foreign-key-support
It is also stated in another so post: https://stackoverflow.com/a/28801481/4560144

Related

Foreign key constraint do cause N+1 query but without constraint, eager-loading works properly

I have added added base_currency_id & base_currency_id2 with & without foreign-key constraint respectively as below in markets table,
def change
create_table :currencies do |t|
t.string :code
t.timestamps
end
create_table :market2 do |t|
t.string :code
t.integer :base_currency_id
t.integer :base_currency_id2
t.foreign_key :currencies, column: :base_currency_id2
t.integer :quote_currency_id
t.timestamps
end
end
ActiveRecord::Migration.change
Market model have following associations defined,
class Market < ApplicationRecord
belongs_to :base_currency, class_name: 'Currency', foreign_key: :base_currency_id
belongs_to :base_currency2, class_name: 'Currency', foreign_key: :base_currency_id2
end
I am not getting why one of the below association is causing N+1 query even on eager-loading here,
Market.includes(:base_currency).each { |x| puts x.base_currency.code }
# Market Load (0.6ms) SELECT "markets".* FROM "markets"
# Currency Load (0.3ms) SELECT "currencies".* FROM "currencies" WHERE "currencies"."id" = $1 [["id", 1]]
# INR
# INR
Market.includes(:base_currency2).each { |x| puts x.base_currency.code }
# Market Load (0.5ms) SELECT "markets".* FROM "markets"
# Currency Load (0.4ms) SELECT "currencies".* FROM "currencies" WHERE "currencies"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
# INR
# Currency Load (0.4ms) SELECT "currencies".* FROM "currencies" WHERE "currencies"."id" = $1 LIMIT $2 [["id", 1], ["LIMIT", 1]]
# INR
Please explain if I miss something here.
change the association called inside the block to base_currency2 in the latter.
Market.includes(:base_currency2).each { |x| puts x.base_currency2.code }

how set the permissions for cancan gem?

please help solve the problem. i try set the permissions after install 'cancan' and 'cancancan' gems.
schema.rb:
create_table "roles", force: :cascade do |t|
t.string "name"
end
create_table "roles_users", id: false, force: :cascade do |t|
t.integer "role_id"
t.integer "user_id"
end
create_table "users", force: :cascade do |t|
t.string "email", default: "", null: false
................................................................
t.datetime "created_at"
t.datetime "updated_at"
end
models:
class User < ActiveRecord::Base
has_and_belongs_to_many :roles
def role?(role)
return !!self.roles.find_by_name(role.to_s.camelize)
end
end
class Role < ActiveRecord::Base
has_and_belongs_to_many :users
end
app/models/ability.rb:
class Ability
include CanCan::Ability
def initialize(user)
user ||= User.new # guest user (not logged in)
if user.role? :admin
can :manage, :all
elsif user.role? :manager
can :manage, :review
cannot :manage, :user
elsif user.role? :user
cannot :manage, :all
end
end
end
i filled my roles table follow values:
id name
0 user
1 manager
2 admin
i filled my join table 'roles_users' follow values:
role_id user_id
2 2
1 3
0 1
but after run application permissions is no effect. the problem is that managers can change info for all users. it is not right. please help to fix it
ps:
my user controller:
class UserController < ApplicationController
load_and_authorize_resource
end
after manager change info about user via adminpanel, console output follow:
Started GET "/admin/users/1" for 127.0.0.1 at 2015-09-19 20:53:47 +0300
Processing by Admin::UsersController#show as HTML
Parameters: {"id"=>"1"}
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 1]]
User Load (0.1ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? ORDER BY "users"."id" ASC LIMIT 1 [["id", 3]]
(0.1ms) SELECT COUNT(*) FROM "active_admin_comments" WHERE "active_admin_comments"."resource_type" = ? AND "active_admin_comments"."resource_id" = ? AND "active_admin_comments"."namespace" = ? [["resource_type", "User"], ["resource_id", "1"], ["namespace", "admin"]]
CACHE (0.0ms) SELECT COUNT(*) FROM "active_admin_comments" WHERE "active_admin_comments"."resource_type" = ? AND "active_admin_comments"."resource_id" = ? AND "active_admin_comments"."namespace" = ? [["resource_type", "User"], ["resource_id", "1"], ["namespace", "admin"]]
Rendered /home/kalinin/.rvm/gems/ruby-2.0.0-p598/bundler/gems/activeadmin-893b46c6530c/app/views/active_admin/resource/show.html.arb (316.7ms)
Completed 200 OK in 321ms (Views: 318.9ms | ActiveRecord: 0.3ms)
You don't need that .camelize call in your role? method, since all your roles in db are stored in lower-case (manager) and not in camelCase (ManagerOfTheApplication).
It seems your Admin::UsersController is located in active_admin. Try to enable active_admin and can_can integration:
config.authorization_adapter = ActiveAdmin::CanCanAdapter
Look into the link above on the details.

Active Record association links, but can't assign values

I'm setting up permissions for each user to determine what access they have to certain data. I have a user model and a permission model that is set up with a has_many and belongs_to association like so:
app/models/permission.rb
class Permission < ActiveRecord::Base
has_many :users
end
app/models/user.rb
class User < ActiveRecord::Base
belongs_to :permission
end
The migration I used to set up this table is as follows:
db/migrate/create_permissions.rb
class CreatePermissions < ActiveRecord::Migration
def change
create_table :permissions do |t|
t.string :group
t.string :can_see
t.string :cant_see
t.timestamps null: false
end
add_column :users, :permissions_id, :integer
end
end
The problem comes when I go into the Rails console. A sample session may look like this:
irb(main):001:0> User.column_names
=> ["id", "name", "email", "created_at", "updated_at", "password_digest", "remember_digest",
"admin", "activation_digest", "activated", "activated_at", "reset_digest", "reset_sent_at",
"last_active_at", "permissions_id"]
irb(main):002:0> User.first.permissions_id
User Load (0.2ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> 3
irb(main):003:0> User.first.permission
User Load (0.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
=> nil
irb(main):004:0> Permission.where(id: 3)
Permission Load (0.3ms) SELECT "permissions".* FROM "permissions" WHERE "permissions"."id" =
? [["id", 3]]
=> #<ActiveRecord::Relation [#<Permission id: 3, group: "CA", can_see: "Some stuff", cant_see:
"Some stuff", created_at: "2015-06-22 12:28:55", updated_at: "2015-06-22 12:28:55">]>
irb(main):005:0> User.first.permission.group
User Load (0.3ms) SELECT "users".* FROM "users" ORDER BY "users"."id" ASC LIMIT 1
NoMethodError: undefined method `group' for nil:NilClass
Commands like User.first.permissions_id return values and that value should be a foreign key for the Permission model, but running User.first.permission returns nil.
Your column should be permission_id, not permissions_id. That is why AR doesn't find the related model.

Can I use ActiveRecord::Associations::Preloader / eager loading to load a has_one relationship with dynamic conditions?

I've got this:
class User < ActiveRecord::Base
has_one :profile
end
class Profile < ActiveRecord::Base
belongs_to :user
has_one :latest_action,
:class_name=>'Action',
:conditions=> Proc.new {["action_at <= ?", self.timezone.now.to_date]},
:order=>"action_at desc"
end
create_table "actions", :force => true do |t|
t.date "action_at"
t.datetime "created_at"
t.datetime "updated_at"
end
I would like to do this:
users = User.limit(10)
ActiveRecord::Associations::Preloader.new(users, [:profile => :latest_action]).run
or this:
User.includes(:profile => :latest_action).limit(10).all
However, this fails:
User Load (0.9ms) SELECT "users".* FROM "users" LIMIT 2
Profile Load (0.8ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."user_id" IN (133622, 133623)
NoMethodError: undefined method `timezone' for #<Class:0x007fc5992152f8>
This works when I'm dealing with a single record:
User.last.profile.latest_action
User Load (0.8ms) SELECT "users".* FROM "users" ORDER BY "users"."id" DESC LIMIT 1
Profile Load (0.5ms) SELECT "profiles".* FROM "profiles" WHERE "profiles"."user_id" = 242222 LIMIT 1
Action Load (0.6ms) SELECT "actions".* FROM "actions" WHERE "actions"."profile_id" = 231220 AND (action_at <= '2013-08-27') ORDER BY action_at desc LIMIT 1
Can I use a Proc to generate dynamic conditions on a has_one association and use that association in an ActiveRecord::Associations::Preloader call or with eager loaded associations via include?
It seems like in the in pre-loader / eager loading context, self in the conditions proc is a class not an instance.
I am on Rails 3.2.13
Note
I realize I could load the association like this, but I can't use that with the preloader
class Profile
has_many :actions do
def latest
where("action_at <= ?", proxy_association.owner.timezone.now.to_date)
end
end
end
The problem is that self isn't your object in the has_many, it's the association proxy. If you want the profile object that is referenced in the association, it should be owner (though this is slightly dependent on the version of Rails you're running -- check the documentation for Rails association extensions for more details).

Improving Query/View Load-Time [Ruby on Rails]

I'm experiencing some pretty slow load times with one of the pages of my Rails app. I have made some headway in my attempts to improve performance, but not as much as I had hoped. I'm wondering if I'm doing something silly to cause myself this grief or if anyone could offer me advice to better optimize my models/migrations/queries/views. I'm running Rails 3.1 and using PostgreSQL. This is what I've tried so far:
added includes(:items => :category) to the controller's query, as an attempt to fetch all the ItemCategories in advance, rather than running additional queries when outfits_helper requires them.
rendering partials as collections, rather than iterating through arrays/relations manually
added indexes to tables that are being accessed by the problematic controller action / view
outfits table: indexes for user_id and category_id
items table: indexes for user_id and category_id
outfit_items table: indexes for item_id, outfit_id, and [outfit_id, item_id]
Below are the relevant parts of my code:
Controller
# outfits_controller.rb
NUM_OUTFITS_PER_PAGE = 8
def other_users_outfits
# using Kaminari for pagination
#outfits = Outfit.does_not_belong_to_user(current_user).in_category_with_id(category_id).includes(:items => :category).page(params[:page]).per(NUM_OUTFITS_PER_PAGE)
end
Models
# outfit.rb
has_many :items, :through => :outfit_items
belongs_to :category, :class_name => 'OutfitCategory'
scope :does_not_belong_to_user, proc {|user| where('user_id != ?', user.id) }
scope :in_category_with_id, proc {|cat_id|
if cat_id.blank?
scoped
else
where(:category_id => cat_id)
end
}
.
# outfit_item.rb
belongs_to :item
belongs_to :outfit
.
# item.rb
has_many :outfit_items
has_many :outfits, :through => :outfit_items
belongs_to :category, :class_name => 'ItemCategory'
.
# item_category.rb
has_many :items, :foreign_key => 'category_id'
.
outfit_category.rb
has_many :outfits, :foreign_key => 'outfit_id'
Migrations
# create_outfits.rb
def self.up
create_table :outfits do |t|
t.column :category_id, :integer
t.column :user_id, :integer
t.timestamps
end
add_index :outfits, :category_id
add_index :outfits, :user_id
end
.
# create_outfit_items.rb
def self.up
create_table :outfit_items, :id => false do |t|
t.column :item_id, :integer
t.column :outfit_id, :integer
t.timestamps
end
add_index :outfit_items, :item_id
add_index :outfit_items, :outfit_id
add_index :outfit_items, [:outfit_id, :item_id]
end
.
# create_items.rb
def self.up
create_table :items do |t|
t.column :category_id, :integer
t.column :user_id, :integer
t.timestamps
end
add_index :items, :category_id
add_index :items, :user_id
end
.
# create_item_categories.rb
def self.up
create_table :item_categories do |t|
t.column :name, :string
t.column :category_type, :string
t.timestamps
end
end
.
# create_outfit_categories.rb
def self.up
create_table :outfit_categories do |t|
t.column :name, :string, :limit => 100, :null => false
t.timestamps
end
end
Views
# other_users_outfits.html.haml
= render :partial => 'other_users_outfit', :collection => outfits, :as => :outfit
.
# _other_users_outfit.html.haml
.outfit
.primary-items
= render :partial => 'other_users_outfit_primary_item', :collection => primary_outfit_items_top_to_bottom(outfit), :as => :item
.secondary-items
= render :partial => 'other_users_outfit_secondary_item', :collection => secondary_outfit_items(outfit), :as => :item
.
# _other_users_outfit_primary_item.html.haml
= image_tag item.image_url(:outfit_item), :class => 'primary-outfit-item'
.
# _other_users_outfit_secondary_item.html.haml
= image_tag item.image_url(:baby_thumb), :class => 'secondary-outfit-item'
Helper
# outfits_helper.rb
def primary_outfit_items_top_to_bottom(outfit)
primary_items = []
primary_items.push(outfit_item_in_category(outfit, 'Tops'))
primary_items.push(outfit_item_in_category(outfit, 'Full-lengths'))
primary_items.push(outfit_item_in_category(outfit, 'Bottoms'))
primary_items.push(outfit_item_in_category(outfit, 'Footwear'))
primary_items.compact
end
def secondary_outfit_items(outfit)
outfit.items.select{|item| item.category.category_type == 'secondary' }
end
def outfit_item_in_category(outfit, cat_name)
outfit.items.select{|item| item.category.name == cat_name }.first
end
Console Output
User Load (1.0ms) SELECT DISTINCT users.id, users.* FROM "users" WHERE "users"."id" = 3 LIMIT 1
(0.5ms) SELECT COUNT(count_column) FROM (SELECT 1 AS count_column FROM "outfits" WHERE (user_id != 3) LIMIT 8 OFFSET 0) subquery_for_count
Outfit Load (0.7ms) SELECT DISTINCT outfits.id, outfits.* FROM "outfits" WHERE (user_id != 3) ORDER BY outfits.created_at DESC LIMIT 8 OFFSET 0
OutfitItem Load (0.6ms) SELECT "outfit_items".* FROM "outfit_items" WHERE "outfit_items"."outfit_id" IN (28, 27, 26, 25, 24, 23, 22, 21)
Item Load (2.2ms) SELECT DISTINCT items.id, items.*, "items".* FROM "items" WHERE "items"."id" IN (18, 20, 23, 7, 6, 30, 4, 1, 17, 5, 15, 12, 9, 29, 10, 19, 3, 8, 13) ORDER BY items.created_at DESC
ItemCategory Load (0.5ms) SELECT "item_categories".* FROM "item_categories" WHERE "item_categories"."id" IN (4, 6, 2, 1, 3, 7)
Rendered outfits/_other_users_outfit_secondary_item.html.haml (1.2ms)
Rendered outfits/_other_users_outfit_secondary_item.html.haml (0.1ms)
Rendered outfits/_other_users_outfit_primary_item.html.haml (2.8ms)
Rendered outfits/_other_users_outfit_secondary_item.html.haml (1.4ms)
Rendered outfits/_other_users_outfit_secondary_item.html.haml (0.9ms)
Rendered outfits/_other_users_outfit_primary_item.html.haml (1.2ms)
Rendered outfits/_other_users_outfit_secondary_item.html.haml (0.3ms)
Rendered outfits/_other_users_outfit_secondary_item.html.haml (0.9ms)
Rendered outfits/_other_users_outfit_primary_item.html.haml (2.7ms)
Rendered outfits/_other_users_outfit_secondary_item.html.haml (0.8ms)
Rendered outfits/_other_users_outfit_secondary_item.html.haml (0.7ms)
Rendered outfits/_other_users_outfit_primary_item.html.haml (1.2ms)
Rendered outfits/_other_users_outfit_secondary_item.html.haml (1.1ms)
Rendered outfits/_other_users_outfit_secondary_item.html.haml (0.3ms)
Rendered outfits/_other_users_outfit_primary_item.html.haml (2.2ms)
Rendered outfits/_other_users_outfit_secondary_item.html.haml (0.8ms)
Rendered outfits/_other_users_outfit_secondary_item.html.haml (0.3ms)
Rendered outfits/_other_users_outfit_primary_item.html.haml (3.4ms)
Rendered outfits/_other_users_outfit_secondary_item.html.haml (0.3ms)
Rendered outfits/_other_users_outfit_secondary_item.html.haml (0.0ms)
Rendered outfits/_other_users_outfit_primary_item.html.haml (2.6ms)
Rendered outfits/_other_users_outfit_secondary_item.html.haml (0.3ms)
Rendered outfits/_other_users_outfit_secondary_item.html.haml (0.3ms)
Rendered outfits/_other_users_outfit_primary_item.html.haml (2.1ms)
Rendered outfits/_other_users_outfit.html.haml (56.3ms)
(0.5ms) SELECT COUNT(*) FROM "outfits" WHERE (user_id != 3)
Rendered outfits/other_users.html.haml within layouts/application (2073.5ms)
Role Load (0.4ms) SELECT "roles".* FROM "roles" WHERE "roles"."name" = 'admin' LIMIT 1
(0.4ms) SELECT 1 FROM "roles" INNER JOIN "roles_users" ON "roles"."id" = "roles_users"."role_id" WHERE "roles_users"."user_id" = 3 AND "roles"."id" = 1 LIMIT 1
Rendered layouts/_top_nav.html.haml (1.0ms)
(0.6ms) SELECT COUNT(*) FROM "items" WHERE "items"."user_id" = 3
Rendered layouts/_points_display.html.haml (5.7ms)
Outfit Load (0.6ms) SELECT DISTINCT outfits.id, outfits.* FROM "outfits" WHERE "outfits"."user_id" = 3 ORDER BY outfits.created_at DESC
OutfitCategory Load (0.3ms) SELECT "outfit_categories".* FROM "outfit_categories"
.
Any help would be much appreciated!
The only thing I see that could stand improvement is the code in the helper. Is there a reason why you're doing this:
def outfit_item_in_category(outfit, cat_name)
outfit.items.select{|item| item.category.name == cat_name }.first
end
instead of pushing this code to a scope in the Item model? If you have a lot of items, you're basically doing a SELECT * on them and then filtering in Ruby.

Resources