ActiveRecord destroy - no exception - exception

I'm doing a destroy via ActiveRecord using a field. However, a record in the database with that field value does not exist. So, given this bit of debugging using pry:
[1] pry(main)> fieldvalue
=> "17785"
[2] pry(main)> person = Person.where(:fieldname => fieldvalue.to_i)
=> []
[3] pry(main)> person
=> []
[4] pry(main)> person.destroy_all
=> []
...why does it not raise an exception at step 4?
The original bit of code, similarly, silently fails to raise an exception:
begin
Person.where(:fieldname => fieldvalue.to_i).destroy_all
rescue ActiveRecord::RecordNotFound => exception
#logger.info("ActiveRecord::RecordNotFound exception raised. Details: #{exception}")
rescue Exception => exception
#logger.info("Unknown exception raised. Details: #{exception}")
else
#logger.info("Destroying person with field value: #{fieldvalue}")
end
i.e. it just logs the "Destroying..." line each time it's run.

destroy_all does not raise an exception when executed on an empty set ([]) because the method, according to the documentation:
Destroys the records matching conditions by instantiating each record and calling its destroy method.
Because your where query is returning an empty set, there are no records to instantiate – destroy is never actually being executed on anything, hence there are no exceptions thrown.
Contrast this to a hypothetical situation wherein a set is returned that – for some unlikely reason – contained a nil object. The set might look something like this
people = Person.where("created_at < ?", Time.now)
#=> [#<Person id:1>, nil, #<Person id:2>] # for demonstration only; would not actually happen
people.destroy_all
#=> NoMethodError: undefined method `destroy' for nil:NilClass
A NoMethodError would be thrown on the second iteration of destroy_all, since destroy would be executed on nil, which does not have a destroy method.
One other thought: you may be conflating the where ActiveRecord method with the find method. where returns an ActiveRecord::Relation object, which effectively operates as an array of objects which match the passed conditions. find, in contrast, returns the first object itself_ which match the conditions passed. In the real world example from above, if you're seeking to return a single object from your query, it may make more sense to use find rather than where:
people = Person.where(:fieldname => fieldvalue.to_i) # returns a collection of all objects matching the condition
people.class
#=> ActiveRecord::Relation
person = Person.find(:fieldname => fieldvalue.to_i) # returns the FIRST object matching the condition
person.class
#=> Person

Related

synthesizing long parameter strings

