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

bulk add opportunities #341

Merged
merged 1 commit into from
Nov 27, 2015
Merged

bulk add opportunities #341

merged 1 commit into from
Nov 27, 2015

Conversation

chadwhitacre
Copy link
Contributor

Closes #338.

To-do

  • add a little inline documentation of the format
  • add a form field to select the organizer based on available organizers for logged-in user
  • implement idempotency on name
  • provide detailed tabular error reporting

@dmtroyer
Copy link
Contributor

dmtroyer commented Nov 6, 2015

@chadwhitacre
Copy link
Contributor Author

!m @dmtroyer

@chadwhitacre
Copy link
Contributor Author

Over to you, @dmtroyer! :-)

@dmtroyer
Copy link
Contributor

dmtroyer commented Nov 6, 2015

image

@chadwhitacre
Copy link
Contributor Author

!m @dmtroyer

@dmtroyer
Copy link
Contributor

dmtroyer commented Nov 6, 2015

Currently, the csv parsing logic expects a header in the csv which defines the names of the fields being uploaded in the order listed in the header.

name,description
Marbles,"David's got marbles"
"Skeet shooting","Shoot some skeet"

@chadwhitacre
Copy link
Contributor Author

Getting back into this, hitting undefined method id' for nil:NilClassinopportunities_controller.rb:39`:

      Opportunity.transaction do
        CSV.parse(params[:csv].read, headers: true) do |row|
          row['resource_sub_type_id'] = ResourceSubType.find_by_name(row.delete('resource_sub_type')).id
          @opportunities.push(Opportunity.create!(row.to_h))
        end
      end

@chadwhitacre
Copy link
Contributor Author

That's with the test data from above at #341 (comment). Looks like 266ce91 added an expectation for the CSV structure ...

@chadwhitacre
Copy link
Contributor Author

Blam!

name,description,resource_sub_type
Marbles,"David's got marbles",Event
"Skeet shooting","Shoot some skeet",Event

screen shot 2015-11-09 at 9 53 57 am

@chadwhitacre
Copy link
Contributor Author

(For my notes: I found the database config in config/database.yml, and then found the available resource subtypes like so:

learn_dev=# select * from resource_sub_types;
┌────┬───────┬──────────────────┐
│ id │ name  │ resource_type_id │
├────┼───────┼──────────────────┤
│  1 │ Event │                1 │
└────┴───────┴──────────────────┘
(1 row)

learn_dev=#

)

@chadwhitacre
Copy link
Contributor Author

screen shot 2015-11-09 at 10 01 22 am

@chadwhitacre
Copy link
Contributor Author

I've started a to-do list in the PR description.

@dmtroyer
Copy link
Contributor

dmtroyer commented Nov 9, 2015

There are some fields that are required, resource_sub_type was the only one that was actually being validated so that's where I started, and I chose to use the name in the csv. A logged on user can have more than one organizer, so they'll probably have to be explicit or select the organizer as part of the upload form or something.

@chadwhitacre
Copy link
Contributor Author

I'm trying to understand the Opportunity model and the opportunities table. They don't appear to sync up. The latter has way more fields than the former.

class Opportunity < ActiveRecord::Base
  has_many :opportunity_instances
  belongs_to :organizer
  has_one :after_this, class_name: "Opportunity", foreign_key: "after_this_id"
  has_one :before_this, class_name: "Opportunity", foreign_key: "before_this_id"
  belongs_to :resource_sub_type
  has_one :resource_type, through: :resource_sub_type

  validates :resource_sub_type, presence: true

  def active_model_serializer
    OpportunitySerializer
  end
end
Null display is "¤".
Line style is unicode.
Border style is 2.
                                            Table "public.opportunities"
