Rails update() method causing error for custom URL - ruby-on-rails

I am to create custom URLs for my Rails app.
So far, I'm following the Friendy URLs documentation, as mentioned here https://gist.github.com/jcasimir/1209730.
I've replaced the default slug (ie: /books/4 ) with an attribute of the model (ie: /books/the-lord-of-the-rings ). In the book.rb model file:
# override to_param
# By default it returns the "slug" of the model
# (ie: #book.to_param => "1")
def to_param
title.parameterize
end
Consequently, I've over-written the Book.find() method, re-defined in the book.rb file as well.
# override find
# the "super" usage is Book.find(1) => object 1
# Now, it is Book.find("bookTitle") => object of that name
def self.find(input)
find_by_title(input)
end
This is added in order to be able to find my model without using the "slug", or ID of my model.
The issue arises when I try to update a Book entry. When #book.update(params[:book]) is called, this is the error I get from my server:
(0.2ms) BEGIN
Book Exists (0.5ms) SELECT 1 AS one FROM "books" WHERE ("books"."title" = 'nasty 22' AND "books"."id" != 62) LIMIT 1
Tag Load (0.6ms) SELECT "tags".* FROM "tags" WHERE "tags"."book_id" = $1 [["book_id", 62]]
Book Load (1.3ms) SELECT "books".* FROM "books" **WHERE "books"."title" = 62** LIMIT 1
PG::UndefinedFunction: ERROR: operator does not exist: character varying = integer
LINE 1: ...CT "books".* FROM "books" WHERE "books"."title" = 62 LIMIT...
The problematic line is
WHERE "books"."title" = 62
Somehow somewhere in the update method, it is reverting to trying to find my Book model using the ID instead of the Title.
Does anyone know how this problem can be solved?
Much appreciated.

If you just continue to follow the instructions in the gist it will fix your problem. You're going to have to deal with the fact that other callers will call Book.find passing an integer id, so you'll want to handle both cases. So, you'll need to check to determine if the parameter being passed is an integer ID or a title, and then call the appropriate find method (either super or find_by_title, respectively). It just won't work any other way, I would think, and find is a pretty basic method.

Related

Rails: Why are has_many relations not saved in the variable/attribute like belongs_to?