When consuming a JSON string, the parameters can be deeply nested, making reading/checking tedious:
update(capture_created: params[:data][:object][:created], capture_currency: params[:data][:object][:currency]
...[...] and so on...
In what way can a node params[:data][:object] be represented only once and be thus able to handle the child values as a parameter?
There are a few things you can.
You could grab the inner hash in a local variable as dbugger mentioned:
p = params[:data][:object]
update(capture_created: p[:created], capture_currency: p[:currency], ...)
Or you could use #tap or #then (depending on what return value you want from the expression):
# This evaluates to params[:data][:object]
params[:data][:object].tap do |p|
update(capture_created: p[:created], capture_currency: p[:currency], ...)
end
# This evaluates to whatever update returns
params[:data][:object].then do |p|
update(capture_created: p[:created], capture_currency: p[:currency], ...)
end
If the keys in the nested hash only need to be consistently renamed (i.e. add a "capture_" prefix) then #transform_keys:
update(params[:data][:object].transform_keys { |k| "capture_#{k}" })
is an option. String keys are fine with an ActiveRecord #update call but you could get symbols if you really want them:
update(params[:data][:object].transform_keys { |k| :"capture_#{k}" })
You might want to include a Hash#slice call if you want to ensure that you're only accessing certain keys:
update(params[:data][:object].slice(:created, :currency, ...).transform_keys { |k| :"capture_#{k}" })

Active Admin filter params passed to ransacker

I am trying to pass a couple IDs through to a custom ransacker filter for active admin
filter :hasnt_purchased_product_items_in, as: :select, collection: -> { ProductItem.all }, multiple: true
The ransacker code
ransacker :hasnt_purchased_product_items, formatter: proc { |product_id|
users_who_purchased_ids = User.joins(:orders => :items).where("orders.status = 'success'").where("order_items.orderable_id IN (?)", product_id) # return only id-s of returned items.
ids = User.where.not(id: users_who_purchased_ids).pluck(:id)
ids.present? ? ids : nil # return ids OR nil!
} do |parent| # not sure why this is needed .. but it is :)
parent.table[:id]
end
This works for a single query but not for multiple, the SQL is doing 2 separate searches instead of one
It's running the following 2 statements
order.items.orderable_id IN ('90')
and
order.items.orderable_id IN ('91')
in 2 separate SQL statements, instead of what i want which is
order.items.orderable_id IN ('90','91')
the submitted info from the active admin page is
q[hasnt_purchased_product_items_in][]: 90
q[hasnt_purchased_product_items_in][]: 91
i think i need to do something to the incoming parameter at the ransacker stage but i'm not sure how to deal with that
It shouldn't be stepping in. The param should be |product_ids| and give us an array object inside the proc that then gets used. Your ransacker should return an active record association or an arel query.
I would try each of these:
Use "_in" in the ransacker name, which bypasses the array logic.
ransacker :hasnt_purchased_product_items_in do |product_ids|
users_who_purchased_ids = User.joins(orders: :items).where(orders: {status: 'success'}, order_items: {orderable_id: product_ids}) # return only id-s of returned items.
User.where.not(id: users_who_purchased_ids)
end
Same ransacker name, but with block use rather than formatter.
ransacker :hasnt_purchased_product_items do |product_ids|
users_who_purchased_ids = User.joins(orders: :items).where(orders: {status: 'success'}, order_items: {orderable_id: product_ids}) # return only id-s of returned items.
User.where.not(id: users_who_purchased_ids)
end
I've had to guess the object to return because it's not clear which model this ransacker is on. If you can tell me the objective (e.g. filtering product recommendations for a user to exclude their purchases) then I can update the answer.
Create a scope and make it available to ransack.
User.rb
scope :hasnt_purchased_product_items_in, ->(product_ids){
users_who_purchased_ids = joins(orders: :items).where(orders: {status: 'success'}, order_items: {orderable_id: product_ids}) # return only id-s of returned items.
where.not(id: users_who_purchased_ids)
}
def self.ransackable_scopes(auth_object = nil)
%i(hasnt_purchased_product_items_in)
end
Then this works:
User.ransack({hasnt_purchased_product_items_in: [[1,2,3]]}).result
Note that the ransack want's an array of args. We want one arg, which is an array.

How to fix Slick Exception of single AutoInc column returned on INSERT

I tried to implement the akka-http rest example provided at
https://github.com/ArchDev/akka-http-rest
but I'm stuck with the
slick.SlickException: This DBMS allows only a single column to be returned from an INSERT, and that column must be an AutoInc column.
at slick.jdbc.JdbcStatementBuilderComponent$JdbcCompiledInsert.buildReturnColumns(JdbcStatementBuilderComponent.scala:67)
Here is the Scala Code:
Signup API:
path("signUp") {
pathEndOrSingleSlash {
post {
entity(as[UsernamePasswordEmail]) { userEntity =>
complete(Created -> signUp(userEntity.username, userEntity.email, userEntity.password))
}
}
}
}
AuthService.scala
def signUp(login: String, email: String, password: String): Future[AuthToken] =
authDataStorage
.saveAuthData(AuthData(UUID.randomUUID().toString, login, email, password.sha256.hex))
.map(authData => encodeToken(authData.id))
AuthDataStorage.scala
...
override def saveAuthData(authData: AuthData): Future[AuthData] =
db.run((auth returning auth).insertOrUpdate(authData)).map(_ => authData)
...
Since I'm new to Scala and Slick, can anyway provide the information why this exception is occurring even though I've defined O.AutoInc in Model. I'm using MySQL RDBMS
The problem is with returning auth. Instead of returning auth i.e complete object, Just return the auto-increment Id id. Slick does not support returning the complete object, though it compiles correctly. It does not generate a valid sql query.
Once you can get access to the auto-increment id then you can build the AuthData using the argument of the function.
Code:
(auth returning auth.map(_.id)).insertOrUpdate(authData)).map(id => authData.copy(id = id))
The exception is the result of a MySQL behavior. As the Slick documentation states:
Many database systems only allow a single column to be returned which must be the table’s auto-incrementing primary key. If you ask for other columns a SlickException is thrown at runtime (unless the database actually supports it).
Change the saveAuthData method to return the id column on an upsert:
override def saveAuthData(authData: AuthData): Future[AuthData] =
db.run((auth returning auth.map(_.id)).insertOrUpdate(authData))
.map(idFromDb => authData.copy(id = idFromDb.getOrElse(authData.id)))
In the above code, idFromDb is a Some[Int] for an insert and a None for an update.

