I'm migrating data between two activerecord connections, I've got my models all setup correctly so I can read from say Legacy::Tablename and Tablename and insert it into the new table.
The problem I have is my new model doesn't have all of the attributes that are in the legacy model so I get an 'unknown attribute' when I try to create a record in the new model via;
legacy_users = Legacy::User.all
legacy_users.each do |legacy_user|
User.create legacy_user.attributes
end
however if I try to remove the offending attribute it still doesn't work eg.
legacy_user.attributes.delete 'some_attribute'
Can anyone offer any pointers?
How about attributes.except(:some_attribute)?
This should work in that case:
legacy_users = Legacy::User.all
legacy_users.each do |legacy_user|
u = User.new
u.attributes.each do |k, v|
old_val = legacy_user.send(k) # Get the attr from old user
u.send("#{k}=", old_val) # Set it to the new user
end
end
You won't need to go through the mess of removing each unused attribute too
I'm working on a migration as well, and in my case I was passing a block to first_or_create to clone objects. I couldn't get delete() or except() to work, but for some reason this works:
scrubbed_obj = my_obj.attributes.reject { |k,v| k == 'the_attribute_you_dont_want' }
new_object.attributes = scrubbed_obj
and then the block saves fine. Just throwing dropping this answer here in case anyone else is experiencing similar issues.
Related
I am fetching data from CSV files and saving them to the database. Here's the simplified code structure (it's a rake task):
... loading CSV tools...
car = Car.new
car.tender_load_id = data.element[8]
car.brand = data.element[2]
car.color = 'green'
...
car.build_car_metadata(mbol: help_var[:mbol], ...)
car.first_registration = data.element[21]
car.skip_registration_code_validation = true # for not creating registration_code
if existing_car = Car.where("cars.tender_load_id ILIKE ?", "%#{car.tender_load_id}%").first
# car already exists => update
existing_car.update(car)
puts "Car exists, updating car with ID #{existing_car.id}."
else
car.save!
end
When I run this take task, I get the following error message:
NoMethodError: undefined method `reject' for #<Car:0x007fa3e2063a78>
and it points out to this line:
existing_car.update(car)
How do I make this work? I unfortunately cannot place the if-else part on the beginning of the rake task, the structure needs to be like this.
Thank you in advance.
You need to update the existing car with just the attributes, you can not pass in a full model.
Build the attributes, then create or update depending on whether a car exists already:
attributes = {}
attributes[:color] = ...
attributes[:brand] = ...
attributes[:tender_load_id] = ...
if (car = Car.where("cars.tender_load_id ILIKE ?", "%#{attributes[:tender_load_id]}%").first)
car.update(attributes)
else
Car.create!(attributes)
end
Note 1: It seems there can be multiple cars with the same tender_load_id. This means that the query for existing car will return an arbitrary record. Perhaps you want to add an order to that query.
Note 2: The way you query seems brittle. Isn't there a better ID in the CSV?
Note 3: attributes ending in _id are usually foreign keys. So perhaps find a better name for this attribute.
In rails 4.2.4, I can successfully execute this line of code.
Model.Create!(name: "Bob")
In Rails 4.2.5, this does nothing and leaves me with a null field in the database. To get past this, I must do something like this...
s = Model.Create!()
s.name = "Bob"
s.save!
Does anyone know why the first method doesn't work? Has rails been updated to something different? I haven't been able to find a solution for this. Thanks in advance.
Difference Between Model.create() and Model.new()
Model.create() It will create and save empty columns DB, if its not "validated".
Model.new() It will create empty table but not saves it in DB. Until will not be saved.
Example:
u = User.new
u.first_name = 'Joe'
u.last_name = 'Doe'
u.save #this saves DB.
Here is good Examples
Is there a way to convert a Rails model into an insert query?
For instance, if I have a model like:
m = Model.new
m.url = "url"
m.header = "header"
How can I get the corresponding SQL query ActiveRecord would generate if I did m.save?
I want to get: "INSERT INTO models(url, header) VALUES('url', 'header')" if possible.
Note: I don't want to actually save the model and get the query back (from log file, etc). I want to get the query IF I chose to save it.
On Rails 4.1, I found the below code snippet working:
record = Post.new(:title => 'Yay', :body => 'This is some insert SQL')
record.class.arel_table.create_insert
.tap { |im| im.insert(record.send(
:arel_attributes_with_values_for_create,
record.attribute_names)) }
.to_sql
Thanks to https://coderwall.com/p/obrxhq/how-to-generate-activerecord-insert-sql
Tested in Rails 3.2.13: I think I got it right this time, it definitely does not persist to the db this time. It also won't fire validations or callbacks so anything they change won't be in the results unless you've called them some other way.
Save this in lib as insert_sqlable.rb and you can then
#in your models or you can send it to ActiveRecord::Base
include InsertSqlable
Then it is model.insert_sql to see it.
#lib/insert_sqlable
module InsertSqlable
def insert_sql
values = arel_attributes_values
primary_key_value = nil
if self.class.primary_key && Hash === values
primary_key_value = values[values.keys.find { |k|
k.name == self.class.primary_key
}]
if !primary_key_value && connection.prefetch_primary_key?(self.class.table_name)
primary_key_value = connection.next_sequence_value(self.class.sequence_name)
values[self.class.arel_table[self.class.primary_key]] = primary_key_value
end
end
im = self.class.arel_table.create_insert
im.into self.class.arel_table
conn = self.class.connection
substitutes = values.sort_by { |arel_attr,_| arel_attr.name }
binds = substitutes.map do |arel_attr, value|
[self.class.columns_hash[arel_attr.name], value]
end
substitutes.each_with_index do |tuple, i|
tuple[1] = conn.substitute_at(binds[i][0], i)
end
if values.empty? # empty insert
im.values = Arel.sql(self.class.connectionconnection.empty_insert_statement_value)
else
im.insert substitutes
end
conn.to_sql(im,binds)
end
end
It turns out the code is in ActiveRecord::Relation and not ActiveRecord::Persistence. The only significant change is the last line which generates the sql instead of performing it.
If you dont want to save the model you call m.destroy when you are done with the object.
You can log the sql query by debugging it like this
Rails.logger.debug "INSERT INTO models(url, header) VALUES(#{m.url}, #{m.header}).inspect
After search a lot over the Internet and forums, I think I found a better solution for your problem: just requires two line of code.
I found a good gem that do exactly what you want, but this gem only works for Rails 3.2 and older. I talked with author and he doesn't want support this gem anymore. So I discovered by myself how to support Rails 4.0 and now I'm maintaining this gem.
Download the "models-to-sql-rails" gem here, supporting Rails 4.0 and older.
With this gem, you can easily do the following. (the examples inside values are just a joke, you will get the correct values when using it in your object).
For objects:
object.to_sql_insert
# INSERT INTO modelName (field1, field2) VALUES ('Wow, amaze gem', 'much doge')
For array of objets:
array_of_objects.to_sql_insert
# INSERT INTO modelName (field1, field2) VALUES ('Awesome doge', "im fucking cop")
# INSERT INTO modelName (field1, field2) VALUES ('much profit', 'much doge')
# (...)
Just see the Github of this project and you'll find how to install and use this wonderful gem.
I have an array with model attributes (these model attributes are columns in DB table). I am trying to iterate through this array and automatically create a record which I would like to save to DB table, something like this:
columns.each_with_index do |c, i|
user.c = data[i]
puts user.c
end
user is model.
But if I try the snippet above, I get
undefined method `c=' for #<User:0x007f8164d1bb80>
I've tried also
columns.each_with_index do |c, i|
user."#{c}" = data[i]
puts user."#{c}"
end
But this doesn't work as well.
Data in columns array are taken from form that sends user, so I want to save only data that he send me, but I still cannot figure it out...
I would like to ask you for help... thank you in advance!
user.send("#{c}=".to_sym, data[i])
Also, you can access the attributes as a hash.
user.attributes[c] = data[i]
The best thing would probably be to build a hash and to use update_attributes:
mydata = {}
columns.each_with_index{|c, i| mydata[c] = data[i]}
user.update_attributes(mydata)
this way you retain the protections provided by attr_accessible.
If this is actually in a controller, you can just make use of some basic rails conventions and build the User record like this:
#user = User.new(params[:user])
if #user.save
# do something
else
# render the form again
end
Although you can set the values using send, I agree with #DaveS that you probably want to protect yourself via attr_accessibles. If your planning to use Rails 4, here's a good overview.
In my rails3.1 application, I'm trying to apply the following logic in one of my order model.
def digital?
line_items.map { |line_item| return false unless line_item.variant_id = '102586070' }
end
I've created a separate variant called prepaid_voucher which has id = 102586070. Despite this, the result is false...
Order has many line_items
LineItem belongs to order and variant
Variant has many line_items
Is this the best way to perform such a task and how can I fix?
First of all I think you want a double == here line_item.variant_id = '102586070', then I rather go for something like that (If I understand what you want)
def digital?
line_items.select{|line_item| line_item.variant_id == '102586070'}.any?
end
But it's hard to understand what you really want, what is the expected behavior if the id is not found?