Skip to content

Commit 061dc8d

Browse files
committed
Adds more documentation to the README.md
1 parent 4eea0cf commit 061dc8d

File tree

1 file changed

+194
-15
lines changed

1 file changed

+194
-15
lines changed

README.md

Lines changed: 194 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ActiveModel::Relation
22

3-
A library that allows querying of collections of Ruby objects, with a similar interface to `ActiveRecord::Relation`.
3+
Query a collection of ActiveModel objects like an ActiveRecord::Relation.
44

55
## Installation
66

@@ -14,6 +14,8 @@ If bundler is not being used to manage dependencies, install the gem by executin
1414

1515
## Usage
1616

17+
### Initialization
18+
1719
Create a new relation by passing the model class and a collection:
1820

1921
```ruby
@@ -25,30 +27,144 @@ relation = ActiveModel::Relation.new(Project, [
2527
])
2628
```
2729

28-
Afterwards you can use it (almost) like an `ActiveRecord::Relation`.
30+
As an alternative, it's also possible to create a collection for a model without explicitly passing a collection.
31+
In this case, the library will attempt to call `Project.records` to get the default collection. If the method doesn't exist or returns `nil`, the collection will default to an empty array.
32+
33+
```ruby
34+
class Project
35+
def self.records
36+
[
37+
Project.new(id: 1, state: 'draft', priority: 1),
38+
Project.new(id: 2, state: 'running', priority: 2),
39+
Project.new(id: 3, state: 'completed', priority: 3),
40+
Project.new(id: 4, state: 'completed', priority: 1)
41+
]
42+
end
43+
end
44+
45+
relation = ActiveModel::Relation.new(Project)
46+
```
47+
48+
### Querying
49+
50+
An `ActiveModel::Relation` can be queried almost exactly like an `ActiveRecord::Relation`.
51+
52+
#### `#find`
53+
54+
You can look up a record by it's primary key, using the `find` method. If no record is found, it will raise a `ActiveModel::Relation::RecordNotFound` error.
55+
56+
```ruby
57+
project = relation.find(1)
58+
```
59+
60+
By default, `ActiveModel::Relation` will assume `:id` as the primary key. You can customize this behavior by setting a `primary_key` on the model class.
61+
62+
```ruby
63+
class Project
64+
def self.primary_key = :identifier
65+
end
66+
```
67+
68+
When passed a block, the `find` method will behave like `Enumerable#find`.
69+
70+
```ruby
71+
project = relation.find { |p| p.id == 1 }
72+
```
73+
74+
#### `#find_by`
75+
76+
To look up a record based on a set of arbitary attributes, you can use `find_by`. It accepts the same arguments as `#where` and will return the first matching record.
77+
78+
```ruby
79+
project = relation.find_by(state: 'draft')
80+
```
81+
82+
#### `#where`
83+
84+
To filter a relation, you can use `where` and pass a set of attributes and the expected values. This method will return a new `ActiveModel::Relation` that only returns the matching records, so it's possible to chain multiple calls. The filtering will only happen when actually accessing records.
2985

3086
```ruby
31-
relation.find(1)
32-
relation.find_by(state: 'draft')
3387
relation.where(state: 'completed')
88+
```
89+
90+
The following two lines will return the same filtered results:
91+
92+
```ruby
93+
relation.where(state: 'completed', priority: 1)
94+
relation.where(state: 'completed').where(priority: 1)
95+
```
96+
97+
To allow for more advanced filtering, `#where` allows filtering using a block. This works similar to `Enumerable#select`, but will return a new `ActiveModel::Relation` instead of an already filtered array.
98+
99+
```ruby
100+
relation.where { |p| p.state == 'completed' && p.priority == 1 }
101+
```
102+
103+
#### `#where.not`
104+
105+
Similar to `#where`, the `#where.not` chain allows you to filter a relation. It will also return a new `ActiveModel::Relation` with that returns only the matching records.
106+
107+
```ruby
34108
relation.where.not(state: 'draft')
35-
relation.offset(3)
36-
relation.limit(2)
109+
```
110+
111+
To allow for more advanced filtering, `#where.not` allows filtering using a block. This works similar to `Enumerable#reject`, but will return a new `ActiveModel::Relation` instead of an already filtered array.
112+
113+
```ruby
114+
relation.where.not { |p| p.state == 'draft' && p.priority == 1 }
115+
```
116+
117+
### Sorting
118+
119+
It is possible to sort an `ActiveModel::Relation` by a given set of attribute names. Sorting will be applied after filtering, but before limits and offsets.
120+
121+
#### `#order`
122+
123+
To sort by a single attribute in ascending order, you can just pass the attribute name to the `order` method.
124+
125+
```ruby
37126
relation.order(:priority)
38-
relation.order(priority: :asc, state: :desc)
39-
relation.extending(Pagination)
40-
relation.only(:where)
41-
relation.except(:limit, :offset)
42127
```
43128

