Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/mauricioszabo/activerecord_finder
Easier finders for Arel (and ActiveRecord) objects
https://github.com/mauricioszabo/activerecord_finder
Last synced: about 3 hours ago
JSON representation
Easier finders for Arel (and ActiveRecord) objects
- Host: GitHub
- URL: https://github.com/mauricioszabo/activerecord_finder
- Owner: mauricioszabo
- Created: 2013-06-17T00:20:15.000Z (over 11 years ago)
- Default Branch: master
- Last Pushed: 2013-11-21T20:54:54.000Z (about 11 years ago)
- Last Synced: 2024-11-09T05:31:55.393Z (about 2 months ago)
- Language: Ruby
- Size: 164 KB
- Stars: 0
- Watchers: 3
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
Arel Finders
============The problem with ActiveRecord today is that there is no way to
construct queries outside "attribute = value" or using "OR" operator.
The solutions so far are not so easy to do, and usualy involves creating
fragments of SQL in string form.But we have Arel. So, this must be solved, right?
Unfortunately, no. Arel's API is quite dificult to use, and its API is quite
inconsistent and changes over time, resulting in difficult code to mantain.## ActiveRecord::Finder
So, the solution was to create an object, an "adapter-kind" of object.
ActiveRecord::Finder is a kind of object that contains all attributes
from a table, and allows you to `OR` or `AND` queries.```ruby
finder = ActiveRecord::Finder.new(Arel::Table.new(:users))
query = finder.age > 10 | finder.name == "Foo"
query.arel.to_sql
#Returns: users.age > 10 OR users.name = 'Foo'
```But it is kind of tedious to do this everytime you want to search for users,
so you can use ActiveRecord::Base#restrict with a block to construct your queries.## ActiveRecord's Finders
Integrating with ActiveRecord, it is possible to use `restrict` to find records on a ActiveRecord object. The syntax is quite simple (Please note that because of operator precedence in Ruby, we must add a parenthesis over the comparission before using `&` or `|`):
```ruby
User.restrict { |f| f.age > 18 }
#or
User.restrict { age > 18 }
```ANDs work on the same way, too:
```ruby
User.restrict { age > 18 & age < 20 }
```### All finders, so far:
```ruby
#WHERE NOT (users.age < 10)
User.restrict { !(age < 10) }#Comparission
User.restrict { age == 20 }
User.restrict { age >= 20 }
User.restrict { age <= 20 }
User.restrict { age != 20 }
User.restrict { age > 20 }
User.restrict { age < 20 }#IN
User.restrict { age.in?([10, 20, 30]) }#LIKE
User.restrict { name =~ "Foo%" }
User.restrict { name !~ "Foo%" }#AND, and OR
User.restrict { (name == "Foo") & (age < 20) }
User.restrict { (name == "Foo") | (age < 30) }
User.restrict do
( (name == "Foo") & (age < 20) ) |
( (name == "Bar") | (age > 20) )
end
```Please note that we can't use instance variables if we use the block syntax without a variable. This is a Ruby's limitation, not ActiveRecord Finder's one.
```ruby
class Foo
def initialize
@name = "Bar"
end
def find_all
User.restrict { name == @name } #Doesn't work - @name is nil
User.restrict { |u| u.name == @name } #Works!
end
end
```## Constructing Queries
To avoid "merging" ActiveRecord::Relation's, there is a specific syntax to construct a complex query on ActiveRecord::Finder.
```ruby
def search(name_to_search, params = {})
query = User.new_finder { name == name_to_search }
query = query & User.new_finder { age > params[:age] } if params.include?(:age)
query = query | User.new_finder { age <= 10 } if params[:include_all_childs]
User.restrict(query)
end
```### Using "joins"
To be simple, the `Finder` simply has the attributes from the table that it was created. So, to be able to find a record using attributes from one of the joined tables, it is possible to use `ActiveRecord::Base#merge`. If it's absolutely necessary, we could use `restrict_with` or `new_finder_with` to construct queries that uses fields from another tables (or table aliases). For example, consider the following examples:
```ruby
#Finding user's roles for specific users
admins = User.admins #(or User.where(admin: true) or User.restrict { admin == true }
roles = Role.restrict { name != "top secret administrative stuff" }
admin_roles = roles.joins(:users).merge(admins)#Finding without "merge" (but you cannot use "scopes" in this case,
#nor use the block syntax without variables)
admin_roles = Role.joins(:users).restrict_with(:users) do |role, user|
(role.name != "top secret administrative stuff") & (user.admin == true)
end#Finding using a "table alias" or some other thing
table = User.where(name: "Foo").as('alias')
User.from([User.arel_table, table]).restrict_with(:alias) do |user, table|
user.id == table.id
end
#Will produce the query:
#SELECT `users`.*
#FROM `users`, (SELECT `users`.* FROM `users` WHERE `users`.`name` = 'Foo') alias
#WHERE `users`.`id` = `alias`.`id`
```