┌───────────────────────┬─────────────────────────────┬────────────────────────────────────────────────────────────┐
│        Column         │            Type             │                         Modifiers                          │
├───────────────────────┼─────────────────────────────┼────────────────────────────────────────────────────────────┤
│ id                    │ integernot null default nextval('opportunities_id_seq'::regclass) │
│ name                  │ character varying           │                                                            │
│ address               │ text                        │                                                            │
│ description           │ text                        │                                                            │
│ registration_url      │ character varying           │                                                            │
│ location_name         │ character varying           │                                                            │
│ registration_deadline │ timestamp without time zone │                                                            │
│ program_type          │ character varying           │                                                            │
│ logo_url              │ character varying           │                                                            │
│ starts_at             │ timestamp without time zone │                                                            │
│ ends_at               │ timestamp without time zone │                                                            │
│ online_address        │ character varying           │                                                            │
│ zipcode               │ character varying           │                                                            │
│ city                  │ character varying           │                                                            │
│ state                 │ character varying           │                                                            │
│ is_online             │ boolean                     │                                                            │
│ hide_reason           │ character varying           │                                                            │
│ hide                  │ boolean                     │                                                            │
│ contact_name          │ character varying           │                                                            │
│ contact_email         │ character varying           │                                                            │
│ contact_phone         │ character varying           │                                                            │
│ price_level           │ integer                     │                                                            │
│ min_age               │ integer                     │                                                            │
│ max_age               │ integer                     │                                                            │
│ extra_data            │ text                        │                                                            │
│ created_at            │ timestamp without time zonenot null                                                   │
│ updated_at            │ timestamp without time zonenot null                                                   │
│ organizer_id          │ integer                     │                                                            │
│ topic_id              │ integer                     │                                                            │
│ after_this_id         │ integer                     │                                                            │
│ before_this_id        │ integer                     │                                                            │
│ badge_class_id        │ character varying           │                                                            │
│ resource_sub_type_id  │ integer                     │                                                            │
└───────────────────────┴─────────────────────────────┴────────────────────────────────────────────────────────────┘
Indexes:
    "opportunities_pkey" PRIMARY KEY, btree (id)
    "index_opportunities_on_after_this_id" btree (after_this_id)
    "index_opportunities_on_before_this_id" btree (before_this_id)
    "index_opportunities_on_organizer_id" btree (organizer_id)
    "index_opportunities_on_resource_sub_type_id" btree (resource_sub_type_id)
    "index_opportunities_on_topic_id" btree (topic_id)
Foreign-key constraints:
    "fk_rails_6d4b8f5561" FOREIGN KEY (topic_id) REFERENCES topics(id)

@chadwhitacre
Copy link
Contributor Author

A logged on user can have more than one organizer, so they'll probably have to be explicit or select the organizer as part of the upload form or something.

Okay, noted in to-do.

@chadwhitacre
Copy link
Contributor Author

@dmtroyer Do we have a test suite? How do I run it?

@chadwhitacre
Copy link
Contributor Author

$ bundle exec rake test
[...]
35 runs, 0 assertions, 0 failures, 35 errors, 0 skips

@dmtroyer
Copy link
Contributor

dmtroyer commented Nov 9, 2015

I'm trying to understand the Opportunity model and the opportunities table. They don't appear to sync up. The latter has way more fields than the former.

Rails is cheeky like that, it dynamically creates default getter and setters for attributes defined in the schema. So, if you're cool with the defaults they don't have to be explicitly defined in the model.

@chadwhitacre
Copy link
Contributor Author

Okay, thanks for the pointer.

$ head -n12 db/schema.rb
# encoding: UTF-8
# This file is auto-generated from the current state of the database. Instead
# of editing this file, please use the migrations feature of Active Record to
# incrementally modify your database, and then regenerate this schema definition.
#
# Note that this schema.rb definition is the authoritative source for your
# database schema. If you need to create the application database on another
# system, you should be using db:schema:load, not running all the migrations
# from scratch. The latter is a flawed and unsustainable approach (the more migrations
# you'll amass, the slower it'll run and the greater likelihood for issues).
#
# It's strongly recommended that you check this file into your version control system.

@chadwhitacre
Copy link
Contributor Author

There are some fields that are required

How is this information encoded? I only see three out of 33 columns in the schema that are not null, and I don't see anything suggestive of requirement in opportunity.rb.

@dmtroyer
Copy link
Contributor

dmtroyer commented Nov 9, 2015

How is this information encoded? I only see three out of 33 columns in the schema that are not null, and I don't see anything suggestive of requirement in opportunity.rb.

A couple of different ways:

  1. At the database and schema level through migrations
  2. At the model level through validations https://github.com/saxifrage/cityasacampus/blob/master/app/models/opportunity.rb#L9

@chadwhitacre
Copy link
Contributor Author

