Skip to content

Commit

Permalink
Customize primary key name
Browse files Browse the repository at this point in the history
Closes #96
  • Loading branch information
paulcsmith committed Jul 2, 2019
1 parent 14c1819 commit a97c2b7
Show file tree
Hide file tree
Showing 16 changed files with 136 additions and 26 deletions.
4 changes: 2 additions & 2 deletions db/migrations/20171006163153_create_posts_and_comments.cr
@@ -1,13 +1,13 @@
class CreatePostsAndComments::V20171006163153 < Avram::Migrator::Migration::V1
def migrate
create :posts do
primary_key id : Int64
primary_key custom_id : Int64
add_timestamps
add title : String
end

create :comments do
primary_key id : Int64
primary_key custom_id : Int64
add_timestamps
add body : String
add post_id : Int64
Expand Down
4 changes: 2 additions & 2 deletions db/migrations/20180115194734_create_taggings.cr
@@ -1,13 +1,13 @@
class CreateTaggings::V20180115194734 < Avram::Migrator::Migration::V1
def migrate
create :tags do
primary_key id : Int64
primary_key custom_id : Int64
add_timestamps
add name : String
end

create :taggings do
primary_key id : Int64
primary_key custom_id : Int64
add_timestamps
add_belongs_to tag : Tag, on_delete: :cascade
add_belongs_to post : Post, on_delete: :cascade
Expand Down
14 changes: 14 additions & 0 deletions db/migrations/20180802180358_different_defaults.cr
@@ -0,0 +1,14 @@
class DifferentDefaults::V20180802180358 < Avram::Migrator::Migration::V1
def migrate
create :table_with_different_default_columns do
# Different name for primary key
# And no timestamps
primary_key custom_id : Int64
add name : String?
end
end

def rollback
drop :table_with_different_default_columns
end
end
19 changes: 19 additions & 0 deletions spec/model_with_different_default_columns_spec.cr
@@ -0,0 +1,19 @@
require "./spec_helper"

describe "when there are no timestamps and the primary key is not 'id'" do
it "find and delete work" do
model = ModelWithDifferentDefaultColumns::SaveOperation.create!(name: "Karen")
ModelWithDifferentDefaultColumns::BaseQuery.find(model.custom_id).should eq(model)

model.delete
ModelWithDifferentDefaultColumns::BaseQuery.new.select_count.should eq(0)
end

it "can create and update" do
model = ModelWithDifferentDefaultColumns::SaveOperation.create!(name: "Just Created")
model.name.should eq("Just Created")

updated_model = ModelWithDifferentDefaultColumns::SaveOperation.update!(model, name: "New Name")
updated_model.name.should eq("New Name")
end
end
14 changes: 7 additions & 7 deletions spec/query_spec.cr
Expand Up @@ -104,7 +104,7 @@ describe Avram::Query do
user = (user_query = UserQuery.new).first?
user.should_not be_nil
user.not_nil!.name.should eq "First"
user_query.query.statement.should eq "SELECT #{User::COLUMN_SQL} FROM users ORDER BY id ASC LIMIT 1"
user_query.query.statement.should eq "SELECT #{User::COLUMN_SQL} FROM users ORDER BY users.id ASC LIMIT 1"
end

it "returns nil if no record found" do
Expand Down Expand Up @@ -173,7 +173,7 @@ describe Avram::Query do
last = (user_query = UserQuery.new).last?
last.should_not be_nil
last && last.name.should eq "Last"
user_query.query.statement.should eq "SELECT #{User::COLUMN_SQL} FROM users ORDER BY id DESC LIMIT 1"
user_query.query.statement.should eq "SELECT #{User::COLUMN_SQL} FROM users ORDER BY users.id DESC LIMIT 1"
end

it "returns nil if last record is not found" do
Expand Down Expand Up @@ -478,7 +478,7 @@ describe Avram::Query do
comment = CommentBox.new.post_id(post.id).create

