How can I pass a default argument to Rails lambda/scopes? - ruby-on-rails

I have this piece of code on my accounts model.
scope :unverified, lambda { |limit|
select('accounts.id, accounts.email').joins('LEFT OUTER JOIN verifications v ON v.account_id = accounts.id')
.where('v.account_id IS NULL').limit(limit)
}
Because my team has rubocop with strict settings, I cannot write it the normal way rails recommends which would look like this:
scope :unverified, -> (limit = nil) {
select('accounts.id, accounts.email').joins('LEFT OUTER JOIN verifications v ON v.account_id = accounts.id')
.where('v.account_id IS NULL').limit(limit)
}
Writing it the normal way will trigger a rubocop error. I have the code close to the way I want but I can't figure out how exactly to pass in a default argument for a lambda. Can someone provide just a little push?

You can simply provide the defaults to the block parameters:
scope :unverified, lambda { |limit = nil|
select('accounts.id, accounts.email').joins('LEFT OUTER JOIN verifications v ON v.account_id = accounts.id')
.where('v.account_id IS NULL').limit(limit)
}
But not sure if it makes sense to pass nil to .limit(). You may want to default it to an integer.

Related

2 expect blocks in Spock?

I have the following Spock test, which passes:
def "test"() {
given:
def name = "John"
expect:
name.length() == 4
when:
name = name.concat(name)
then:
name.length() == 8
}
But when I modify the last then block and make it an expect block...
// previous part same
expect:
name.length() == 8
I am getting:
Groovy-Eclipse: Groovy:'expect' is not allowed here; instead, use one of: [and, then]
Is it because multiple expect blocks are not allowed in a single test? If so, is this documented anywhere? There is a similar test here written with given - expect - when - then but it is not clear why a second expect was not used, although what is being asserted is same, just being flipped.
when-expect is simply a syntax error with regard to Spock's specification DSL. The compiler message already tells you how to solve your problem. After when you need then (or and first, if you want to structure your when block into multiple sections). In contrast, expect is a kind of when-then contracted into a single block, because both the stimulus and verifying the response in a condition appear together. Block labels and how to use them is documented here.
Under Specifications as Documentation, you learn more about why you might want to use and and block labels. Under Invocation Order you learn about what you can achieve by using multiple then blocks in contrast to then-and.
You can use multiple expect blocks within one feature, no problem. But you do need to make sure that your when-then (if any) is complete, before you can use another expect.
For example, this is a valid feature:
def "my feature"() {
given: true
and: true
and: true
expect: true
when: true
and: true
then: true
and: true
then: true
expect: true
and: true
cleanup: true
}

Mongoid simple Map/Reduce on a has_many relationship

In a Rails app I have a collection of events with a list of invitees for each of them. I would like to have the whole list of invitees as a single flatten list.
A simple .map is not an option as there are thousands of events with as much of invitees...
class Event
include Mongoid::Document
include Mongoid::Timestamps
has_many :invitees
end
I was trying to use Map/Reduce for that with the following code:
map = %Q{
function() {
var event = this;
this.invitees.forEach(function(invitee){
emit(invitee.id, { event: event.title, id: invitee.id });
});
}
}
reduce = %Q{
function(key, values) {
var result = [];
values.forEach(function(v) {
result.push(v)
});
return { value: result };
}
}
Event.map_reduce(map, reduce).out(replace: "invitees")
But Mongo returns the following error: Mongo::Error::OperationFailure (TypeError: this.invitees is undefined :)
Is Map/Reduce the right way to achieve this operation ? If so, what am I doing wrong ?
These days map/reduce is deprecated and instead using the aggregation pipeline is recommended.
To perform the equivalent of joins in MongoDB, use $unwind aggregation pipeline stage: https://docs.mongodb.com/manual/reference/operator/aggregation/unwind/
You could then use $project to reduce the size of the result set to the fields/subdocuments that you care about: https://docs.mongodb.com/manual/reference/operator/aggregation/project/
Mongoid does not provide aggregation pipeline helpers, thus you have to go through the driver to use it. Driver documentation: https://docs.mongodb.com/ruby-driver/current/tutorials/ruby-driver-aggregation/
To get a collection object in a Rails app using Mongoid:
ModelClass.collection