jooq throws NPE when fetchOne is used

I have a simple query on a table which queries with the primary key.
dslContext.select(XXX.NAME)
.from(XXX)
.where(
XXX.ID.eq(id)
.and(XXX.LEVEL.eq(2))
.and(XXX.NAME.isNotNull())
)
.fetchOne().into(String.class);
In my case for a particular id, the query results in a empty set. But jooq seems to throw a NPE. When I further investigated, fetchOne() calls CursorImpl.fetchOne(). This checks the size of the result and if it is not 1, it returns null. I have a chained into(String.class) which gets called on this null value and hence resulting in NPE.
I don't want to call fetch() and iterate over the results/get the first element of the list.
Is there an alternative way of writing the query such that it will throw a org.jooq.exception.NoDataFoundException if there is no data?
Why a NullPointerException is being thrown
Technically, jOOQ doesn't throw a NullPointerException. Your calling into(Class) on a potentially null record does, as documented also in the Javadoc of ResultQuery.fetchOne()
Returns:
The resulting record or null if the query returns no records.
Throwing a NoDataFoundException.
You could use fetchOptional() and then use orElseThrow():
String string =
dslContext.select(XXX.NAME)
.from(XXX)
.where(
XXX.ID.eq(id)
.and(XXX.LEVEL.eq(2))
.and(XXX.NAME.isNotNull())
)
.fetchOptional()
.orElseThrow(() -> new NoDataFoundException("..."))
.into(String.class);
Note, there's a pending feature request for such a built-in fetch method: https://github.com/jOOQ/jOOQ/issues/5411
In jOOQ 3.10, you will be able to write:
String string =
dslContext.select(XXX.NAME)
.from(XXX)
.where(
XXX.ID.eq(id)
.and(XXX.LEVEL.eq(2))
.and(XXX.NAME.isNotNull())
)
.fetchSingle() // Might throw a NoDataFoundException but never returns null
.into(String.class);

HAML Check for Null

Working on a learning management system. NOT a RoR person. Have the line of HAML to generate an average score based on quizzes taken:
="#{(QuizResult.average('score', :conditions => "user_id = #{#user.id}") * 100).round}%"
The quiz_results table has columns for used_id and score.
However, if there are no records in the quiz_results table, the page doesn't render. I want to check if any scores exist for that user id, and if so, to show the average. If none exist, I want to display "No quizzes taken." here's what I have:
19: %td
20: -if QuizResult('score', :conditions => "user_id = #{#user.id}").exists?
21: ="#{(QuizResult.average('score', :conditions => "user_id = #{#user.id}") * 100).round}%"
22: -else
23: %em No quizzes taken
I get the following error:
"ActionView::TemplateError (undefined method `QuizResult' for #ActionView::Base:0x7028c7f5cb88>) on line #20 of app/views/manage_users/show_all_users.haml:"
I've been struggling all day with this. Any suggestions? Thanks in advance, from an RoR noob.
I am guessing that QuizResult is a model class. If that is the case then you cannot use it as a method to look up an instance.
There are various ways that you can look up an object by some condition, for example:
- if QuizResult.find_by_user_id(#user_id).present?
To check for null (or nil as its referred to in Ruby) you can use the nil? method that Ruby itself provides or the Rails convenience method present? which returns true unless the object is nil, an empty string or empty collection.
You might check your quotes. For the condiitions hash, it looks like you should be using single quotes rather than doubles.