Something like this:
q = User.where(a)
.where(b)
.where(c)
is equivalent to:
q = User
q = q.where(a)
q = q.where(b)
q = q.where(c)
So you could write:
users = User
keywords.each do |keyword|
users = users.where(query, *keyword)
end
But any time you see that sort of feedback pattern (i.e. apply an operation to the operation's result or f(f( ... f(x)))) you should start thinking about Enumerable#inject (AKA Enumerable#reduce):
users = keywords.inject(User) { |users, k| users.where(query, *k) }
That said, your query has two placeholders but keywords is just a flat array so you won't have enough values in:
users.where(query, *k)
to replace the placeholders. I think you'd be better off using a named placeholder here:
query = 'name like :k or desc like :k'
keywords = %w[abc 123]
users = keywords.inject(User) { |users, k| users.where(query, k: k) }
You'd probably also want to include some pattern matching for your LIKE so:
query = "name like '%' || :k || '%' or desc like '%' || :k || '%'"
users = keywords.inject(User) { |users, k| users.where(query, k: k)
where || is the standard SQL string concatenation operator (which AFAIK not all databases understand) and % in a LIKE pattern matches any sequence of characters. Or you could add the pattern matching in Ruby and avoid having to worry about the different ways that databases handle string concatenation:
query = 'name like :k or desc like :k'
users = keywords.inject(User) { |users, k| users.where(query, k: "%#{k}%")
Furthermore, this:
User.where("name LIKE(?) OR desc LIKE(?)",'abc','abc')
.where("name LIKE(?) OR desc LIKE(?)",'123','123')
produces a WHERE clause like:
where (name like 'abc' or desc like 'abc')
and (name like '123' or desc like '123')
so you're matching all the keywords, not any of them. This may or may not be your intent.