Is there something similar to JS 'Promise.all()' in Ruby?

Below is a code that should be optimized:
def statistics
blogs = Blog.where(id: params[:ids])
results = blogs.map do |blog|
{
id: blog.id,
comment_count: blog.blog_comments.select("DISTINCT user_id").count
}
end
render json: results.to_json
end
Each SQL query cost around 200ms. If I have 10 blog posts, this function would take 2s because it runs synchronously. I can use GROUP BY to optimize the query, but I put that aside first because the task could be a third party request, and I am interested in how Ruby deals with async.
In Javascript, when I want to dispatch multiple asynchronous works and wait all of them to resolve, I can use Promise.all(). I wonder what the alternatives are for Ruby language to solve this problem.
Do I need a thread for this case? And is it safe to do that in Ruby?
There are multiple ways to solve this in ruby, including promises (enabled by gems).
JavaScript accomplishes asynchronous execution using an event loop and event driven I/O. There are event libraries to accomplish the same thing in ruby. One of the most popular is eventmachine.
As you mentioned, threads can also solve this problem. Thread-safety is a big topic and is further complicated by different thread models in different flavors of ruby (MRI, JRuby, etc). In summary I'll just say that of course threads can be used safely... there are just times when that is difficult. However, when used with blocking I/O (like to an API or a database request) threads can be very useful and fairly straight-forward. A solution with threads might look something like this:
# run blocking IO requests simultaneously
thread_pool = [
Thread.new { execute_sql_1 },
Thread.new { execute_sql_2 },
Thread.new { execute_sql_3 },
# ...
]
# wait for the slowest one to finish
thread_pool.each(&:join)
You also have access to other currency models, like the actor model, async classes, promises, and others enabled by gems like concurrent-ruby.
Finally, ruby concurrency can take the form of multiple processes communicating through built in mechanisms (drb, sockets, etc) or through distributed message brokers (redis, rabbitmq, etc).
Sure just do the count in one database call:
blogs = Blog
.select('blogs.id, COUNT(DISTINCT blog_comments.user_id) AS comment_count')
.joins('LEFT JOIN blog_comments ON blog_comments.blog_id = blogs.id')
.where(comments: { id: params[:ids] })
.group('blogs.id')
results = blogs.map do |blog|
{ id: blog.id, comment_count: blog.comment_count }
end
render json: results.to_json
You might need to change the statements depending on how your table as named in the database because I just guessed by the name of your associations.
Okay, generalizing a bit:
You have a list of data data and want to operate on that data asynchronously. Assuming the operation is the same for all entries in your list, you can do this:
data = [1, 2, 3, 4] # Example data
operation = -> (data_entry) { data * 2 } # Our operation: multiply by two
results = data.map{ |e| Thread.new(e, &operation) }.map{ |t| t.value }
Taking it apart:
data = [1, 2, 3, 4]
This could be anything from database IDs to URIs. Using numbers for simplicity here.
operation = -> (data_entry) { data * 2 }
Definition of a lambda that takes one argument and does some calculation on it. This could be an API call, an SQL query or any other operation that takes some time to complete. Again, for simplicity, I'm just multiplicating the numbers by 2.
results =
This array will contain the results of all the asynchronous operations.
data.map{ |e| Thread.new(e, &operation) }...
For every entry in the data set, spawn a thread that runs operation and pass the entry as argument. This is the data_entry argument in the lambda.
...map{ |t| t.value }
Extract the value from each thread. This will wait for the thread to finish first, so by the end of this line all your data will be there.
Lambdas
Lambdas are really just glorified blocks that raise an error if you pass in the wrong number of arguments. The syntax -> (arguments) {code} is just syntactic sugar for Lambda.new { |arguments| code }.
When a method accepts a block like Thread.new { do_async_stuff_here } you can also pass a Lambda or Proc object prefixed with & and it will be treated the same way.

Validating call arguments with a closure when stubbing return value

I have a question about validating arguments in a mock call with a closure. Sometimes I do this:
customerRepository.save({ Customer customer ->
assert ...
assert ...
}) >> { ... some return value ... }
etc. i.e. multiple (but not too many) asserts in the closure, and also want to stub the call to return something. What I found out is that the code above doesn't work, I need to return a truthy value from the closure, otherwise the object I want to return is not returned and the test will fail somewhere else.
I don't think this is documented, could anybody say what the rules here are exactly?
Edit: actually, I've just checked and I need to return a truthy value even if I don't stub the return value.
So far i know two options for validating arguments. Either match the arguments in-place which does not require asserts:
then:
1 * customerRepository.save({ it.id == 1 && it.name == "joe" }) >> returnValue
However, this will give you "too few invocations" if the validation fails which I find misleading in some cases and usually harder to debug.
Alternatively, match all arguments and assert in the implementation:
then:
1 * customerRepository.save(_) >> { Customer customer ->
assert customer.id == 1
assert customer.name == "joe"
return returnValue
}
This will give you very descriptive assertion errors.

Grails <where> returns DetachedCriteria with all entries

Domain.where {
1 == 0
}.count()
This returned all the elements of the domain class. The more general case:
Domain.where {
false
}.count()
Will return all elements; if I use one of the fields and make a false condition, the result is as expected.
My question is why does this happen (the first case) ? If it is a too naive question, please just suggest some reading material. Thanks!
The version of grails that I use is 2.3.6 (it may be different in newer versions?)
I'm not sure what you are trying to achieve, but here is an explanation (maybe a bit general because of that :).
What you pass to the where method is actually a DSL for specifying SQL criterias, it just uses normal Groovy syntax to pretend to be more natural. But when you do someProperty != 5 && someOtherProperty == 6 that is not evaluated directly, but transformed to end up in an SQL query as select * from Domain where some_property != 5 and some_other_property = 6.
Since you are not passing any reference to a property in your criteria (1 == 0), it gets ignored by the detached criteria DSL evaluator, thus returning the result of select * from domain. You can try to do for example id != id to see how you get an empty list as the result. If you again examine the resulting query, you'll see that a where id<>id is included.
You can learn more about the where method: https://grails.github.io/grails-doc/latest/guide/GORM.html#whereQueries
Bear in mind that what you pass to the where method is a Closure, so the code inside is not executed upfront, and is not necessarily evaluated in the context where it was declared. You can learn more about Groovy Closures. Also about creating DSLs with Groovy, though is a bit of an advanced topic.
(I simplified the SQL queries to make them more undestandable, if you activate the query log of Hibernate or MySQL/other DB you are using, you'll see they are bigger).
To illustrate Deigote's explanation, here's a very crude implementation of a WHERE query builder (actually just the WHERE clause) using the criteria criteria format:
class WhereBuilder {
def conditions = []
def eq(column, value) {
conditions << [
column: column,
operator: '=',
value: value]
}
def ne(column, value) {
conditions << [
column: column,
operator: '!=',
value: value]
}
String toString() {
'WHERE ' <<
conditions.collect {
"${it.column} ${it.operator} '${it.value}'"
}.join(' AND ')
}
}
def builder = new WhereBuilder()
builder.with {
1 == 0
false
eq 'firstName', 'John'
ne 'lastName', 'Smith'
}
assert builder.toString() == "WHERE firstName = 'John' AND lastName != 'Smith'"
As you can see, the expressions 1 == 0 and false have no effect.

Resources