Skip to content
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

Closed
wants to merge 16 commits into from

Conversation

struan
Copy link
Contributor

@struan struan commented Oct 3, 2016

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

@struan struan added the 3 - WIP label Oct 3, 2016
@struan struan force-pushed the create-term-classes-dynamic-event-class branch from 32b0974 to 1a16c61 Compare October 3, 2016 16:21
@struan struan changed the title WIP Add election and legislative period classes for events Add election and legislative period classes for events Oct 3, 2016
@struan struan force-pushed the create-term-classes-dynamic-event-class branch from 1a16c61 to 07ec8fb Compare October 4, 2016 08:47
end
alias terms legislative_periods

def current_legislative_period
legislative_periods.last
legislative_periods.first
Copy link
Contributor

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.

Copy link
Contributor Author

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]
Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

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
Copy link
Contributor

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?)

@struan struan force-pushed the create-term-classes-dynamic-event-class branch from d025476 to ee6ceef Compare October 4, 2016 14:27
Copy link
Contributor

@tmtmtmtm tmtmtmtm left a 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
Copy link
Contributor

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)
Copy link
Contributor

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)?

@tmtmtmtm tmtmtmtm assigned struan and unassigned tmtmtmtm Oct 10, 2016
@struan struan force-pushed the create-term-classes-dynamic-event-class branch 3 times, most recently from e12791a to 00b7195 Compare October 13, 2016 16:32
ondenman and others added 15 commits October 14, 2016 11:30
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.
@struan struan force-pushed the create-term-classes-dynamic-event-class branch from 00b7195 to 02a9ef5 Compare October 14, 2016 10:31
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
@struan
Copy link
Contributor Author

struan commented Oct 14, 2016

Closed in favour of #109

@struan struan closed this Oct 14, 2016
@struan struan removed the 3 - WIP label Oct 14, 2016
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants