Lets say I have a model (an ActiveRecord class):
class Sample < ActiveRecord::Base
attr_accessor :x1
end
I know that
Sample.last.x1 == 1 #true
If I set Sample.last.x1 = 3 then Sample.last.x1_was == 1 #true.
But when I set the value of x1 again: Sample.last.x1 = 8 then Sample.last.x1_was == 3 #false, but Sample.last.x1 == 1 #true
I can guess why it happens (Sample.last wasn't saved since the change), but I want to find a way to retrieve the former value (not the db value) of x1. Can you suggest a way to do it?
I can't think of a reason to do that, but if you really need to, you could override the setter to store the various changes as you go.
def x1=( value )
#previous_x1_value = x1
super
end
def previous_x1_value
#previous_x1_value || x1_was
end
IT's all built in to rails. See the documentation for [ActiveRecord#dirty][1]
person.name = 'Bob'
person.changed? # => true
person.name_changed? # => true
person.name_was # => 'uncle bob'
person.name_change # => ['uncle bob', 'Bob']
person.name = 'Bill'
person.name_change # => ['uncle bob', 'Bill']
Related
In Rails, i have an object called values that could be 1 of 20 kinds of ActiveRecord, and in only 1 of them there's a method(may be the wrong term, rails newbie) that can add a customized field in returned JSON object where the method name is the field name and method returned value is the field value. For example
class XXXController < ApplicationController
..
if a
values = A
elsif b
values = B
elseif c
values = C
..
end
render :json => values.to_json(:methods => :type_needed)
and you will see response like
{
..
"type_needed": true,
..
}
I only have type_needed defined in A which will return true in some cases. For others like B, C, D... which in total 19, i want them to all have type_needed returned as false, is there a way i can do that in one place instead of add type_needed method in the rest 19?
I will do it as follows:
json = values.to_json(:methods => :type_needed)
# => "[{\"id\":1,\"name\":\"Aaa\"},{\"id\":\"2\",\"name\":\"Bbb\"}]" # => Representational value only
ary = JSON.parse(json)
# => [{"id"=>1, "name"=>"Aaa"}, {"id"=>2, "name"=>"Bbb"}]
ary.map! { |hash| hash[:type_needed] = false unless hash.key?(:type_needed); hash }
# => [{"id"=>1, "name"=>"Aaa", :type_needed=>false}, {"id"=>2, "name"=>"Bbb", :type_needed=>false}]
ary.to_json
# => "[{\"id\":1,\"name\":\"Aaa\",\"type_needed\":false},{\"id\":\"2\",\"name\":\"Bbb\",\"type_needed\":false}]"
If I am understanding your question correctly then you want to define type_needed method once and have it included on all your 20 models. If yes, then you can define a concern and include it in all your 20 models.
app/models/concerns/my_model_concern.rb
module MyModelConcern
extend ActiveSupport::Concern
def type_needed?
self.respond_to?(:some_method)
end
end
app/models/a.rb
class A < ApplicationRecord
include MyModelConcern
def some_method
end
end
app/models/b.rb
class B < ApplicationRecord
include MyModelConcern
end
app/models/c.rb
class C < ApplicationRecord
include MyModelConcern
end
With the above
a = A.new
a.type_needed?
=> true
b = B.new
b.type_needed?
=> false
c = C.new
c.type_needed?
=> false
See if this helps.
I have this variable opinions I want to store as an instance variable in my model... am I right in assuming I will need to add a column for it or else be re-calculating it constantly?
My other question is what is the syntax to store into a column variable instead of just a local one?
Thanks for the help, code below:
# == Schema Information
#
# Table name: simulations
#
# id :integer not null, primary key
# x_size :integer
# y_size :integer
# verdict :string
# arrangement :string
# user_id :integer
#
class Simulation < ActiveRecord::Base
belongs_to :user
serialize :arrangement, Array
validates :user_id, presence: true
validates :x_size, :y_size, presence: true, :numericality => {:only_integer => true}
validates_numericality_of :x_size, :y_size, :greater_than => 0
def self.keys
[:soft, :hard, :none]
end
def generate_arrangement
#opinions = Hash[ Simulation.keys.map { |key| [key, 0] } ]
#arrangement = Array.new(y_size) { Array.new(x_size) }
#arrangement.each_with_index do |row, y_index|
row.each_with_index do |current, x_index|
rand_opinion = Simulation.keys[rand(0..2)]
#arrangement[y_index][x_index] = rand_opinion
#opinions[rand_opinion] += 1
end
end
end
def verdict
if #opinions[:hard] > #opinions[:soft]
:hard
elsif #opinions[:soft] > #opinions[:hard]
:soft
else
:push
end
end
def state
#arrangement
end
def next
new_arrangement = Array.new(#arrangement.size) { |array| array = Array.new(#arrangement.first.size) }
#opinions = Hash[ Simulation.keys.map { |key| [key, 0] } ]
#seating_arrangement.each_with_index do |array, y_index|
array.each_with_index do |opinion, x_index|
new_arrangement[y_index][x_index] = update_opinion_for x_index, y_index
#opinions[new_arrangement[y_index][x_index]] += 1
end
end
#arrangement = new_arrangement
end
private
def in_array_range?(x, y)
((x >= 0) and (y >= 0) and (x < #arrangement[0].size) and (y < #arrangement.size))
end
def update_opinion_for(x, y)
local_opinions = Hash[ Simulation.keys.map { |key| [key, 0] } ]
for y_pos in (y-1)..(y+1)
for x_pos in (x-1)..(x+1)
if in_array_range? x_pos, y_pos and not(x == x_pos and y == y_pos)
local_opinions[#arrangement[y_pos][x_pos]] += 1
end
end
end
opinion = #arrangement[y][x]
opinionated_neighbours_count = local_opinions[:hard] + local_opinions[:soft]
if (opinion != :none) and (opinionated_neighbours_count < 2 or opinionated_neighbours_count > 3)
opinion = :none
elsif opinion == :none and opinionated_neighbours_count == 3
if local_opinions[:hard] > local_opinions[:soft]
opinion = :hard
elsif local_opinions[:soft] > local_opinions[:hard]
opinion = :soft
end
end
opinion
end
end
ActiveRecord analyzes the database tables and creates setter and getter methods with metaprogramming.
So you would create a database column with a migration:
rails g migration AddOpinionToSimulation opinion:hash
Note that not all databases support storing a hash or a similar key/value data type in a column. Postgres does. If you need to use another database such MySQL you should consider using a relation instead (storing the data in another table).
Then when you access simulation.opinion it will automatically get the database column value (if the record is persisted).
Since ActiveRecord creates a setter and getter you can access your property from within the Model as:
class Simulation < ActiveRecord::Base
# ...
def an_example_method
self.opinions # getter method
# since self is the implied receiver you can simply do
opinions
opinions = {foo: "bar"} # setter method.
end
end
The same applies when using the plain ruby attr_accessor, attr_reader and attr_writer macros.
When you assign to an attribute backed by a database column ActiveRecord marks the attribute as dirty and will include it when you save the record.
ActiveRecord has a few methods to directly update attributes: update, update_attributes and update_attribute. There are differences in the call signature and how they handle callbacks.
you can add a method like
def opinions
#opinions ||= Hash[ Simulation.keys.map { |key| [key, 0] }
end
this will cache the operation into the variable #opinions
i would also add a method like
def arrangement
#arrangement ||= Array.new(y_size) { Array.new(x_size) }
end
def rand_opinion
Simulation.keys[rand(0..2)]
end
and then replace the variables with your methods
def generate_arrangement
arrangement.each_with_index do |row, y_index|
row.each_with_index do |current, x_index|
arrangement[y_index][x_index] = rand_opinion
opinions[rand_opinion] += 1
end
end
end
now your opinions and your arrangement will be cached and the code looks better. you didn't have to add a new column in you table
you now hat to replace the #opinions variable with your opinions method
I've been trying to figure out how to write this ruby code more eloquently. Does someone have a better solution?
a[:new] = {} if a[:new].nil?
a[:new].merge!( { new_key => new_value } )
is there a way to write this in a more elegant way? I come across this a lot when dealing with nested hashes that need to check whether an key exist and if not, create it.
Write it as below taking the help of Hash#to_h and NilClass#to_h
a[:new] = a[:new].to_h.merge( { new_key => new_value } )
Example :
hsh1[:a] # => nil
hsh1[:a] = hsh1[:a].to_h.merge({1=>2})
hsh1[:a] # => {1=>2}
hsh2 = {:a => {'k' => 2}}
hsh2[:a] # => {"k"=>2}
hsh2[:a] = hsh2[:a].to_h.merge({1=>2})
hsh2 # => {:a=>{"k"=>2, 1=>2}}
Do this at the beginning:
a = Hash.new{|h, k| h[k] = {}}
then, without caring whether a has a key :new or not, do
a[:new].merge!(new_key => new_value)
or
a[:new][new_key] = new_value
In our Rails 3.2.13 app (Ruby 2.0.0 + Postgres on Heroku), we are often retreiving a large amount of Order data from an API, and then we need to update or create each order in our database, as well as the associations. A single order creates/updates itself plus approx. 10-15 associcated objects, and we are importing up to 500 orders at a time.
The below code works, but the problem is it's not at all efficient in terms of speed. Creating/updating 500 records takes approx. 1 minute and generates 6500+ db queries!
def add_details(shop, shopify_orders)
shopify_orders.each do |shopify_order|
order = Order.where(:order_id => shopify_order.id.to_s, :shop_id => shop.id).first_or_create
order.update_details(order,shopify_order,shop) #This calls update_attributes for the Order
ShippingLine.add_details(order, shopify_order.shipping_lines)
LineItem.add_details(order, shopify_order.line_items)
Taxline.add_details(order, shopify_order.tax_lines)
Fulfillment.add_details(order, shopify_order.fulfillments)
Note.add_details(order, shopify_order.note_attributes)
Discount.add_details(order, shopify_order.discount_codes)
billing_address = shopify_order.billing_address rescue nil
if !billing_address.blank?
BillingAddress.add_details(order, billing_address)
end
shipping_address = shopify_order.shipping_address rescue nil
if !shipping_address.blank?
ShippingAddress.add_details(order, shipping_address)
end
payment_details = shopify_order.payment_details rescue nil
if !payment_details.blank?
PaymentDetail.add_details(order, payment_details)
end
end
end
def update_details(order,shopify_order,shop)
order.update_attributes(
:order_name => shopify_order.name,
:order_created_at => shopify_order.created_at,
:order_updated_at => shopify_order.updated_at,
:status => Order.get_status(shopify_order),
:payment_status => shopify_order.financial_status,
:fulfillment_status => Order.get_fulfillment_status(shopify_order),
:payment_method => shopify_order.processing_method,
:gateway => shopify_order.gateway,
:currency => shopify_order.currency,
:subtotal_price => shopify_order.subtotal_price,
:subtotal_tax => shopify_order.total_tax,
:total_discounts => shopify_order.total_discounts,
:total_line_items_price => shopify_order.total_line_items_price,
:total_price => shopify_order.total_price,
:total_tax => shopify_order.total_tax,
:total_weight => shopify_order.total_weight,
:taxes_included => shopify_order.taxes_included,
:shop_id => shop.id,
:email => shopify_order.email,
:order_note => shopify_order.note
)
end
So as you can see, we are looping through each order, finding out if it exists or not (then either loading the existing Order or creating the new Order), and then calling update_attributes to pass in the details for the Order. After that we create or update each of the associations. Each associated model looks very similar to this:
class << self
def add_details(order, tax_lines)
tax_lines.each do |shopify_tax_line|
taxline = Taxline.find_or_create_by_order_id(:order_id => order.id)
taxline.update_details(shopify_tax_line)
end
end
end
def update_details(tax_line)
self.update_attributes(:price => tax_line.price, :rate => tax_line.rate, :title => tax_line.title)
end
I've looked into the activerecord-import gem but unfortunately it seems to be more geared towards creation of records in bulk and not update as we also require.
What is the best way that this can be improved for performance?
Many many thanks in advance.
UPDATE:
I came up with this slight improvement, which essentialy removes the call to update the newly created Orders (one query less per order).
def add_details(shop, shopify_orders)
shopify_orders.each do |shopify_order|
values = {:order_id => shopify_order.id.to_s, :shop_id => shop.id,
:order_name => shopify_order.name,
:order_created_at => shopify_order.created_at,
:order_updated_at => shopify_order.updated_at,
:status => Order.get_status(shopify_order),
:payment_status => shopify_order.financial_status,
:fulfillment_status => Order.get_fulfillment_status(shopify_order),
:payment_method => shopify_order.processing_method,
:gateway => shopify_order.gateway,
:currency => shopify_order.currency,
:subtotal_price => shopify_order.subtotal_price,
:subtotal_tax => shopify_order.total_tax,
:total_discounts => shopify_order.total_discounts,
:total_line_items_price => shopify_order.total_line_items_price,
:total_price => shopify_order.total_price,
:total_tax => shopify_order.total_tax,
:total_weight => shopify_order.total_weight,
:taxes_included => shopify_order.taxes_included,
:email => shopify_order.email,
:order_note => shopify_order.note}
get_order = Order.where(:order_id => shopify_order.id.to_s, :shop_id => shop.id)
if get_order.blank?
order = Order.create(values)
else
order = get_order.first
order.update_attributes(values)
end
ShippingLine.add_details(order, shopify_order.shipping_lines)
LineItem.add_details(order, shopify_order.line_items)
Taxline.add_details(order, shopify_order.tax_lines)
Fulfillment.add_details(order, shopify_order.fulfillments)
Note.add_details(order, shopify_order.note_attributes)
Discount.add_details(order, shopify_order.discount_codes)
billing_address = shopify_order.billing_address rescue nil
if !billing_address.blank?
BillingAddress.add_details(order, billing_address)
end
shipping_address = shopify_order.shipping_address rescue nil
if !shipping_address.blank?
ShippingAddress.add_details(order, shipping_address)
end
payment_details = shopify_order.payment_details rescue nil
if !payment_details.blank?
PaymentDetail.add_details(order, payment_details)
end
end
end
and for the associated objects:
class << self
def add_details(order, tax_lines)
tax_lines.each do |shopify_tax_line|
values = {:order_id => order.id,
:price => tax_line.price,
:rate => tax_line.rate,
:title => tax_line.title}
get_taxline = Taxline.where(:order_id => order.id)
if get_taxline.blank?
taxline = Taxline.create(values)
else
taxline = get_taxline.first
taxline.update_attributes(values)
end
end
end
end
Any better suggestions?
Try wrapping your entire code into a single database transaction. Since you're on Heroku it'll be a Postgres bottom-end. With that many update statements, you can probably benefit greatly by transacting them all at once, so your code executes quicker and basically just leaves a "queue" of 6500 statements to run on Postgres side as the server is able to dequeue them. Depending on the bottom end, you might have to transact into smaller chunks - but even transacting 100 at a time (and then close and re-open the transaction) would greatly improve throughput into Pg.
http://api.rubyonrails.org/classes/ActiveRecord/Transactions/ClassMethods.html
http://www.postgresql.org/docs/9.2/static/sql-set-transaction.html
So before line 2 you'd add something like:
def add_details(shop, shopify_orders)
Order.transaction do
shopify_orders.each do |shopify_order|
And then at the very end of your method add another end:
if !payment_details.blank?
PaymentDetail.add_details(order, payment_details)
end
end //shopify_orders.each..
end //Order.transaction..
end //method
You can monkey-patch ActiveRecord like this:
class ActiveRecord::Base
#http://stackoverflow.com/questions/15317837/bulk-insert-records-into-active-record-table?lq=1
#https://gist.github.com/jackrg/76ade1724bd816292e4e
# "UPDATE THIS SET <list_of_column_assignments> FROM <table_name> THIS JOIN (VALUES (<csv1>, <csv2>,...) VALS ( <column_names> ) ON <list_of_primary_keys_comparison>"
def self.bulk_update(record_list)
pk = self.primary_key
raise "primary_key not found" unless pk.present?
raise "record_list not an Array of Hashes" unless record_list.is_a?(Array) && record_list.all? {|rec| rec.is_a? Hash }
return nil if record_list.empty?
result = nil
#test if every hash has primary keys, so we can JOIN
record_list.each { |r| raise "Primary Keys '#{self.primary_key.to_s}' not found on record: #{r}" unless hasAllPKs?(r) }
#list of primary keys comparison
pk_comparison_array = []
if (pk).is_a?(Array)
pk.each {|thiskey| pk_comparison_array << "THIS.#{thiskey} = VALS.#{thiskey}" }
else
pk_comparison_array << "THIS.#{pk} = VALS.#{pk}"
end
pk_comparison = pk_comparison_array.join(' AND ')
#SQL
(1..record_list.count).step(1000).each do |start|
key_list, value_list = convert_record_list(record_list[start-1..start+999])
#csv values
csv_vals = value_list.map {|v| "(#{v.join(", ")})" }.join(", ")
#column names
column_names = key_list.join(", ")
#list of columns assignments
columns_assign_array = []
key_list.each {|col|
unless inPK?(col)
columns_assign_array << "THIS.#{col} = VALS.#{col}"
end }
columns_assign = columns_assign_array.join(', ')
sql = "UPDATE THIS SET #{columns_assign} FROM #{self.table_name} THIS JOIN ( VALUES #{csv_vals} ) VALS ( #{column_names} ) ON ( #{pk_comparison} )"
result = self.connection.execute(sql)
return result if result<0
end
return result
end
def self.inPK?(str)
pk = self.primary_key
test = str.to_s
if pk.is_a?(Array)
(pk.include?(test))
else
(pk==test)
end
end
#test if given hash has primary keys included as hash keys and those keys are not empty
def self.hasAllPKs?(hash)
h = hash.stringify_keys
pk = self.primary_key
if pk.is_a?(Array)
(pk.all? {|k| h.key?(k) and h[k].present? })
else
h.key?(pk) and h[pk].present?
end
end
def self.convert_record_list(record_list)
# Build the list of keys
key_list = record_list.map(&:keys).flatten.map(&:to_s).uniq.sort
value_list = record_list.map do |rec|
list = []
key_list.each {|key| list << ActiveRecord::Base.connection.quote(rec[key] || rec[key.to_sym]) }
list
end
# If table has standard timestamps and they're not in the record list then add them to the record list
time = ActiveRecord::Base.connection.quote(Time.now)
for field_name in %w(created_at updated_at)
if self.column_names.include?(field_name) && !(key_list.include?(field_name))
key_list << field_name
value_list.each {|rec| rec << time }
end
end
return [key_list, value_list]
end
end
Then, you can generate a array of hashes containing your models attributes (including theirs primary keys) and do something like:
ActiveRecord::Base.transaction do
Model.bulk_update [ {attr1: val1, attr2: val2,...}, {attr1: val1, attr2: val2,...}, ... ]
end
It will be a single SQL command without Rails callbacks and validations.
For PostgreSQL, there are several issues that the above approach does not address:
You must specify an actual table, not just an alias, in the update target table.
You cannot repeat the target table in the FROM phrase. Since you are joining the target table to a VALUES table (hence there is only one table in the FROM phrase, you won't be able to use JOIN, you must instead use "WHERE ".
You don't get the same "free" casts in a VALUES table that you do in a simple "UPDATE" command, so you must cast date/timestamp values as such (#val_cast does this).
class ActiveRecord::Base
def self.update!(record_list)
raise ArgumentError "record_list not an Array of Hashes" unless record_list.is_a?(Array) && record_list.all? {|rec| rec.is_a? Hash }
return record_list if record_list.empty?
(1..record_list.count).step(1000).each do |start|
field_list, value_list = convert_record_list(record_list[start-1..start+999])
key_field = self.primary_key
non_key_fields = field_list - [%Q["#{self.primary_key}"], %Q["created_at"]]
columns_assign = non_key_fields.map {|field| "#{field} = #{val_cast(field)}"}.join(",")
value_table = value_list.map {|row| "(#{row.join(", ")})" }.join(", ")
sql = "UPDATE #{table_name} AS this SET #{columns_assign} FROM (VALUES #{value_table}) vals (#{field_list.join(", ")}) WHERE this.#{key_field} = vals.#{key_field}"
self.connection.update_sql(sql)
end
return record_list
end
def self.val_cast(field)
field = field.gsub('"', '')
if (column = columns.find{|c| c.name == field }).sql_type =~ /time|date/
"cast (vals.#{field} as #{column.sql_type})"
else
"vals.#{field}"
end
end
def self.convert_record_list(record_list)
# Build the list of fields
field_list = record_list.map(&:keys).flatten.map(&:to_s).uniq.sort
value_list = record_list.map do |rec|
list = []
field_list.each {|field| list << ActiveRecord::Base.connection.quote(rec[field] || rec[field.to_sym]) }
list
end
# If table has standard timestamps and they're not in the record list then add them to the record list
time = ActiveRecord::Base.connection.quote(Time.now)
for field_name in %w(created_at updated_at)
if self.column_names.include?(field_name) && !(field_list.include?(field_name))
field_list << field_name
value_list.each {|rec| rec << time }
end
end
field_list.map! {|field| %Q["#{field}"] }
return [field_list, value_list]
end
end
I want to give one of my models an attribute accessor that defaults to an array of eight zeros. This is the first syntax I tried:
attr_accessor_with_default:weekly_magnitude_list, [0,0,0,0,0,0,0,0]
The above didn't do what I expected because all instances of the model end up sharing the same Array object. The blog (http://barelyenough.org/blog/2007/09/things-to-be-suspicious-of-attr_accessor_with_default-with-a-collection/) that clued me into that suggested a different syntax, basically wrapping the default value in a block.
attr_accessor_with_default(:weekly_magnitude_list) {[0,0,0,0,0,0,0,0]}
That doesn't work (for me, in Rails 3). Any time I call the accessor, I seem to be getting a completely new Array object. That effectively means I can't write to it.
Does anybody know the correct way to do this?
For your pleasure, I've included the output of a simple test demonstrating this:
class Container
attr_accessor_with_default :naive_collection, [0,0]
attr_accessor_with_default(:block_collection) {[0,0]}
end
> c = Container.new
=> #<Container:0x7f3610f717a8>
> c.naive_collection[0] = "foo"
=> "foo"
> Container.new.naive_collection
=> ["foo", 0]
# expected [0,0]
> c.block_collection[0] = "foo"
=> "foo"
> c.block_collection
=> [0, 0]
# expected ["foo", 0]
I just stumbled onto this question while running into the same problem.
For reference, the docs specify the block form is dynamically evaluated in the instance scope. To continue the example, its usefulness is really quite limited, but at least works the way you might expect:
class Container
attr_accessor_with_default(:block_collection) { name.underscore }
end
> c = Container.new(:name => "TestName")
> c.block_collection # => "test_name"
> c.block_collection = "something else" # => "something else"
> c.name => "TestName"
Here's the really quirky part though...
class Container
attr_accessor_with_default :naive_collection, [0, 0]
end
# This works as expected
> c = Container.new
> c.naive_collection = ["foo", "bar"] # => ["foo", "bar"]
> Container.new.naive_collection # => [0, 0]
> c.naive_collection[0] = 0 # => [0, "bar"]
> Container.new.naive_collection # => [0, 0]
# But this doesn't
> c2 = Container.new
> c2.naive_collection # => [0, 0]
> c2.naive_collection[0] = "problem!" # => ["problem!", 0]
> Container.new.naive_collection # => ["problem!", 0]
Digging into the source a bit, I see that attr_accessor_with_default defines an instance method that returns the default value or executes the block. Which is fine.
It then goes on to execute this within a module_eval:
def #{sym}=(value)
class << self;
attr_reader :#{sym}
end
##{sym} = value
end
Which is just ridiculously convoluted. I may end up ticketing this as a bug if I can't figure out the motivation for this behavior.
Update:
I managed to figure out what was going wrong here.
def attr_accessor_with_default(sym, default = Proc.new)
define_method(sym, block_given? ? default : Proc.new { default })
module_eval(<<-EVAL, __FILE__, __LINE__ + 1)
def #{sym}=(value)
class << self; attr_accessor :#{sym} end
##{sym} = value
end
EVAL
end
Initially, the default value exists as a proc.
Once you invoke the setter, the getter and setter methods are overwritten by attr_accessor methods and the instance variable is initialized.
The problem is that the default proc in the getter returns the class-level default value. So when you do something like:
> c2.naive_collection[0] = "problem!" # => ["problem!", 0]
you're actually changing the default value for the class.
I think this method should probably be implemented as:
class Module
def attr_accessor_with_default(sym, default = Proc.new)
module_eval(<<-EVAL, __FILE__, __LINE__ + 1)
def #{sym}
class << self; attr_reader :#{sym} end
##{sym} = #{ default.dup }
end
def #{sym}=(value)
class << self; attr_accessor :#{sym} end
##{sym} = value
end
EVAL
end
end
I'll ticket it and offer up a patch.
Update again:
https://rails.lighthouseapp.com/projects/8994-ruby-on-rails/tickets/6496