query = Comment::BaseQuery.new.join_posts
query.to_sql.should eq ["SELECT comments.id, comments.created_at, comments.updated_at, comments.body, comments.post_id FROM comments INNER JOIN posts ON comments.post_id = posts.id"]
query.to_sql.should eq ["SELECT comments.custom_id, comments.created_at, comments.updated_at, comments.body, comments.post_id FROM comments INNER JOIN posts ON comments.post_id = posts.custom_id"]

result = query.first
result.post.should eq post
Expand All @@ -489,7 +489,7 @@ describe Avram::Query do
comment = CommentBox.new.post_id(post.id).create

query = Post::BaseQuery.new.join_comments
query.to_sql.should eq ["SELECT posts.id, posts.created_at, posts.updated_at, posts.title, posts.published_at FROM posts INNER JOIN comments ON posts.id = comments.post_id"]
query.to_sql.should eq ["SELECT posts.custom_id, posts.created_at, posts.updated_at, posts.title, posts.published_at FROM posts INNER JOIN comments ON posts.custom_id = comments.post_id"]

result = query.first
result.comments.first.should eq comment
Expand All @@ -501,7 +501,7 @@ describe Avram::Query do
tagging = TaggingBox.new.post_id(post.id).tag_id(tag.id).create

query = Post::BaseQuery.new.join_tags
query.to_sql.should eq ["SELECT posts.id, posts.created_at, posts.updated_at, posts.title, posts.published_at FROM posts INNER JOIN taggings ON posts.id = taggings.post_id INNER JOIN tags ON taggings.tag_id = tags.id"]
query.to_sql.should eq ["SELECT posts.custom_id, posts.created_at, posts.updated_at, posts.title, posts.published_at FROM posts INNER JOIN taggings ON posts.custom_id = taggings.post_id INNER JOIN tags ON taggings.tag_id = tags.custom_id"]

result = query.first
result.tags.first.should eq tag
Expand All @@ -523,7 +523,7 @@ describe Avram::Query do
post = PostBox.create

query = Post::BaseQuery.new.left_join_comments
query.to_sql.should eq ["SELECT posts.id, posts.created_at, posts.updated_at, posts.title, posts.published_at FROM posts LEFT JOIN comments ON posts.id = comments.post_id"]
query.to_sql.should eq ["SELECT posts.custom_id, posts.created_at, posts.updated_at, posts.title, posts.published_at FROM posts LEFT JOIN comments ON posts.custom_id = comments.post_id"]

result = query.first
result.should eq post
Expand All @@ -533,7 +533,7 @@ describe Avram::Query do
post = PostBox.create

query = Post::BaseQuery.new.left_join_tags
query.to_sql.should eq ["SELECT posts.id, posts.created_at, posts.updated_at, posts.title, posts.published_at FROM posts LEFT JOIN taggings ON posts.id = taggings.post_id LEFT JOIN tags ON taggings.tag_id = tags.id"]
query.to_sql.should eq ["SELECT posts.custom_id, posts.created_at, posts.updated_at, posts.title, posts.published_at FROM posts LEFT JOIN taggings ON posts.custom_id = taggings.post_id LEFT JOIN tags ON taggings.tag_id = tags.custom_id"]

result = query.first
result.should eq post
Expand Down
4 changes: 4 additions & 0 deletions spec/support/comment.cr
@@ -1,7 +1,11 @@
require "./post"

class Comment < Avram::Model
skip_default_columns

table comments do
primary_key custom_id : Int64
timestamps
column body : String
belongs_to post : Post
end
Expand Down
10 changes: 10 additions & 0 deletions spec/support/model_with_different_default_columns.cr
@@ -0,0 +1,10 @@
# Different primary key name
# No timestamps
class ModelWithDifferentDefaultColumns < Avram::Model
skip_default_columns

table :table_with_different_default_columns do
primary_key custom_id : Int64
column name : String
end
end
5 changes: 5 additions & 0 deletions spec/support/post.cr
@@ -1,7 +1,12 @@
require "./comment"

class Post < Avram::Model
skip_default_columns

table posts do
primary_key custom_id : Int64
timestamps

column title : String
column published_at : Time?
has_many comments : Comment
Expand Down
4 changes: 4 additions & 0 deletions spec/support/tag.cr
@@ -1,5 +1,9 @@
class Tag < Avram::Model
skip_default_columns

table :tags do
primary_key custom_id : Int64
timestamps
column name : String
has_many taggings : Tagging
end
Expand Down
4 changes: 4 additions & 0 deletions spec/support/tagging.cr
@@ -1,5 +1,9 @@
class Tagging < Avram::Model
skip_default_columns

table :taggings do
primary_key custom_id : Int64
timestamps
belongs_to tag : Tag
belongs_to post : Post
end
Expand Down
32 changes: 29 additions & 3 deletions src/avram/base_query_template.cr
@@ -1,11 +1,23 @@
class Avram::BaseQueryTemplate
macro setup(type, columns, associations, table_name, *args, **named_args)
macro setup(type, columns, associations, table_name, primary_key_name, *args, **named_args)
class ::{{ type }}::BaseQuery < Avram::Query
include Avram::Queryable({{ type }})

@@table_name = :{{ table_name }}
@@schema_class = {{ type }}

# If not using default 'id' primary key
{% if primary_key_name.id != "id".id %}
# Then point 'id' to the primary key
def id(*args, **named_args)
{{ primary_key_name.id }}(*args, **named_args)
end
{% end %}

def primary_key_name
:{{ primary_key_name.id }}
end

macro generate_criteria_method(query_class, name, type)
def \{{ name }}
column_name = "#{@@table_name}.\{{ name }}"
Expand Down Expand Up @@ -33,14 +45,28 @@ class Avram::BaseQueryTemplate
{% for join_type in ["Inner", "Left"] %}
def {{ join_type.downcase.id }}_join_{{ assoc[:name] }}
{% if assoc[:relationship_type] == :belongs_to %}
join(Avram::Join::{{ join_type.id }}.new(@@table_name, :{{ assoc[:name] }}, {{ assoc[:foreign_key] }}, :id))
join(
Avram::Join::{{ join_type.id }}.new(
from: @@table_name,
to: :{{ assoc[:name] }},
primary_key: {{ assoc[:foreign_key] }},
foreign_key: primary_key_name
)
)
{% elsif assoc[:through] %}
{{ join_type.downcase.id }}_join_{{ assoc[:through].id }}
{{ assoc[:through].id }} do |join_query|
join_query.{{ join_type.downcase.id }}_join_{{ assoc[:name] }}
end
{% else %}
join(Avram::Join::{{ join_type.id }}.new(@@table_name, :{{ assoc[:name] }}, foreign_key: {{ assoc[:foreign_key] }}))
join(
Avram::Join::{{ join_type.id }}.new(
from: @@table_name,
to: :{{ assoc[:name] }},
foreign_key: {{ assoc[:foreign_key] }},
primary_key: primary_key_name
)
)
{% end %}
end
{% end %}
Expand Down
9 changes: 8 additions & 1 deletion src/avram/join.cr
Expand Up @@ -4,7 +4,14 @@ module Avram::Join
abstract class SqlClause
getter :from, :to, :from_column, :to_column

def initialize(@from : Symbol, @to : Symbol, @primary_key : Symbol? = nil, @foreign_key : Symbol? = nil, @comparison : String? = "=", @using : Array(Symbol) = [] of Symbol)
def initialize(
@from : Symbol,
@to : Symbol,
@primary_key : Symbol? = nil,
@foreign_key : Symbol? = nil,
@comparison : String? = "=",
@using : Array(Symbol) = [] of Symbol
)
end

abstract def join_type : String
Expand Down
20 changes: 15 additions & 5 deletions src/avram/model.cr
Expand Up @@ -44,7 +44,7 @@ class Avram::Model
end
end

def_equals @id
def_equals id

def to_param
id.to_s
Expand All @@ -65,16 +65,25 @@ class Avram::Model

macro primary_key(type_declaration)
PRIMARY_KEY_TYPE = {{ type_declaration.type }}
PRIMARY_KEY_NAME = {{ type_declaration.var }}
column {{ type_declaration.var }} : {{ type_declaration.type }}, autogenerated: true
alias PrimaryKeyType = {{ type_declaration.type }}