@dmtroyer I only see one validated (required) field in opportunity.rb. In schema.rb there are only two null: false fields (there is of course an id field in the actual database schema that is also not null): created_at and updated_at. Are those the only three (or four) required fields, then?

The backtrace at #341 (comment) seems to indicate that organizer_id ought to be required as well, and that therefore in general we are not yet requiring all of the fields that we should be.

@dmtroyer
Copy link
Contributor

dmtroyer commented Nov 9, 2015

@whit537 All correct. Field requirement and validation has not been completely flushed out.

@chadwhitacre
Copy link
Contributor Author

Okay!

@chadwhitacre
Copy link
Contributor Author

I'm gonna pull @timothyfcook over to make some decisions about required fields for the CSV upload.

@dmtroyer
Copy link
Contributor

dmtroyer commented Nov 9, 2015

!m @whit537 @timothyfcook

@chadwhitacre
Copy link
Contributor Author

The word from @timothyfcook:

name
description
registration_url
program_type
logo_url
is_online
contact_name
contact_email
contact_phone
price_level
min_age
max_age
organizer_id
topic_id
resource_sub_type_id

If is_online, then online_address should be required. Otherwise, address, city, state, and zipcode should be required.

@chadwhitacre
Copy link
Contributor Author

screen shot 2015-11-09 at 12 24 40 pm

@chadwhitacre
Copy link
Contributor Author

Aren't there supposed to be gems for everything? Isn't that why we use Rails? Let's go look! :-)

https://www.ruby-toolbox.com/categories/CSV_Parsers

validates :registration_deadline, presence: true
validates :starts_at, presence: true
validates :ends_at, presence: true

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

dat validation 👍

@dmtroyer
Copy link
Contributor

@whit537 thought I'd help out a little with the uniqueness constraint on name per organizer. Active Record Uniqueness Validations save the day here.

@chadwhitacre
Copy link
Contributor Author

Blam. Thanks, @dmtroyer. I was just about to ask you for help! :-)

screen shot 2015-11-09 at 7 56 26 pm

@chadwhitacre
Copy link
Contributor Author

And with that, I'm out for the rest of the week. Heading to New York for gratipay/inside.gratipay.com#384 this weekend. See you next week! :-)

@dmtroyer
Copy link
Contributor

@whit537 have fun! 🚌 🌆 💵

@chadwhitacre
Copy link
Contributor Author

/me back ...

@chadwhitacre
Copy link
Contributor Author

Rebased on master. Former head was c33b567.

@chadwhitacre
Copy link
Contributor Author

screen shot 2015-11-17 at 3 45 02 pm

@chadwhitacre
Copy link
Contributor Author

Working on inline docs.

screen shot 2015-11-23 at 9 20 40 am

@chadwhitacre
Copy link
Contributor Author

I added a dynamic listing of the possible values for topic and resource_sub_type.

screen shot 2015-11-23 at 9 35 26 am

@chadwhitacre
Copy link
Contributor Author

Okay! I think this is ready to merge.

Waddya think, @dmtroyer @timothyfcook? :-)

@timothyfcook
Copy link
Contributor

I'll let @dmtroyer sign off on this one

@chadwhitacre
Copy link
Contributor Author

Waddya say, @dmtroyer? :-)

@dmtroyer
Copy link
Contributor

Will do sometime today!

On Fri, Nov 27, 2015, 10:25 Chad Whitacre [email protected] wrote:

Waddya say, @dmtroyer https://github.com/dmtroyer? :-)


Reply to this email directly or view it on GitHub
#341 (comment)
.

@dmtroyer dmtroyer force-pushed the bulk-add-opportunities branch from 1010d72 to 565e801 Compare November 27, 2015 20:20
@dmtroyer
Copy link
Contributor

oops, unsure what the previous head was. sorry, seems to be OK. I'll probably squash it before I merge it, anyhow...

@dmtroyer dmtroyer force-pushed the bulk-add-opportunities branch from 4231420 to bf58481 Compare November 27, 2015 20:40
dmtroyer added a commit that referenced this pull request Nov 27, 2015
@dmtroyer dmtroyer merged commit e74891a into master Nov 27, 2015
@dmtroyer dmtroyer deleted the bulk-add-opportunities branch November 27, 2015 20:41
@chadwhitacre
Copy link
Contributor Author

!m @dmtroyer

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants