I am running the following code on a model containing website urls. It should strip off the 'www.' from every url and save the record back to the db. However, there are records where the changed url simply will not be saved. I have tested the .sub routine via the console and it does make the changes to the string, however, the change isn't saved.
def strip
b = Sites.all
b.each do |t|
t.url.sub!(/www./, '')
t.save
end
end
I also ran the above code directly in the rails console and the output was as follows (again, nothing was saved):
(0.0ms) commit transaction
(0.0ms) begin transaction
(0.0ms) commit transaction
(0.0ms) begin transaction
(0.0ms) commit transaction
(0.0ms) begin transaction
(0.0ms) commit transaction
(0.0ms) begin transaction
(0.0ms) commit transaction
Don't change activerecord model attributes in place - you'll confuse the change tracking and activerecord will think you've made no changes.
Instead do
t.url = t.url.sub(/www\./,'')
You should also escape the . in the URL or you'd match any character after the www.
Try changing:
t.url.sub!(/www./, '')
to
t.url= t.url.sub(/www./, '')
Activerecord tracks changes and only fields that, it thinks, are changed are part of the update clause. Directly changing t.url using sub! is not marking the field as changed (maybe a bug in Activerecord).
Another suggestion change your pattern to /www\./, otherwise . matches with any character.
Don't iterate over every row just to update the column. I'd suggest a better alternative:
def strip
Site.update_all('url = REPLACE(url, "www.", "")')
end
I'd also change the ambiguous naming from strip to clean_urls!.
Related
I followed the settings described in active_model_serializers guide to couple a Rails 5 API with Ember 3 app.
In Rails ShopLanguagesController, I modified shop_language_params method as described in the above guide:
private
def shop_language_params
#params.require(:shop_language).permit(:shop_id, :language_id, :modified_by)
ActiveModelSerializers::Deserialization.jsonapi_parse!(params, only: [:shop_id, :language_id, :modified_by] )
end
Here is what I get when I post the following data from Ember app (seen in Rails log console):
Started POST "/shops/613/languages" for 127.0.0.1 at 2018-04-09 16:53:05 +0200
Processing by ShopLanguagesController#create as JSONAPI
Parameters: {"data"=>{"attributes"=>{"modified_by"=>"Z28SCAMB"}, "relationships"=>{"shop"=>{"data"=>{"type"=>"shops", "id"=>"613"}}, "language"=>{"data"=>{"type"=>"languages", "id"=>"374"}}}, "type"=>"shop-languages"}, "shop_id"=>"613"}
User Load (0.4ms) SELECT "users".* FROM "users" WHERE "users"."username" = $1 LIMIT $2 [["username", "Z28SCAMB"], ["LIMIT", 1]]
Shop Load (0.4ms) SELECT "shops".* FROM "shops" WHERE "shops"."id" = $1 LIMIT $2 [["id", 613], ["LIMIT", 1]]
++++ params: {:modified_by=>"Z28SCAMB"}
The posted data seems to be correct.
How to extract the needed parameters (for example, language_id) from this JSON ?
Parsing JSON in Rails is quite straightforward:
parsed_json = ActiveSupport::JSON.decode(your_json_string)
Let's suppose, the object you want to associate the shortUrl with is a Site object, which has two attributes - short_url and long_url. Than, to get the shortUrl and associate it with the appropriate Site object, you can do something like:
parsed_json["results"].each do |longUrl, convertedUrl|
site = Site.find_by_long_url(longUrl)
site.short_url = convertedUrl["shortUrl"]
site.save
end
The solution that worked for me was to modify the private shop_language_params method in ShopLanguagesController as follows:
def shop_language_params
ActiveModelSerializers::Deserialization.jsonapi_parse!(params, only: [:shop, :language, :modified_by] )
end
As you see, I permit not foreign keys values for shop and language (shop_id and language_id) but the objects themselves: :shop and :language. Now you have shop_id and language_id values available in params hash as usually.
And, of course, you should call shop_language_params everywhere in the controller where you need do pass the shop_language params.
Example where I find a language in the same controller:
private
def find_language
#language = Language.find_by!(id: shop_language_params[:language_id])
end
The gist of it all
This method call:
user.update_attribute('image_url', 'arch.png')
Generates this SQL statement:
UPDATE "users" SET "image_url" = ?, [["image_url", "rock.jpg"]]
That SQL statement is not what the desired outcome is.
The desired outcome is this SQL statement:
UPDATE "users" SET "image_url" = ?, [["image_url", "arch.png"]]
Notice that the SQL statement generated has the wrong argument for image_url.
How is that possible though?
How can I fix it?
Background
I use the form_for tag to generate a form where users can change their profile picture. The form sends a post request to the controller which calls another method, passing along the parameters. The final method calls update_attribute on the record, saving the changes.
I'm using the gem "carrierwave" for the images.
Debugging
I've done some debugging and it looks like everything goes well, until the last method calls update_attribute. That call generates a SQL statement which is incorrect.
For reference purposes, the old picture's file name is "rock.jpg", and the new one is "arch.png".
The gist of it is that these two lines are executed:
puts "\n\nSending #{attribute} and #{value} to update_attribute\n\n"
if user.update_attribute(attribute, value)
Which result in:
Sending image_url and arch.png to update_attribute
UPDATE "users" SET "image_url" = ?, WHERE "users"."id" = ? [["image_url", "rock.jpg"], [...]]
I took out the updated_at part of the generated SQL statement, since it isn't important. The full SQL statement as well as more output from the helper can be seen just below.
How is the image_url in the SQL statement different from the value passed to it?
Detailed output from log
Received update request!!!!!
---------------------------
Attribute - image_url
Value - arch.png
--------------------------
Sending image_url and arch.png to update_attribute
(0.1ms) begin transaction
CACHE (0.0ms) SELECT "users".* FROM "users" WHERE "users"."id" = ? LIMIT 1 [["id", 186]]
SQL (0.2ms) UPDATE "users" SET "image_url" = ?, "updated_at" = ? WHERE "users"."id" = ? [["image_url", "rock.jpg"], ["updated_at", "2016-04-27 01:25:49.476024"], ["id", 186]]
(90.4ms) commit transaction
Code
Form:
<%= form_for(#current_member, html: {id: 'image_form'}) do %>
<%= file_field_tag :image_url,
type: 'file', html: {id: 'file_field'} %>
<%= hidden_field_tag 'updateParam', 'image_url' %>
<%= submit_tag %>
<% end %>
Controller:
def update
update_account(#current_member, params[:updateParam], params)
end
Helper:
def update_account(user, attribute, parameters)
puts "\n\n\n\n\n"
puts "Received update request!!!!!\n
puts "---------------------------\n"
puts "Attribute - #{attribute}\n"
puts "Value - #{parameters[attribute]}\n"
puts "--------------------------\n\n\n"
if editable attribute
if valid(attribute, parameters)
value = parameters[attribute]
puts "\n\nSending #{attribute} and #{value} to update_attribute\n\n"
if user.update_attribute(attribute, value)
# ...
# ...
The gist of it all
This method call:
user.update_attribute('image_url', 'arch.png')
Generates this SQL statement:
UPDATE "users" SET "image_url" = ?, [["image_url", "rock.jpg"]]
That SQL statement is not what the desired outcome is.
The desired outcome is this SQL statement:
UPDATE "users" SET "image_url" = ?, [["image_url", "arch.png"]]
Notice that the SQL statement generated has the wrong argument for image_url.
How is that possible?
How can I fix it?
Extending comments to answer:
It looks, there is some callback in your model which is changing the value before save/update.
To skip callbacks, you can use update_column instead of update_attribute.
Note: update_column also skips validations and does not update the updated_at attribute.
Edit from author
This answer was right, a callback changed the result to something that could be saved in the database.
Using update_column I was able to see that my parameters were passed in correctly, but were not correct. The carrierwave gem uses a callback which checks to see if the parameter is valid, if not, it reverts the value to what it was previous to the call. If the value is valid (an UploadedFile), then it saves the file in the save_dir and changes the value to be a url to point to the file.
I used this to debug further and find that the value I passed to update_attribute wasn't valid because the form_for didn't include multipart: true, which is necessary for forms using carrierwave to upload images. Adding this solved the problem.
I had to go back to using update_attribute instead of update_column so that carrierwave's callback would still be executed and the uploaded file would be saved in the database.
There is a lot of clutter in your question, this makes it hard to read and help.
The goal of such a bug hunt is to reduce the elements involved to have as few things to worry about as possible.
Bypass Rails
Run the statement in a SQL console. Just to be sure that you don't have any fancy triggers and functions in place. From the output you showed, this should not be the case. But... It costs you 1min. and gives you some security.
Bypass View/Controller
Run the code on the Rails console. I tend to write everything on one line, with a reload! statement in front. So changes in the code can be checked fast:
reload!; user = User.find(12345); user.update_attribute('image_url', 'arch.png'); User.find(12345).image_url
What does it print?
I assume that your problem stems from callbacks. So it should print the wrong value for image_url already. So you can be sure that the problem is not caused by View/Controller layer.
Bypass View
If everything is fine, then you can be sure that the problem is somewhere in View/Controller. In this case i'd not use params to build the update statement but use hardcoded values. Like this you bypass the View layer.
Bypass callbacks
Use update_column instead of update_attribute
Callbacks
First: don't use callbacks. Second: don't use them. And if you use them be damn sure you know what you do:-)
If update_column solves the problem, then it probably is because of a callback.
Search your code for the attribute name and callback names to see if there are any.
In reasons of speed increase I decide to use raw SQL. So, with ActiveRecord I had 17714 ms for fill DB, now I have 1730 ms. Good, isn't it? But here is one problem, texts are different from each other and some of them don't want to go to my DB. By the way, with same groups of posts I hadn't problems when work with them through AR.
insert_arr = []
walls.each do |wall|
wall['items'].each do |post|
if post['text'].empty?
post['text'] = 'репост'
else
post['text'] = post['text'].slice(0, 100) + '...'
end
insert_arr.push "('#{#group[:id]}', '#{post['id']}', '#{post['date']}', '#{post['text']}')"
end
end
sql = "INSERT INTO posts (`group_id`, `post_id`, `post_date`, `post_text`) VALUES #{insert_arr.join(', ')}"
ActiveRecord::Base.connection.execute(sql)
I've tried to slice the text, it's not help. But if I remove text at it works just fine! post['text'] = '' So, the problem is here. Please, help me. What should I do with text before insert it?
Instead of above try to use the regular ActiveRecord way, but enclose all the inserts bulk with transaction:
ActiveRecord::Base.transaction do
# put your inserts here
end
it will avoid SQL commits after each INSERT saving lots of time.
In my User model
def activities
Activity.where actor_type: self.class.name
end
When I call current_user.activities.page(params[:page]) in controller , I want to know it will load all activity records or not ?
ActiveRecord is lazy-loading - it will only call the SQL query when you actually access the data in the result set.
It won't load Activity data until you do something like .all or .each or something else that actually requires access to the data.
No, kaminari will not load all records at once. You can test it in the rails console:
current_user.activities.page(params[:page]).to_sql # => "SELECT \"activities\".* FROM \"activities\" WHERE \"activities\".\"user_id\" = 1 LIMIT 25 OFFSET 25"
As you can see, LIMIT and OFFSET are both involved into the SQL query. And of course, it's eager loading.
Rails is not returning the updated version of a record.
I have two methods in a model, submit_job(sig, label, jobtype) for submitting a job to a db that will get processed on the backend, and then poll_result(id) which will poll that submitted job every second to see when it completes, and then return the results from the completed job to the user.
My issue is that the poll_result(id) method is never getting the updated record.
def self.poll_result(id)
change = false
Workbench.where("id = ?", id).each do |sig|
if sig.resultsready.to_i == 1
change = true
end
end
return change
end
All this does is comeback with the results from my original insert over and over, as I can see when I have it print out the results of the record it is accessing. I am looking directly at the database and can see that it is calling the right ID, and that the record has been updated. resultsready is set to 1 in the database, the loop should end and it should return back, but it just gets stuck in an infinite loop.
My assumption is that it is somehow getting an old/stale record that is being cached somehow, but I cannot for the life of me figure out how to force it to get the new record.
Thank You,
-Dennis
Using the Workbench.connection.clear_query_cache fixed the issue! To be specific, I added it at the controller level, right before calling Workbench.poll_result(id)