-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add election and legislative period classes for events #86
Conversation
32b0974
to
1a16c61
Compare
1a16c61
to
07ec8fb
Compare
end | ||
alias terms legislative_periods | ||
|
||
def current_legislative_period | ||
legislative_periods.last | ||
legislative_periods.first |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm assuming this means that legislative_periods
has now reversed order? That's not necessarily a bad thing, but it definitely needs to be explicit in the CHANGELOG.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah no, this should probably have squashed down as it's just reverting a change in e81a50f.
@@ -38,8 +38,29 @@ def wikidata | |||
attr_reader :document | |||
end | |||
|
|||
class DynamicEventClassFinder | |||
def self.new(doc, *args) | |||
case doc[:classification] |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We've only just removed one giant case
statement for this sort of thing. Bringing back another one doesn't seem ideal. We can't quite do the same declarative in-place approach as then, but making this data driven seems like it might be slightly better.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So, this is the problem I was trying to solve with all the meta-programming in #75. I can't see how to make this work without some way of getting a list of all the possible subclasses to match against.
We could improve it slightly by pushing the match down into the subclass but we'd still need a case statement. A further improvement might be to have a list of subclasses and then looping over those doing the matching based on a property in the class which reduces the code a bit:
def self.new(doc, *args)
subclasses = [Election, LegislativePeriod]
klass = subclasses.select { |s| s.classification == doc[:classification] }.first || Event
klass.new(doc, *args)
end
which needs a bit of tidying but does work with the addition of classification
to Entity
and the relevant calls in Election
and LegislativePeriod
.
I did have a look at how to push things up from the subclasses into the parent in a add_match_class Election
sort of way but it was looking like being a bit awful and also feels wrong.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking of something as simple as:
def self.new(doc, *args)
classifications = {
'general election' => Election,
'legislative period' => LegislativePeriod,
}
classifications.fetch(doc[:classification], Event).new(doc, *args)
end
(Though with also moving that Hash definition out of new
— inlined here just as proof of concept)
As with your approach the primary goal is simply to rid of all that duplicated logic in the case statement (and indeed the case
itself, which is pretty much always a warning flag in anything OO based), and move the thing that varies to data instead, which should be easier to maintain, alter etc.
But your suggestion seems a little better. Ideally we want to follow Open/Closed here — adding a new subclass shouldn't require changing a superclass too (both for maintainability, and because it's very bad practice for a superclass know anything about its children).
@@ -38,8 +38,29 @@ def wikidata | |||
attr_reader :document | |||
end | |||
|
|||
class DynamicEventClassFinder |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a better name for this, based on what it is, rather than what it does?
(Also, what's the path to adding more of these, e.g. for having separate Party
and Legislature
subclasses of Organization
? Do we extend this? Or add a parallel version somewhere?)
d025476
to
ee6ceef
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm still not entirely sure whether we're heading in the right direction with this. Some of this, I suspect, is because the Popolo we're dealing with is generally still quite slim, compared to what we're likely to have in the (hopefully not-too-distant) future, as we start to add many more types of information.
But I'm having some difficulty trying to reason about how some things in the future will need to work. (NB this isn't about getting everything perfect now — but this is a library that external people are using, and which we actively want lots of people to use, so I'm particularly cautious that we don't want to paint ourselves into too many nasty corners with approaches that people come to rely on but turn out to have been the wrong design)
One realisation I had whilst thinking about this again this morning is that the Organization example I've been using will get even more complicated when we have something like Committee memberships, as that will have an impact on both Organization(s) and Membership(s). I suspect it's going to be worth mocking up an example Popolo file with those, and making sure our current approach is going to make it not just possible, but ideally fairly trivial, to add a new Organization type like that (ideally entirely declaratively), and have everything Do The Right Thing™.
(That we're also actively trying to change various other things about how the class hierarchy works at the same time doesn't help (#88) — I'm starting to have some difficulty keeping the various changes in my head at the same time. So I also suspect that we should try to close #54 first, to avoid any more odd interactions between these. @struan I think you were already looking at that one at some point already? Might be useful if you can help @ondenman finish that one off before coming back to this one.)
end | ||
|
||
def self.find_class(classification) | ||
subclasses.select { |s| s.classification == classification }.first || default_class |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.select { }.first
is generally simpler as .find { }
entity_class EventFactory | ||
|
||
def elections | ||
where(classification: 'general election').sort_by(&:start_date) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if Events are already pre-sorted in popolo.rb
, do we need to also sort here (and in legislative_periods
below)?
e12791a
to
00b7195
Compare
LegislativePeriod subclasses from Event. Is an entity of LegislativePeriods.
This (ab)uses the `entity_class` method to dynamically look up an entity class for Events.
also change current_term to point to the last one as that's the order they are in
This does much the same thing as the DynamicEventClassFinder but instead the declaration of the subclasses happens in the Event class which means this can be re-used for other similar classes
this adds an EntityFactory which EventFactory inherits from to configure the subclasses and default class. This is then used as the entity_class for Events. This means we are creating the objects in an intermediate class which is less confusing and fragile than the Event class returning objects which are not Events. It also allows us to declare the possible Event classes in a fairly straight forward way, and doesn't rely on meta-programming to work it out. Finally, it should be adaptable to other places we want to use this.
00b7195
to
02a9ef5
Compare
When fetching all the elections/terms from Events rather than hard coding the classification comparison in Events as well as the subclass just use the `.classification` method of the subclass
Closed in favour of #109 |
This adds terms/legislativeperiods and elections methods to popolo that return specific classes for those event types. It also updates Events to generate those classes as part of the Events collection.
Fixes #67