def self.primary_key_name : String
{{ type_declaration.var.stringify }}
def self.primary_key_name : Symbol
:{{ type_declaration.var.stringify }}
end

def primary_key_name : String
def primary_key_name : Symbol
self.class.primary_key_name
end

# If not using default 'id' primary key
{% if type_declaration.var.id != "id".id %}
# Then point 'id' to the primary key
def id
{{ type_declaration.var.id }}
end
{% end %}
end

macro default_columns
Expand All @@ -100,6 +109,7 @@ class Avram::Model
type: {{ @type }},
table_name: {{ table_name }},
primary_key_type: {{ PRIMARY_KEY_TYPE }},
primary_key_name: {{ PRIMARY_KEY_NAME }},
columns: {{ COLUMNS }},
associations: {{ ASSOCIATIONS }}
)
Expand All @@ -108,7 +118,7 @@ class Avram::Model

def delete
Avram::Repo.run do |db|
db.exec "DELETE FROM #{@@table_name} WHERE id = #{id}"
db.exec "DELETE FROM #{@@table_name} WHERE #{primary_key_name} = #{id}"
end
end

Expand Down
4 changes: 3 additions & 1 deletion src/avram/queryable.cr
@@ -1,6 +1,8 @@
module Avram::Queryable(T)
include Enumerable(T)

abstract def id

@query : Avram::QueryBuilder?
setter query

Expand Down Expand Up @@ -151,7 +153,7 @@ module Avram::Queryable(T)
if query.ordered?
query
else
query.order_by(:id, :asc)
id.asc_order.query
end
end

Expand Down
9 changes: 5 additions & 4 deletions src/avram/save_operation.cr
Expand Up @@ -43,6 +43,7 @@ abstract class Avram::SaveOperation(T)

abstract def table_name
abstract def attributes
abstract def primary_key_name

def log_failed_save
Avram.logger.warn({
Expand Down Expand Up @@ -340,8 +341,8 @@ abstract class Avram::SaveOperation(T)
def after_update(_record : T); end

private def insert : T
self.created_at.value ||= Time.utc
self.updated_at.value ||= Time.utc
self.created_at.value ||= Time.utc if responds_to?(:created_at)
self.updated_at.value ||= Time.utc if responds_to?(:created_at)
@record = Avram::Repo.run do |db|
db.query insert_sql.statement, insert_sql.args do |rs|
@record = @@schema_class.from_rs(rs).first
Expand All @@ -350,7 +351,7 @@ abstract class Avram::SaveOperation(T)
end

private def update(id) : T
self.updated_at.value = Time.utc
self.updated_at.value = Time.utc if responds_to?(:updated_at)
@record = Avram::Repo.run do |db|
db.query update_query(id).statement_for_update(changes), update_query(id).args_for_update(changes) do |rs|
@record = @@schema_class.from_rs(rs).first
Expand All @@ -362,7 +363,7 @@ abstract class Avram::SaveOperation(T)
Avram::QueryBuilder
.new(table_name)
.select(@@schema_class.column_names)
.where(Avram::Where::Equal.new(:id, id.to_s))
.where(Avram::Where::Equal.new(primary_key_name, id.to_s))
end

private def insert_sql
Expand Down
6 changes: 5 additions & 1 deletion src/avram/save_operation_template.cr
@@ -1,5 +1,5 @@
class Avram::SaveOperationTemplate
macro setup(type, columns, table_name, primary_key_type, *args, **named_args)
macro setup(type, columns, table_name, primary_key_type, primary_key_name, *args, **named_args)
class ::{{ type }}::BaseForm
macro inherited
\{% raise "BaseForm has been renamed to SaveOperation. Please inherit from #{type}::SaveOperation." %}
Expand All @@ -15,6 +15,10 @@ class Avram::SaveOperationTemplate
:{{ table_name }}
end

def primary_key_name
:{{ primary_key_name.id }}
end

add_column_attributes({{ primary_key_type }}, {{ columns }})
end
end
Expand Down

0 comments on commit a97c2b7

Please sign in to comment.