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 foreign key option to Fields #807

Merged
merged 9 commits into from
Jul 31, 2017

Conversation

iarie
Copy link
Contributor

@iarie iarie commented Mar 31, 2017

#777

  • Allows to specify foreign_key option in associative fields
  • Updates example_app to demonstrate this feature

deferred.permitted_attribute(:foo)
context "when not given a `foreign_key` option" do
it "delegates to the backing class" do
deferred = Administrate::Field::Deferred.new(Administrate::Field::String)

Choose a reason for hiding this comment

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

Line is too long. [81/80]

expect(field.permitted_attribute).to eq("customers_uuid")
end
end

Choose a reason for hiding this comment

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

Extra empty line detected at block body end.

{code: 'DE', name: 'Germany'},
{code: 'CA', name: 'Canada'},
{code: 'BR', name: 'Brazil'},
])

Choose a reason for hiding this comment

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

Indent the right bracket the same as the first position after the preceding left parenthesis.

{code: 'RU', name: 'Russia'},
{code: 'DE', name: 'Germany'},
{code: 'CA', name: 'Canada'},
{code: 'BR', name: 'Brazil'},

Choose a reason for hiding this comment

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

Space inside { missing.
Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
Space inside } missing.

{code: 'CN', name: 'China'},
{code: 'RU', name: 'Russia'},
{code: 'DE', name: 'Germany'},
{code: 'CA', name: 'Canada'},

Choose a reason for hiding this comment

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

Space inside { missing.
Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
Space inside } missing.

