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

Use ActiveRecord methods to generate INSERT SQLs #26

Merged
merged 2 commits into from
Dec 7, 2015
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 17 additions & 30 deletions lib/polo/sql_translator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -49,45 +49,32 @@ def ignore_transform(inserts)
end

def raw_sql(record)
connection = ActiveRecord::Base.connection
attributes = record.attributes

keys = attributes.keys.map do |key|
"`#{key}`"
end

values = attributes.map do |key, value|
column = record.column_for_attribute(key)
connection.quote(cast_attribute(record, column, value))
end

"INSERT INTO `#{record.class.table_name}` (#{keys.join(', ')}) VALUES (#{values.join(', ')})"
record.class.arel_table.create_insert.tap do |insert_manager|
insert_manager.insert(insert_values(record))
end.to_sql
end

module ActiveRecordLessThanFourPointTwo
def cast_attribute(record, column, value)
attribute = record.send(:type_cast_attribute_for_write, column, value)

if record.class.serialized_attributes.include?(column.name)
attribute.serialize
else
attribute
end
module ActiveRecordLessThanFour
def insert_values(record)
record.send(:arel_attributes_values)
end
end

module ActiveRecordFourPointTwoOrGreater
def cast_attribute(record, column, value)
column.type_cast_for_database(value)
module ActiveRecordFourOrGreater
def insert_values(record)
connection = ActiveRecord::Base.connection
values = record.send(:arel_attributes_with_values_for_create, record.attribute_names)
values.each do |attribute, value|
column = record.column_for_attribute(attribute.name)
values[attribute] = connection.type_cast(value, column)
end
Copy link
Contributor Author

Choose a reason for hiding this comment

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

I wish this block could be avoided and have that done automatically, but I couldn't figure out how.

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 it looks alright. It's ok since this code is extracted into its own method.
It might be worth writing some docs for this method/module now that things are getting a little more complicated.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@nettofarah Done! Let me know if it looks alright.

Copy link
Contributor

Choose a reason for hiding this comment

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

This is fantastic.

end
end

if ActiveRecord::VERSION::STRING.start_with?('3.2') ||
ActiveRecord::VERSION::STRING.start_with?('4.0') ||
ActiveRecord::VERSION::STRING.start_with?('4.1')
include ActiveRecordLessThanFourPointTwo
if ActiveRecord::VERSION::MAJOR < 4
include ActiveRecordLessThanFour
else
include ActiveRecordFourPointTwoOrGreater
include ActiveRecordFourOrGreater
end
end
end
30 changes: 15 additions & 15 deletions spec/polo_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,13 @@

it 'generates an insert query for the base object' do
exp = Polo.explore(AR::Chef, 1)
insert = "INSERT INTO `chefs` (`id`, `name`, `email`) VALUES (1, 'Netto', '[email protected]')"
insert = %q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', '[email protected]')}
expect(exp).to include(insert)
end

it 'generates an insert query for the objects with non-standard primary keys' do
exp = Polo.explore(AR::Person, 1)
insert = "INSERT INTO `people` (`ssn`, `name`) VALUES (1, 'John Doe')"
insert = %q{INSERT INTO "people" ("ssn", "name") VALUES (1, 'John Doe')}
expect(exp).to include(insert)
end

Expand All @@ -25,8 +25,8 @@
serialized_nil = "'null'"
end

