undefined method `updated_at=' for ActiveRecord - ruby-on-rails

I am trying to check if a record is stale and update if it is. Here's my code:
#listing = Listing.where(listing_id: listing['listing_id'])
if #listing.exists?
if #listing.created_at < 7.days.ago # line where error shows
#listing.update(state: listing['state'])
end
end
And I'm getting the following error:
undefined method `updated_at' for #<Listing::ActiveRecord_Relation:0x00007fb3ebabbac0> Did you mean? update_all
Here's my DB record:
<Listing id: 5, listing_id: 188996174, state: "active", created_at: "2018-03-13 20:43:35", updated_at: "2018-03-13 20:46:48">
And my migration:
class CreateListings < ActiveRecord::Migration[5.1]
def change
create_table :listings do |t|
t.integer :listing_id
t.string :state
t.timestamps
end
end
end
Any ideas?
Thanks in advance!

using where, you get an ActiveRecord_Relation :
#listing = Listing.where(listing_id: listing['listing_id'])
You want an single object here.
I am unsure about exactly how you've set things, but assuming the listing_id column is unique, the following should do the job :
#listing = Listing.find_by(listing_id: listing['listing_id'])
the the rest of your code is okay. As it would only return the first object matching the listing-id. So then you could try to call updated_at on this object
If they're not unique, you might want to do the following :
#listings = Listing.where(listing_id: params['listing_id'])
#listings.each do |listing|
listing.update(state: params['state']) if listing.created_at < 7.days.ago
end
Or shorter :
Listing
.where(
'listing_id = ? AND updated_at > ?',
params['listing_id'],
Time.zone.now - 7.days
)
.update_all(state: params['state'])

Related

Rails Model Callback Not Saving Instance Method Results

I'm struggling to make my model callback functions behave properly :(
Using Rails 5.2.4.1, Ruby 2.6.3 and pg ~> 0.21
I have a model "Batch" that I want to have automatically calculate and update its own "Value" attribute once its "Price" and "Quantity" values are greater than zero.
def change
create_table :batches do |t|
t.references :product, foreign_key: true, null: false
t.references :currency, foreign_key: true
t.string :batch_number, null: false
t.string :status, null: false, default: "pending"
t.integer :quantity, null: false, default: 0
t.integer :price, null: false, default: 0
t.integer :value, null: false, default: 0
t.timestamps
end
end
end
In my seed file I create some Batch instances with specified Quantity and then Price, and leave the value to default to 0 (this is to be added later when creating an Order instance):
batch1 = Batch.new(
product_id: Product.last.id,
batch_number: "0001-0001-0001",
quantity: 1800
)
if batch1.valid?
batch1.save
p batch1
else
p first_batch1.errors.messages
end
batch1.price = 3
batch1.save
Then my troubles begin...
I've tried a few approaches similar to the below:
after_find :calculate_value
def calculate_value
self.value = price * quantity if value != price * quantity
end
I'm not sure if I'm missing something very obvious here, but the value never seems to update.
I've tried adding save into the method but it doesn't seem to work either. I'm finding some of the other behaviours with saving in these callbacks very strange.
For example, I assign a Currency to a Batch through a join table with this instance method:
after_find :assign_currency
def assign_currency
self.currency_id = currency.id unless currency.nil?
# save
end
If I uncomment that "save" (or make it "self.save") then the seed file creates the Batches but then fails to create the join table, returning {:batch=>["must exist"]}. Yet in the console, the batch does:
[#<Batch:0x00007fb874ad0aa0
id: 1,
product_id: 1,
batch_number: "0001-0001-0001",
status: "pending",
quantity: 1800,
currency_id: nil,
price: 0,
value: 0,
created_at: Thu, 09 Jan 2020 00:38:42 UTC +00:00,
updated_at: Thu, 09 Jan 2020 00:38:42 UTC +00:00>,
I'm still new to rails so would be very grateful for any advice or suggestions whatsoever! This feels like it should be simple and it's driving me crazy...
I'd recommend using a before_validation callback instead of an after_find. The reason I'd recommend that is because in an after_find, the value column will be populated only when the object is loaded using finder (.find, .find_or_create), and hence, the you would not be able to validate the value column before saving. In fact, during initial save, the value column will be empty.
The order of execution of callbacks is as follows:
before_validation
after_validation
before_save
around_save
before_create
around_create
after_create
after_save
after_commit/after_rollback
So, in your case, this could work:
before_validation :calculate_value, if: :price_or_quantity_changed?
validates :value, presence: true # This can be added because the before_validation callback will ensure that value is present
def calculate_value
self.value = price * quantity if value != price * quantity
end
private
def price_or_quantity_changed?
self.price_changed? || self.quantity_changed?
end
_changed? methods are from the ActiveModel::Dirty module which helps us keep track of values that have changed in a record.
However, if you would want to use after_find, I think this StackOverflow answer may help you understand it more.
You could use after_find after defining it as a method as follows:
def after_find
<your code to set value>
end
after_initialize & after_find callbacks order in Active Record object life cycle?
how about this in your model?
after_find :calculate_value
def calculate_value
self.value = self.price * self.quantity if self.value != self.price * self.quantity
end

How to add an extra value into a Rails param

I'm trying to solve a problem with getting all the values saved to my database. Here's how my application is setup is
before_filter :load_make, only: :create
def create
#record = #make.message.new(my_params)
#record.save
end
def load_make
make_id = params[:message] ? params[:message][:make_id] : params[:make_id]
#make = Car.find_by(custom_make: make_id)
#make ||= Car.find_by(id: make_id).tap
end
def my_params
params.require(:message).permit(:message_type, :message_date, :make_id)
end
My problem is that I want to create another method that does a GET request from a different API that returns a created_at date and also save it on create, similar to:
def lookup_car
Car.car_source.find(id: my_params[:make_id]).created_at
# returns a datetime value
end
I'm looking for advice on what's the best way to put this into the my_params method :message_date and save it along with everything else on the create action?
Schema for model:
create_table "messages", force: :cascade do |t|
t.integer "make_id"
t.string "message", limit: 255
t.datetime "created_at"
t.datetime "updated_at"
t.string "message_type", limit: 255
t.datetime "message_date"
end
Firstly, you should NOT really change / update / insert to the created_at since Rails does that for you. If anything, I suggest you adding another column.
Secondly, you should NOT do and create / update on a Get request.
Other than that, adding another field to your params is easy as long as you have that column ready. Not sure how your models are structure, but here you can do something along the line like this below. Let's say you have message_date column in your whatever model, you can do this:
def my_params
message_date_param = { message_date: Time.now }
params.require(:message).permit(:message_type, :message_date, :make_id).merge(message_date)
end
I wrote this pretty late at night when I probably wasn't making much sense, but essentially what I was wanting was to update another value at same time save was called on the #record. I solved this by using attributes, which updates but doesn't call save (since I was doing that already below).
For example:
def create
car = Car.car_source.find(id: my_params[:make_id]).created_at
#record.attributes = { message_date: car }
#record.save
end
Also much thanks to all the other comments. I've completely refactored this code to be more rails idiomatic.

How to modifying a method of a class before posting it to the database rails

I have three fields a start_time:time , end_time:time and total_min:decimal fields. The user inputs the start_time and the end_time which are used to calculate the total minutes spent before saving it to the database (sq-lite). How do i calculate that total_min before posting it ?
class CreateInternets < ActiveRecord::Migration
def change
create_table :internets do |t|
t.time :start_time
t.time :end_time
t.decimal :total_min
t.timestamps
end
end
end
Use before_save callback by ActiveRecord.
before_save :update_total_min
def update_total_min
total_min = end_time - start_time
end
http://guides.rubyonrails.org/active_record_callbacks.html#callbacks-overview
You can use arithmetic with Time's. You can add this line to your Internet#create action:
#internet.total_min = params[:start_time] - params[:end_time] / 60
Or using strong_params:
#internet.total_min = internet_params[:start_time] - internet_params[:end_time] / 60
Make sure to whitelist the attributes in your controller:
private
def internet_params
params.require(:internet).permit(:start_time, :end_time)
end

ruby/rails how to ignore a comma in when ordering

I have a price field for a product in a catalog. Sometimes the admin user is putting a comma when dealing with thousands (ex: $10,000) and sometimes he is just doing $6000. While I would like to simply tell him to do it one way or the other, I would also like to solve the issue programmatically.
The #show action responsible is here:
def show
#category = Category.find_by_url_name(params[:category_id])
#brand = Brand.find(params[:id])
#search = Product.find(:all, :conditions => ['brand_id = ? and category_id = ?', #brand.id, #category.id],
:order=> params[:order] || 'price DESC')
#products = #search.paginate(:page => params[:page], :per_page => 12 )
#meta_title = "#{#brand.name}"
respond_to do |format|
format.html # show.html.erb
format.xml { render :xml => #brand }
end
end
I also have a sort_options helper in my application helper that is providing the ordering options to the site user:
def product_sort_options
options_for_select([
['', nil],
['Newest to Oldest', 'descend_by_date'],
['Oldest to Newest', 'ascend_by_date'],
['Price: Highest to Lowest', 'descend_by_price'],
['Price: Lowest to Highest', 'ascend_by_price'],
['Name', 'ascend_by_name']
])
end
any ideas?
To make it a full answer - price should not be a string. The fact that you have 300 products now is not a big deal.
Make a migration:
rails generate migration decimalise
Then edit it (db/migrate/*decimalise.rb), and write something like this:
class Decimalise < ActiveRecord::Migration
def up
connection = ActiveRecord::Base.connection()
# kill the weird chars in the string field
connection.execute("UPDATE products SET price = REPLACE(REPLACE(price, ',', ''), '$', '')")
# convert the string column into a decimal one
change_table :products do |t|
# adjust for your use case - this gives you values up to 9999999.99
# if you need more, increase the 10
t.column :price, :decimal, :precision => 10, :scale => 2
end
end
def down
change_table :products do |t|
t.column :price, :string, :limit => 10
end
end
end
then finally, run
rake db:migrate
(untested, you will probably need to tweak. also, back up your DB before any tinkering - I'll not be responsible for any data loss you suffer)
EDIT One thing I forgot: how to print it out.
<%= number_to_currency #product.price %>
should give you something like $1,999.99 for a price of 1999.99.
You can use String.gsub to search the commas and replace them by nothing.

Globalize2 and migrations

I have used globalize2 to add i18n to an old site. There is already a lot of content in spanish, however it isn't stored in globalize2 tables. Is there a way to convert this content to globalize2 with a migration in rails?
The problem is I can't access the stored content:
>> Panel.first
=> #<Panel id: 1, name: "RT", description: "asd", proje....
>> Panel.first.name
=> nil
>> I18n.locale = nil
=> nil
>> Panel.first.name
=> nil
Any ideas?
I'm sure you solved this one way or another but here goes. You should be able to use the read_attribute method to dig out what you're looking for.
I just used the following to migrate content from the main table into a globalize2 translations table.
Add the appropriate translates line to your model.
Place the following in config/initializers/globalize2_data_migration.rb:
require 'globalize'
module Globalize
module ActiveRecord
module Migration
def move_data_to_translation_table
klass = self.class_name.constantize
return unless klass.count > 0
translated_attribute_columns = klass.first.translated_attributes.keys
klass.all.each do |p|
attribs = {}
translated_attribute_columns.each { |c| attribs[c] = p.read_attribute(c) }
p.update_attributes(attribs)
end
end
def move_data_to_model_table
# Find all of the translated attributes for all records in the model.
klass = self.class_name.constantize
return unless klass.count > 0
all_translated_attributes = klass.all.collect{|m| m.attributes}
all_translated_attributes.each do |translated_record|
# Create a hash containing the translated column names and their values.
translated_attribute_names.inject(fields_to_update={}) do |f, name|
f.update({name.to_sym => translated_record[name.to_s]})
end
# Now, update the actual model's record with the hash.
klass.update_all(fields_to_update, {:id => translated_record['id']})
end
end
end
end
end
Created a migration with the following:
class TranslateAndMigratePages < ActiveRecord::Migration
def self.up
Page.create_translation_table!({
:title => :string,
:custom_title => :string,
:meta_keywords => :string,
:meta_description => :text,
:browser_title => :string
})
say_with_time('Migrating Page data to translation tables') do
Page.move_data_to_translation_table
end
end
def self.down
say_with_time('Moving Page translated values into main table') do
Page.move_data_to_model_table
end
Page.drop_translation_table!
end
end
Borrows heavily from Globalize 3 and refinerycms.

Resources