Country.create([
{code: 'US', name: 'USA'},
{code: 'CN', name: 'China'},
{code: 'RU', name: 'Russia'},

Choose a reason for hiding this comment

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

Space inside { missing.
Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
Space inside } missing.


Country.create([
{code: 'US', name: 'USA'},
{code: 'CN', name: 'China'},

Choose a reason for hiding this comment

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

Space inside { missing.
Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
Space inside } missing.

Country.destroy_all

Country.create([
{code: 'US', name: 'USA'},

Choose a reason for hiding this comment

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

Use 2 spaces for indentation in an array, relative to the first position after the preceding left parenthesis.
Space inside { missing.
Prefer double-quoted strings unless you need single quotes to avoid extra backslashes for escaping.
Space inside } missing.

@@ -10,6 +10,7 @@ class CustomerDashboard < Administrate::BaseDashboard
orders: Field::HasMany.with_options(limit: 2),
updated_at: Field::DateTime,
kind: Field::Select.with_options(collection: Customer::KINDS),
country: Field::BelongsTo.with_options(primary_key: :code, foreign_key: :country_code),

Choose a reason for hiding this comment

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

Line is too long. [91/80]

:name,
]

ATTRIBUTE_TYPES = {

Choose a reason for hiding this comment

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

Freeze mutable objects assigned to constants.

@iarie iarie force-pushed the add-foreign-key-option branch 3 times, most recently from 030f1bf to d0a1010 Compare April 1, 2017 09:06
@@ -0,0 +1,4 @@
class Country < ActiveRecord::Base
validates :code, presence: true
validates :name, presence: true
Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe do:

validates :code, :name, presence: true

class CreateCountries < ActiveRecord::Migration
def change
create_table :countries do |t|
t.string :code
Copy link
Collaborator

Choose a reason for hiding this comment

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

t.string :code, null: false, index: { unique: true }

That way you don't need the add_index at the bottom. Also, since you're validating at the application level, these should be null: false as well.

Country.create(code: "CA", name: "Canada")
Country.create(code: "CN", name: "China")
Country.create(code: "RU", name: "Russia")
Country.create(code: "AU", name: "Australia")
Copy link
Collaborator

Choose a reason for hiding this comment

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

These could fail silently for a developer. Why not:

Country.create! [
  { code: "US", name: "Trump Temptations: The Billionaire & The Bellboy" },
  { code: "CA", name: "Canada" },
  { code: "CN", name: "China" },
  { code: "RU", name: "Russia" },
  { code: "AU", name: "Australia" }
]

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Ok, good point, but should we also add create-bang to other models in seeds.rb, i did create to preserve uniformity. Also i'll better stick with old definition of US 🤣


100.times do
name = "#{Faker::Name.first_name} #{Faker::Name.last_name}"
Customer.create(
name: name,
email: Faker::Internet.safe_email(name),
country: Country.first,
Copy link
Collaborator

Choose a reason for hiding this comment

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

This iterates 100 times. Maybe do:

country = Country.first
customers = Array.new(100) do
  {
    name: Faker::Name.name,
    email: Faker::Internet.safe_email(name),
    country: country
  }
end

Customer.create!(customers)

Array.new is faster than Integer#times; best practice to always use Array.new. Also, you don't have to call Customer.create 100 times, just once with an array for it to iterate over. We don't need this blazing fast. It's not inserting 2,000 records. However, we nearly made it do an extra 100 queries with Customer.first instead of just one. We could actually make it even simpler with taking the data returned from our Country.create! and sampling from there.

{ code: "CA", name: "Canada" },
{ code: "CN", name: "China" },
{ code: "RU", name: "Russia" },
{ code: "AU", name: "Australia" }

Choose a reason for hiding this comment

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

Put a comma after the last item of a multiline array.

@iarie iarie force-pushed the add-foreign-key-option branch 2 times, most recently from 1445eb6 to 7e2d03f Compare April 5, 2017 08:38
@iarie
Copy link
Contributor Author

iarie commented Apr 5, 2017

@BenMorganIO let me know if there is something we need to make/improve

Copy link
Contributor

@jcmorrow jcmorrow 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 not sure of the best way to solve this, but I think this is in the right direction.

foreign_key: "customers_uuid",
)
field = association.new(:customers, [], :show)
expect(field.permitted_attribute).to eq("customers_uuid")
Copy link
Contributor

Choose a reason for hiding this comment

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

Could we break up the phases of this test with newlines? (https://robots.thoughtbot.com/four-phase-test)


describe "foreign_key option" do
it "determines what foreign key is used on the relationship for the form" do
association =
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this line spacing is a bit awkward, what about

      association = Administrate::Field::BelongsTo.with_options(
        foreign_key: "customers_uuid",
      )

@@ -8,7 +8,7 @@ def self.permitted_attribute(attr)
end

def permitted_attribute
self.class.permitted_attribute(attribute)
foreign_key || self.class.permitted_attribute(attribute)
Copy link
Contributor

Choose a reason for hiding this comment

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

So, I found this whole permitted_attribute instance method calling a class method thing to be hard to follow and reason about. How would you feel about renaming class level implementations of permitted_attribute to like default_attribute or something like that? Or we could add an instance level default_attribute method and put that here instead of calling a class method as a default?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jcmorrow i ended up with

def foreign_key
  options.fetch(:foreign_key, :"#{attribute}_id")
end

on the instance level of BelongsTo, i found dangerous to rewrite permitted_attributes because it's used by BaseDashobard and Deffered classes.

@@ -24,6 +24,10 @@ def associated_class_name
def primary_key
options.fetch(:primary_key, :id)
end

def foreign_key
options.fetch(:foreign_key, nil)
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 not a huge fan of the way this reads in combination with line 25 above. Line 25 is handing you a default if not specified (which is what I would expect), but foreign_key is instead returning nil, and then allowing permitted_attribute to implement the default behavior for a foreign_key, but it's not clear from this line that that is what is going on. Does that make sense?

@iarie iarie force-pushed the add-foreign-key-option branch 4 times, most recently from fc45f8f to cd0f445 Compare April 21, 2017 10:55
@iarie iarie force-pushed the add-foreign-key-option branch 2 times, most recently from 469a6dd to e866000 Compare May 2, 2017 13:00
@iarie
Copy link
Contributor Author

iarie commented May 2, 2017

CircleCI fails with:

bundler: failed to load command: rake (/home/ubuntu/administrate/vendor/bundle/ruby/2.4.0/bin/rake)
Gem::LoadError: You have already activated bundler 1.13.7, but your Gemfile requires bundler 1.14.6. Prepending `bundle exec` to your command may solve this.

i don't understand why this happening...


# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"

create_table "countries", force: :cascade do |t|
t.string "code"
Copy link
Collaborator

Choose a reason for hiding this comment

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

Didn't you make this null: false in your migration?

@iarie iarie force-pushed the add-foreign-key-option branch from 25f3e3c to 8a3d8a8 Compare May 3, 2017 07:12
@iarie iarie force-pushed the add-foreign-key-option branch from 8a3d8a8 to 14f93e0 Compare May 10, 2017 09:27
@iarie iarie force-pushed the add-foreign-key-option branch from 14f93e0 to 769aa38 Compare May 30, 2017 15:43
@nickcharlton
Copy link
Member

Hi @iarie!

I think this is almost good to go. Do you think you'd be able to document this in the docs directory?

field = self.class::ATTRIBUTE_TYPES[key]

next key if association_classes.include?(field)
key if association_classes.include?(field.try :deferred_class)

Choose a reason for hiding this comment

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

Add parentheses to nested method call field.try :deferred_class.

@@ -5,6 +5,7 @@ class ApplicationController < ActionController::Base
def index
search_term = params[:search].to_s.strip
resources = Administrate::Search.new(resource_resolver, search_term).run
resources = resources.includes(*resource_includes) if resource_includes.any?

Choose a reason for hiding this comment

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

Line is too long. [82/80]

@@ -1,4 +1,7 @@
#{$all-buttons},
button,

Choose a reason for hiding this comment

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

Avoid qualifying attribute selectors with an element.

field = self.class::ATTRIBUTE_TYPES[key]

next key if association_classes.include?(field)
key if association_classes.include?(field.try :deferred_class)

Choose a reason for hiding this comment

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

Add parentheses to nested method call field.try :deferred_class.

@@ -5,6 +5,7 @@ class ApplicationController < ActionController::Base
def index
search_term = params[:search].to_s.strip
resources = Administrate::Search.new(resource_resolver, search_term).run
resources = resources.includes(*resource_includes) if resource_includes.any?

Choose a reason for hiding this comment

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

Line is too long. [82/80]

@@ -1,4 +1,7 @@
#{$all-buttons},
button,

Choose a reason for hiding this comment

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

Avoid qualifying attribute selectors with an element.

@iarie iarie force-pushed the add-foreign-key-option branch from 6f6c12c to e640768 Compare June 17, 2017 13:59
require "administrate/base_dashboard"

class CountryDashboard < Administrate::BaseDashboard
ATTRIBUTES = [

Choose a reason for hiding this comment

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

Use %i or %I for an array of symbols.

require "administrate/base_dashboard"

class CountryDashboard < Administrate::BaseDashboard
ATTRIBUTES = [

Choose a reason for hiding this comment

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

Use %i or %I for an array of symbols.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nickcharlton done! And should i fix that dashboard attributes definition according to Hound?

@iarie iarie force-pushed the add-foreign-key-option branch 2 times, most recently from 3a570eb to 9c8580b Compare June 20, 2017 10:02
@iarie iarie force-pushed the add-foreign-key-option branch from 9c8580b to d715683 Compare July 10, 2017 08:26
@iarie
Copy link
Contributor Author

iarie commented Jul 10, 2017

rebased against master

@iarie iarie force-pushed the add-foreign-key-option branch from d715683 to 432a59e Compare July 17, 2017 09:23
@nickcharlton
Copy link
Member

This looks great! Could I get you to fix up those Hound warnings? I'd done that on a recent one I'd merged in, so it'd be good to start doing that.

@iarie
Copy link
Contributor Author

iarie commented Jul 19, 2017

@nickcharlton fixing that syntax in example_app i thought should we also remake dashboard generators to rely on hound's syntax( add symbols %i array and freeze stuff)? Not here, in another PR? Not so important ofc :)

@nickcharlton
Copy link
Member

Mmm. As they're not related to this PR, we should fix them up in another.

@@ -0,0 +1,2 @@
---
BUNDLE_RETRY: "1"
Copy link
Member

Choose a reason for hiding this comment

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

I didn't notice this before; is this important for this PR or do you think it's better excluded?

ATTRIBUTES = %i(
code
name
).freeze
Copy link
Member

Choose a reason for hiding this comment

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

Could we put this all on one line?


100.times do
countries = Country.create! [
Copy link
Member

Choose a reason for hiding this comment

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

Should this be indented inside this block?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nickcharlton Sorry, i don't understand what do you mean.
Something wrong with brackets?
I can separate attributes declaration from .create! like this:

country_attributes = [
  { code: "US", name: "USA" },
  { code: "CA", name: "Canada" },
  { code: "CN", name: "China" },
  { code: "RU", name: "Russia" },
  { code: "AU", name: "Australia" },
]

countries = Country.create!(country_attributes)

Copy link
Member

Choose a reason for hiding this comment

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

Oh, oops! Apparently I didn't read the diff correctly.

@@ -0,0 +1 @@

Copy link
Member

Choose a reason for hiding this comment

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

Hmm, git still seems to think this file exists (but with just a blank line). Could you remove it completely?

@iarie iarie force-pushed the add-foreign-key-option branch from ada91e9 to 7c5964a Compare July 31, 2017 11:03
@nickcharlton
Copy link
Member

Excellent, thank you! Merging now.

@nickcharlton nickcharlton merged commit 736266d into thoughtbot:master Jul 31, 2017
@dan-ding
Copy link

dan-ding commented Aug 2, 2017

curious - why not use reflections to get the foreign key of a relation?

@pablobm
Copy link
Collaborator

pablobm commented Feb 7, 2020

@dan-ding - I think you are right, and we just didn't think of it at the time.

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.

7 participants