44-
It's also possible to use method calls for filtering, while still returning a `ActiveModel::Relation`.
129+
To specify the sort direction, you can pass a hash with the attribute name as key and either `:asc`, or `:desc` as value.
45130

46131
```ruby
47-
relation.where { |project| project.completed? }
48-
relation.where.not { |project| project.draft? }
132+
relation.order(priorty: :desc)
49133
```
50134

51-
After including `ActiveModel::Relation::Model`, the library also supports named scope methods on the model class.
135+
To order by multiple attributes, you can pass them in the order of specificity you want.
136+
137+
```ruby
138+
relation.order(:state, :priority)
139+
```
140+
141+
For multiple attributes, it's also possible to specify the direction.
142+
143+
```ruby
144+
relation.order(state: :desc, priority: :asc)
145+
```
146+
147+
### Limiting and offsets
148+
149+
#### `#limit`
150+
151+
To limit the amount of records returned in the collection, you can call `limit` on the relation. It will return a new `ActiveModel::Relation` that only returns the given limit of records, allowing you to chain multiple other calls. The limit will only be applied when actually accessing the records later on.
152+
153+
```ruby
154+
relation.limit(10)
155+
```
156+
157+
#### `#offset`
158+
159+
To skip a certain number of records in the collection, you can use `offset` on the relation. It will return a new `ActiveModel::Relation` that skips the given number of records at the beginning. The offset will only be applied when actually accessing the records later on.
160+
161+
```ruby
162+
relation.offset(20)
163+
```
164+
165+
### Scopes
166+
167+
After including `ActiveModel::Relation::Model`, the library also supports calling class methods defined on the model class as part of the relation.
52168

53169
```ruby
54170
class Project
@@ -64,8 +180,71 @@ class Project
64180
where(state: 'completed')
65181
end
66182
end
183+
```
184+
185+
Given the example above, you can now create relations like you're used to from `ActiveRecord::Relation`.
186+
187+
```ruby
188+
projects = Project.all
189+
completed_projects = all_projects.completed
190+
important_projects = all_projects.where(priority: 1)
191+
```
192+
193+
### Spawning
194+
195+
It's possilbe to create new versions of a `ActiveModel::Relation` that only includes certain aspects of the `ActiveModel::Relation` it is based on. It's currently possible to customize the following aspects: `:where`, `:limit`, `:offset`.
196+
197+
#### `#except`
198+
199+
To create a new `ActiveModel::Relation` without certain aspects, you can use `except` and pass a list of aspects, you'd like to exclude from the newly created instance. The following example will create a new `ActiveModel::Relation` without any previously defined limit or offset.
200+
201+
```ruby
202+
relation.except(:limit, :offset)
203+
```
204+
#### `#only`
205+
206+
Similar to `except`, the `only` method will return a new instance of the `ActiveModel::Relation` it is based on but with only the passed list of aspects applied to it.
207+
208+
```ruby
209+
relation.only(:where)
210+
```
211+
212+
### Extending relations
213+
214+
#### `#extending`
215+
216+
In order to add additional methods to a relation, you can use `extending`. You can either pass a list of modules that will be included in this particular instance, or a block defining additional methods.
217+
218+
```ruby
219+
module Pagination
220+
def page_size = 25
221+
222+
def page(page)
223+
limit(page_size).offset(page.to_i * page_size)
224+
end
225+
226+
def total_count
227+
except(:limit, :offset).count
228+
end
229+
end
230+
231+
relation.extending(Pagination)
232+
```
67233

68-
relation.completed
234+
The following example is equivalent to the example above:
235+
236+
```ruby
237+
relation.extending do
238+
def page_size = 25
239+
240+
def page(page)
241+
limit(page_size).offset(page.to_i * page_size)
242+
end
243+
244+
def total_count
245+
except(:limit, :offset).count
246+
end
247+
end
69248
```
70249

71250
## Development

0 commit comments

Comments
 (0)