So let's say I have the following models in Rails.
class Post < ApplicationRecord
belongs_to :user
end
class User < ApplicationRecord
has_many :posts
end
When I put the Post instance in a variable and call user on it, the following sql query runs once, after that the result it is saved/cached.
post.user
User Load (0.9ms) SELECT `users`.* FROM `users` WHERE `users`.`id` = 1 LIMIT 1
#<User id: 1 ...>
post.user
#<User id: 1 ...>
etc.
However, when I go the other way around with user.posts, it always runs the query.
user.posts
Post Load (1.0ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` = 1 LIMIT 11
user.posts
Post Load (1.0ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` = 1 LIMIT 11
etc.
Unless I convert it to an array, in which case it does get saved.
user.posts.to_a
Post Load (1.0ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` = 1 LIMIT 11
user.posts
# No query here.
But user.posts still produces a ActiveRecord::Associations::CollectionProxy object on which I can call other activerecord methods. So no real downside here.
Why does this work like this? It seems like an unnecessary impact on sql optimization. The obvious reason I can think of is that user.posts updates correctly when a Post is created after user is set. But in reality that doesn't happen so much imo, since variables are mostly set and reset in consecutively ran Controller actions. Now I know there is a caching system in place that shows a CACHE Post sql in the server logs, but I can't really rely on that when I'm working with activerecord objects between models or with more complex queries.
Am I missing some best practice here? Or an obvious setting that fixes exactly this?
You're examining this in irb and that's why you're seeing the query always running.
In an actual block of code in a controller or other class if you were to write
#users_posts = user.posts
The query is NOT executed... not until you iterate through the collection, or request #count
irb, to be helpful, always runs queries immediately

Differences between `any?` and `exists?` in Ruby on Rails?

In Ruby on Rails, there appear to be two methods to check whether a collection has any elements in it.
Namely, they are ActiveRecord::FinderMethods’ exists? and ActiveRecord::Relation’s any?. Running these in a generic query (Foo.first.bars.exists? and Foo.first.bars.any?) generated equivalent SQL. Is there any reason to use one over the other?
#any and #exists? are very different beasts but query similarly.
Mainly, #any? accepts a block — and with this block it retrieves the records in the relation, calls #to_a, calls the block, and then hits it with Enumerable#any?. Without a block, it's the equivalent to !empty? and counts the records of the relation.
#exists? always queries the database and never relies on preloaded records, and sets a LIMIT of 1. It's much more performant vs #any?. #exists? also accepts an options param as conditions to apply as you can see in the docs.
The use of ActiveRecord#any? is reduced over ActiveRecord#exists?. With any? you can check, in the case of passing a block, if certain elements in that array matches the criteria. Similar to the Enumerable#any? but don't confuse them.
The ActiveRecord#any? implements the Enumerable#any? inside the logic of its definition, by converting the Relation accessed to an array in case a block has been passed to it and yields and access the block parameters to implement in a "hand-made" way a "Ruby" any? method.
The handy else added is intended to return the negation of empty? applied to the Relation. That's why you can check in both ways if a model has or no records in it, like:
User.count # 0
User.any? # false
# SELECT 1 AS one FROM "users" LIMIT ? [["LIMIT", 1]]
User.exists? # false
# SELECT 1 AS one FROM "users" LIMIT ? [["LIMIT", 1]]
You could also check in the "any?" way, if some record attribute has a specific value:
Foo.any? { |foo| foo.title == 'foo' } # SELECT "posts".* FROM "posts"
Or to save "efficiency" by using exists? and improve your query and lines of code:
Foo.exists?(title: 'foo') # SELECT 1 AS one FROM "posts" WHERE "posts"."title" = ? LIMIT ? [["title", "foo"], ["LIMIT", 1]]
ActiveRecord#exists? offers many implementations and is intended to work in a SQL level, rather than any?, that anyways will convert the Relation what you're working with in an array if you don't pass a block.
The answers here are all based on very outdated versions. This commit from 2016 / ActiveRecord 5.1 changes empty?, which is called by any? when no block is passed, to call exists? when not preloaded. So in vaguely-modern Rails, the only difference when no block is passed is a few extra method calls and negations, and ignoring preloaded results.
I ran into a practical issue: exists? forces a DB query while any? doesn't.
user = User.new
user.skills = [Skill.new]
user.skills.any?
# => true
user.skills.exists?
# => false
Consider having factories and a before_create hook:
class User < ActiveRecord::Base
has_many :skills
before_create :ensure_skills
def ensure_skills
# Don't want users without skills
errors.add(:skills, :invalid) if !skills.exists?
end
end
FactoryBot.define do
factory :user do
skills { [association(:skill)] }
end
end
create(:user) will fail, because at the time of before_create skills are not yet persisted. Using .any? will solve this.

Rails `find_by` returning huge ID

Curious if anyone knows the intricacies of find_by since I've checked documentation and been unable to find info.
I know that find is used to find by primary keys like:
#user = User.find(params[:id]), returning the correct user.
Before I corrected my code it was #user = User.find_by(params[:id]) and returned a user with an ID way above the number of users in my DB.
Can anyone help me understand what is happening under the hood? What does find_by search by default when a parameter is omitted that is returning this strange user object?
find_by_field(value) is equivalent to where(field: value) but is not supposed to be used without appending a field name to the method like you mentioned. Moreover it returns only the first matching value. For example instead of doing User.where(name: 'John').limit(1) you can use: User.find_by_name 'John'.
On my side, using find_by with postgresql raises an error, when find_by_id does work:
User.find_by(1)
SELECT "users".* FROM "users" WHERE (1) ORDER BY "users"."email" ASC LIMIT 1
PG::DatatypeMismatch: ERROR: argument of WHERE must be type boolean, not type integer
User.find_by_id 1
SELECT "users".* FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."email" ASC LIMIT 1
<User id: 1, ...
User.where(id: 1) # note that there is no LIMIT 1 in the generated SQL
SELECT "users".* FROM "users" WHERE "users"."id" = 1 ORDER BY "users"."email" ASC
You can use gem query_tracer to see the generated SQL or check this thread.
Please take a look here.
Here is an excerpt for find_by:
Finds the first record matching the specified conditions. There is no implied ordering so if order matters, you should specify it yourself.
If no record is found, returns nil.
Post.find_by name: 'Spartacus', rating: 4
Post.find_by "published_at < ?", 2.weeks.ago
Is that what you were looking for?
UPDATE
User.find_by(3) is equivalent to User.where(3).take
Here's the output from the console
pry(main)> User.where(3).take
#=> User Load (0.3ms) SELECT `users`.* FROM `users` WHERE (3) LIMIT 1
It's look like rails return to you an id of object in memory. It's like query User.find(params[:id]).object_id. But why it's happens? I try did same on my app with 4.2.4 version and all goes fine

Order and limit clauses unexpectedly passed down to scope

(the queries here have no sensible semantic but I chose them for the sake of simplicity)
Project.limit(10).where(id: Project.select(:id))
generates as expected the following SQL query:
SELECT
"projects".*
FROM
"projects"
WHERE
"projects"."id" IN (
SELECT
"projects"."id"
FROM
"projects"
) LIMIT 10
But if I defined in my Project class the method
def self.my_filter
where(id: Project.select(:id))
end
Then
Project.limit(10).my_filter
generates the following query
SELECT
"projects".*
FROM
"projects"
WHERE
"projects"."id" IN (
SELECT
"projects"."id"
FROM
"projects" LIMIT 10
) LIMIT 10
See how the LIMIT 10 has now been also applied to the subquery.
Same issue when using a .order clause.
It happens with Rails 4.2.2 and Rails 3.2.20. It happens when the subquery is done on the Project table, it does happens if the subquery is done on another table.
Is there something I'm doing wrong here or do you think it is a Rails bug?
A workaround is to build my_filter by explicitly adding limit(nil).reorder(nil) to it but it is hackish.
EDIT: another workaround is to append the limit clause after the my_filter scope: Project.my_filter.limit(10).
This is actually a feature. Class methods work similar to scopes in ActiveRecord models.
And if you want to remove the already added scopes, you can either use unscoped, either call the method on a class directly, not on a scope:
def self.my_filter
unscoped.where(id: Project.select(:id))
end
# or
Project.my_filter
Your class method is applied in a way you may not be expecting:
Project.limit(10) # => a relation, not the Project class
.my_filter # => calling a class method on a relation
# Does, the following, suddenly:
# scoping { Project.my_filter }
# It's a relation's wrapper
From: .../ruby-2.0.0-p598/gems/activerecord-4.1.6/lib/active_record/relation.rb # line 281:
Owner: ActiveRecord::Relation
Visibility: public
Signature: scoping()
Scope all queries to the current scope.
Comment.where(post_id: 1).scoping do
Comment.first
end
# => SELECT "comments".* FROM "comments"
# WHERE "comments"."post_id" = 1 ORDER BY "comments"."id" ASC LIMIT 1
Please check unscoped if you want to remove all previous scopes (including
the default_scope) during the execution of a block.
Inside that scoping block, your class will include all the scoping rules of a relation it was built from into all queries, as scoping will enforce context. This is done so class methods can be properly chained, while still retaining the correct self. Of course, when you try using a class method inside the class method, stuff blows up.
In your first, "expected outcome" example, where is "natively" defined on relations, so no scope enforcement takes place: it's just not necessary.
Yeah, documentation hints that you can use unscoped in your nested query, like so:
def my_filter
where(id: Project.unscoped.select(:id))
end
...since that's where you need the "bare basis". Or, as you've already found out, you can just place limit at the end:
Project.my_filter.limit(10)
...here, at the time my_filter gets to execute, scoping will do effectively nothing: there will be no context built up to this point.

How to stack rails helper methods in controller?

I'd like to
#cart = Cart.find_by(id: session[:cart], :limit => 1)
but that throws out an error SQLite3::SQLException: no such column: carts.limit: SELECT "carts".* FROM "carts" WHERE "carts"."id" = 1 AND "carts"."limit" = 1 LIMIT 1 so how can I use multiple methods on a single global variable? Version: rails 4.0.1
It appears you mean to specify a limit of one record to be returned from find_by.
However, find_by only returns one record anyway, so this is not needed. Your :limit gets interpreted as a table column, which of course doesn't exist.
You can omit it entirely.
#cart = Cart.find_by(id: session[:cart])
If you're going to be querying by id, you can simply do:
ruby #cart = Card.find(session[:cart])
as find will do a SELECT ... LIMIT 1 by the primary key.

Resources