turkey_insert = "INSERT INTO `recipes` (`id`, `title`, `num_steps`, `chef_id`, `metadata`) VALUES (1, 'Turkey Sandwich', NULL, 1, #{serialized_nil})"
cheese_burger_insert = "INSERT INTO `recipes` (`id`, `title`, `num_steps`, `chef_id`, `metadata`) VALUES (2, 'Cheese Burger', NULL, 1, #{serialized_nil})"
turkey_insert = %Q{INSERT INTO "recipes" ("id", "title", "num_steps", "chef_id", "metadata") VALUES (1, 'Turkey Sandwich', NULL, 1, #{serialized_nil})}
cheese_burger_insert = %Q{INSERT INTO "recipes" ("id", "title", "num_steps", "chef_id", "metadata") VALUES (2, 'Cheese Burger', NULL, 1, #{serialized_nil})}

inserts = Polo.explore(AR::Chef, 1, [:recipes])

Expand All @@ -35,10 +35,10 @@
end

it 'generates queries for nested dependencies' do
patty = "INSERT INTO `ingredients` (`id`, `name`, `quantity`) VALUES (3, 'Patty', '1')"
turkey = "INSERT INTO `ingredients` (`id`, `name`, `quantity`) VALUES (1, 'Turkey', 'a lot')"
one_cheese = "INSERT INTO `ingredients` (`id`, `name`, `quantity`) VALUES (2, 'Cheese', '1 slice')"
two_cheeses = "INSERT INTO `ingredients` (`id`, `name`, `quantity`) VALUES (4, 'Cheese', '2 slices')"
patty = %q{INSERT INTO "ingredients" ("id", "name", "quantity") VALUES (3, 'Patty', '1')}
turkey = %q{INSERT INTO "ingredients" ("id", "name", "quantity") VALUES (1, 'Turkey', 'a lot')}
one_cheese = %q{INSERT INTO "ingredients" ("id", "name", "quantity") VALUES (2, 'Cheese', '1 slice')}
two_cheeses = %q{INSERT INTO "ingredients" ("id", "name", "quantity") VALUES (4, 'Cheese', '2 slices')}

inserts = Polo.explore(AR::Chef, 1, :recipes => :ingredients)

Expand All @@ -50,10 +50,10 @@

it 'generates inserts for many to many relationships' do
many_to_many_inserts = [
"INSERT INTO `recipes_ingredients` (`id`, `recipe_id`, `ingredient_id`) VALUES (1, 1, 1)",
"INSERT INTO `recipes_ingredients` (`id`, `recipe_id`, `ingredient_id`) VALUES (2, 1, 2)",
"INSERT INTO `recipes_ingredients` (`id`, `recipe_id`, `ingredient_id`) VALUES (3, 2, 3)",
"INSERT INTO `recipes_ingredients` (`id`, `recipe_id`, `ingredient_id`) VALUES (4, 2, 4)",
%q{INSERT INTO "recipes_ingredients" ("id", "recipe_id", "ingredient_id") VALUES (1, 1, 1)},
%q{INSERT INTO "recipes_ingredients" ("id", "recipe_id", "ingredient_id") VALUES (2, 1, 2)},
%q{INSERT INTO "recipes_ingredients" ("id", "recipe_id", "ingredient_id") VALUES (3, 2, 3)},
%q{INSERT INTO "recipes_ingredients" ("id", "recipe_id", "ingredient_id") VALUES (4, 2, 4)},
]

inserts = Polo.explore(AR::Chef, 1, :recipes => :ingredients)
Expand All @@ -72,7 +72,7 @@
end

exp = Polo.explore(AR::Chef, 1)
insert = /INSERT INTO `chefs` \(`id`, `name`, `email`\) VALUES \(1, 'Netto', (.+)\)/
insert = /INSERT INTO "chefs" \("id", "name", "email"\) VALUES \(1, 'Netto', (.+)\)/
scrambled_email = insert.match(exp.first)[1]

expect(scrambled_email).to_not eq('[email protected]')
Expand All @@ -86,7 +86,7 @@

inserts = Polo.explore(AR::Chef, 1)

expect(inserts).to eq [ %q{INSERT INTO `chefs` (`id`, `name`, `email`) VALUES (1, 'Netto', 'changeme')} ]
expect(inserts).to eq [ %q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'changeme')} ]
end
end

Expand All @@ -97,7 +97,7 @@
end

exp = Polo.explore(AR::Chef, 1)
insert = /INSERT IGNORE INTO `chefs` \(`id`, `name`, `email`\) VALUES \(1, 'Netto', (.+)\)/
insert = /INSERT IGNORE INTO "chefs" \("id", "name", "email"\) VALUES \(1, 'Netto', (.+)\)/
expect(insert).to match(exp.first)
end
end
Expand Down
6 changes: 3 additions & 3 deletions spec/sql_translator_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
end

it 'translates records to inserts' do
insert_netto = [%q{INSERT INTO `chefs` (`id`, `name`, `email`) VALUES (1, 'Netto', '[email protected]')}]
insert_netto = [%q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', '[email protected]')}]
netto_to_sql = Polo::SqlTranslator.new(netto).to_sql
expect(netto_to_sql).to eq(insert_netto)
end
Expand All @@ -25,7 +25,7 @@
describe "options" do
describe "on_duplicate: :ignore" do
it 'uses INSERT IGNORE as opposed to regular inserts' do
insert_netto = [%q{INSERT IGNORE INTO `chefs` (`id`, `name`, `email`) VALUES (1, 'Netto', '[email protected]')}]
insert_netto = [%q{INSERT IGNORE INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', '[email protected]')}]
netto_to_sql = Polo::SqlTranslator.new(netto, Polo::Configuration.new(on_duplicate: :ignore)).to_sql
expect(netto_to_sql).to eq(insert_netto)
end
Expand All @@ -34,7 +34,7 @@
describe "on_duplicate: :override" do
it 'appends ON DUPLICATE KEY UPDATE to the statement' do
insert_netto = [
%q{INSERT INTO `chefs` (`id`, `name`, `email`) VALUES (1, 'Netto', '[email protected]') ON DUPLICATE KEY UPDATE id = VALUES(id), name = VALUES(name), email = VALUES(email)}
%q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', '[email protected]') ON DUPLICATE KEY UPDATE id = VALUES(id), name = VALUES(name), email = VALUES(email)}
]

netto_to_sql = Polo::SqlTranslator.new(netto, Polo::Configuration.new(on_duplicate: :override)).to_sql
Expand Down