diff --git a/lib/polo/sql_translator.rb b/lib/polo/sql_translator.rb index 095d1a4..26e2718 100644 --- a/lib/polo/sql_translator.rb +++ b/lib/polo/sql_translator.rb @@ -48,46 +48,48 @@ def ignore_transform(inserts) end end + # Internal: Generates an insert SQL statement for a given record + # + # It will make use of the InsertManager class from the Arel gem to generate + # insert statements + # 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 + # Internal: Returns an object's attribute definitions along with + # their set values (for Rails 3.x). + # + 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) + # Internal: Returns an object's attribute definitions along with + # their set values (for Rails >= 4.x). + # + # From Rails 4.2 onwards, for some reason attributes with custom serializers + # wouldn't be properly serialized automatically. That's why explict + # 'type_cast' call are necessary. + # + 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 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 diff --git a/spec/polo_spec.rb b/spec/polo_spec.rb index 76f2e13..c7ce43e 100644 --- a/spec/polo_spec.rb +++ b/spec/polo_spec.rb @@ -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', 'nettofarah@gmail.com')" + insert = %q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'nettofarah@gmail.com')} 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 @@ -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]) @@ -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) @@ -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) @@ -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('nettofarah@gmail.com') @@ -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 @@ -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 diff --git a/spec/sql_translator_spec.rb b/spec/sql_translator_spec.rb index 6539fc3..f71a0e3 100644 --- a/spec/sql_translator_spec.rb +++ b/spec/sql_translator_spec.rb @@ -11,7 +11,7 @@ end it 'translates records to inserts' do - insert_netto = [%q{INSERT INTO `chefs` (`id`, `name`, `email`) VALUES (1, 'Netto', 'nettofarah@gmail.com')}] + insert_netto = [%q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'nettofarah@gmail.com')}] netto_to_sql = Polo::SqlTranslator.new(netto).to_sql expect(netto_to_sql).to eq(insert_netto) end @@ -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', 'nettofarah@gmail.com')}] + insert_netto = [%q{INSERT IGNORE INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'nettofarah@gmail.com')}] netto_to_sql = Polo::SqlTranslator.new(netto, Polo::Configuration.new(on_duplicate: :ignore)).to_sql expect(netto_to_sql).to eq(insert_netto) end @@ -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', 'nettofarah@gmail.com') ON DUPLICATE KEY UPDATE id = VALUES(id), name = VALUES(name), email = VALUES(email)} + %q{INSERT INTO "chefs" ("id", "name", "email") VALUES (1, 'Netto', 'nettofarah@gmail.com') 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