Skip to content
Newer
Older
100644 167 lines (111 sloc) 4.94 KB
56e91ac @bokmann initial braindump
bokmann authored Mar 25, 2012
1 # In the spirit of "make it look the way you want it to work, then make it
2 # work like that", here is a vision of the Repository pattern ala Ruby. I
3 # don't want to fault ActiveRecord for being an implementation of the
4 # ActiveRecord pattern; At RailsConf 2006, in his keynote, Martin Fowler
5 # said that ActiveRecord was "the most faithful implementation of the
6 # Activerecord pattern [he] had ever seen", and he was the one that
7 # documented that pattern in the first place. So lets respect it for what
8 # it is, and look at the repository pattern, done with Rails Flair.
9
10
11
12 # While Person is a plain old Ruby object, inheriting from something like
13 # ActiveModel::Relationships gives part of the dsl we are used to. the clues
14 # from that dsl could also be used by Repository implementations.
15
16 class Person
17 include ActiveModel::Relationships
18
19 has_many :hobbies
20 belongs_to :fraternity
21
22 attr_accessor :name
23 attr_accessor :password
24 attr_accessor :sex
25 attr_accessor :born_on
26 end
27
28 # conventions like _on and _at are still used.
29
30
31 # The person has no clue it is 'Persistent Capable'. All of that is handled
32 # in the Repository. Notice we could call the Repo anything we want.
33
34 class PersonStore < ActiveRepository::Base
35 repository_for :person
36
37 # other things that control the persistence can go here
38 table :users
39 encrypts_attribute :password
40 map_attribute_to_column :sex, :gender
41 end
42
43
44 # I would really like to have the ability to 'new' model objects as normal:
45
46 p = Person.new
47
48 # but it might be necessary to create them through the store, like:
49
50 p = PersonStore.create #, or
51 p = PersonStore.build
52
53 # saving is no longer done on the object, but through the repository
54
55 PersonStore.save(p)
56
57 # all the usual finder suspects are on the repository
58 p = PersonStore.find(5)
59 p = PersonStore.first
60 p = PersonStore.all
61 p = PersonStore.find_by_name("Chris")
62
63 # we could also create things like scopes, etc.
64
65
66 # Since Person is nothing special, you could easily swap in something like:
67
68 Class PersonStore < RedisRepository::Base
69 ...
70 end
71
72 # or even:
73
74 Class PersonStore < RestfulResource::Repository
75 repository_for :person
76 base_uri "http://coolstuff.livingsocial.com"
77 end
78
79
80 # Swapping repositories might give you radically different methods (only an
81 # ActiveRepository would give you a find_by_sql method, for instance), but
82 # thats ok. The "Persistant Capable" classes don't change with a change in
83 # the persistence mechanism. The "Persistent Aware" classes, like the
84 # controllers, would.
85
86
87 # And it might even be possible to have multiple repositories in the same
88 # app...
89
90 Class PersonStore < ActiveRepository::Base
91 #...
92 end
93
94 # and
95
96 Class RemotePersonStore < RestfulResource::Repository
97 #...
98 end
99
100 # and then you could do stuff like:
101
102 p = RemotePersonStore.find(5)
103 PersonStore.save(p)
104
105 # and essentially use two repositories as an ETL engine.
106
107
108 # One nice thing we get from ActiveRecord would have to change slightly -
109 # the migrations.
110 # Actually, the migrations could work pretty much as they do now, but devs
111 # would have to make the corresponding attr_accessor declarations in their
112 # models.
113 #
114 # if an attr_accessor was declared that didn't have a corresponding column in
115 # the db, then it could warn on application init. That warning for that field
116 # could be silenced in the repository with something like:
117
118 not_persisted :current_location
119
120 # and in reverse, if a migration added a column that couldn't map to an
121 # attr_accessor, it could warn unless the repo had a line like:
122
123 ignore_column :eye_color
124
125 # (of course, nosql dbs may have their own requirements met with their own
126 # dsls).
127
128 # You could even have the store do something like:
129
130 synthetic_field :age, { Date.today - born_on }
131
132 # and as shown above, if you wanted a different attribute name, you could:
133
134 map_attribute_to_column :sex, :gender
135
136 # One potentially awesome tweak to the dsl is how this would
137 # handle polymorphic tables:
138
139 class PersonStore < ActiveRepository::Base
140 repository_for :person
141 repository_for :client
142 repository_for :administrator
143 polymorphic_model_column :type
144 end
145
146
147
148 # Given this pattern, I relationship declarations go in the models,
149 # since there they can add the appropriate methods to return collections,
150 # etc, and since the repo knows what its supposed to manage, it can get
151 # access to the same knowledge to do whatever it needs to do. If they were
152 # declared in the repo, it would be inappropriate for the model to
153 # introspect there, otherwise the model would become 'persistence aware'.
154 # They 'feel' a little attr_accessor-like things to me.
155
156 # Finally, while the model as code looks completely unaware of its storage,
157 # underneath the covers at runtime the repository could 'enhance' it with
158 # things like dirty flags for fields, is_dirty? & is_new? methods, etc.
159 # In fact, for years I used an object-oriented database in Java named
160 # Versant, and it had a 'bytecode enhancement' step that did exactly this
161 # during the compile - it modified the classes bytecode with dirty flags
162 # and other persistence helpers.
163
164
165
166
Something went wrong with that request. Please try again.