why does update_attribute not work here - ruby-on-rails

Why does update attribute not work here? If it shouldn't be used at the model level, then what could I use instead to accomplish what I'm trying to accomplish below?
class Question < ActiveRecord::Base
attr_accessible :body, :title
def getNewTitle qid
if qid.is_a? Integer
"new question title"
end
end
# should only have to run it once
def updateTitles
Question.all.each do |q|
begin
newTitle = getNewTitle q.id
if newTitle
q = Question.find(q.id)
q.update_attribute(:title => newTitle)
end
rescue
puts "======================error======================"
end
end
end
end

update_attribute takes in two arguments. attribute name and value
q.update_attribute(:title, newTitle)
What you have is the syntax for update_attributes
q.update_attributes(:title => newTitle)
rails convention is to use underscores instead of camelCase for method names and variable names
Eg, new_title, update_titles

As Vimsha noted, update_attribute takes two arguments.
But at the risk of being voted down, I would offer an alternative-- use update. update_attribute bypasses validation, using update will ensure that your changes pass validation before saving.
For example:
Question.all.each do |q|
begin
newTitle = getNewTitle q.id
if newTitle
Question.update(q.id, title: newTitle)
end
rescue
puts "======================error======================"
end
end
API references
for update and update_attribute

Related

Can I use the _destroy attribute in a non-nested form?

Say I have something like this in my controller:
FacultyMembership.update(params[:faculty_memberships].keys,
params[:faculty_memberships].values)
and whenever the _destroy key in params[:faculty_memberships].values is true, the record is destroyed.
Is there something like this in rails? I realize there are other ways of doing this, I was just curious if something like this existed.
Short answer
no!
Long answer
Still no! It is true that it works on nested attributes:
If you want to destroy the associated model through the attributes
hash, you have to enable it first using the :allow_destroy option.
Now, when you add the _destroy key to the attributes hash, with a
value that evaluates to true, you will destroy the associated model.
But why not trying it out in the console:
?> bundle exec rails c
?> m = MyModel.create attr_1: "some_value", attr_2: "some_value"
?> m.update(_destroy: '1') # or _destroy: true
?> ActiveRecord::UnknownAttributeError: unknown attribute '_destroy' for MyModel
This is because the update implementation is the following:
# File activerecord/lib/active_record/persistence.rb, line 245
def update(attributes)
# The following transaction covers any possible database side-effects of the
# attributes assignment. For example, setting the IDs of a child collection.
with_transaction_returning_status do
assign_attributes(attributes)
save
end
end
and the source for assign_attributes is:
# File activerecord/lib/active_record/attribute_assignment.rb, line 23
def assign_attributes(new_attributes)
if !new_attributes.respond_to?(:stringify_keys)
raise ArgumentError, "When assigning attributes, you must pass a hash as an argument."
end
return if new_attributes.blank?
attributes = new_attributes.stringify_keys
multi_parameter_attributes = []
nested_parameter_attributes = []
attributes = sanitize_for_mass_assignment(attributes)
attributes.each do |k, v|
if k.include?("(")
multi_parameter_attributes << [ k, v ]
elsif v.is_a?(Hash)
nested_parameter_attributes << [ k, v ]
else
_assign_attribute(k, v)
end
end
assign_nested_parameter_attributes(nested_parameter_attributes) unless nested_parameter_attributes.empty?
assign_multiparameter_attributes(multi_parameter_attributes) unless multi_parameter_attributes.empty?
end
This code worked for me:
class FacultyMembership < ApplicationRecord
attr_accessor :_destroy
def _destroy= value
self.destroy if value.present?
end
end
Possibly this can break nested forms with destroy - didn't check.

Rails: .create nullifies a custom value for :id

When I execute a Model.create method, if I specify a value for :id, it later gets nullified. Example:
Model.create (
:id => 50,
:name => Joe,
:enabled => yes
)
Instead what I have to do is use a .new and store it in a class variable, store my id value via the class variable, and then finally call a save:
m = Model.new (
:name => Joe,
:enabled => yes
)
m.id = 50
m.save
I am trying to execute this code in a seeds.rb, and this is NOT very DRY code. How can I do this better and achieve the same results?
id is just attr_protected. To prevent that, you can override the list of default protected attributes. Be careful doing this anywhere that attribute information can come from the outside. The id field is default protected for a reason.
class Model < ActiveRecord::Base
private
def attributes_protected_by_default
[]
end
end
or go with #Leo answer
This might be an answer for you. Model.create is basically a Model.new followed by a Model.save and since you are changing the id and saving again you might as well do
m = Model.new {
:name => Joe,
:enabled => yes
}
m.id = 50
m.save!
That will rid you of doing two saves.

Validate uniqueness of value between multiple fields

I understand that validating uniqueness of a standard, single field like "username" is easy. However, for something that has an unlimited number of inputs like, for example, "Favorite Movies" where a user can add as many favorite movies, is something I can't figure out.
They can choose to add or remove fields via the builder, but how do I ensure that no two or more entries are duplicates?
I think the easiest way to accomplish something like this is to validate the uniqueness of something in a scope. I can't say for sure how it would fit in your scenario since you did not describe you model associations but here is an example of how it could work in a FavoriteMovie model:
class FavoriteMovie < ActiveRecord::Base
belongs_to :user
validates_uniqueness_of :movie_name, :scope => :user_id
end
This makes sure that there can't be two movie names that are the same for one specific user.
It turns out that when using nested attributes, you can only validate what's already in the database and not new duplicate occurrences. So, a validation extension (below) with memory validation is really the only option, unfortunately.
#user.rb
class User
has_many :favorite_movies
validate :validate_unique_movies
def validate_unique_movies
validate_uniqueness_of_in_memory(
favorite_movies, [:name, :user_id], 'Duplicate movie.')
end
end
#lib/extensions.rb
module ActiveRecord
class Base
def validate_uniqueness_of_in_memory(collection, attrs, message)
hashes = collection.inject({}) do |hash, record|
key = attrs.map {|a| record.send(a).to_s }.join
if key.blank? || record.marked_for_destruction?
key = record.object_id
end
hash[key] = record unless hash[key]
hash
end
if collection.length > hashes.length
self.errors.add_to_base(message)
end
end
end
end
A very un-rails like solution to the problem would be to add a unique key constraint on the columns that in combination are required to be unique:
create unique index names_idx on yourtable (id, name);
you could easly check it like:
params[:user][:favourite_movies].sort.uniq == params[:user][:favourite_movies].sort
or in model:
self.favourite_movies.sort.uniq == self.favourite_movies.sort
irb(main):046:0> movies = ['terminator', 'ninja turtles', 'titanic', 'terminator' ].map {|movie| movie.downcase }
=> ["terminator", "ninja turtles", "titanic", "terminator"]
irb(main):047:0> movies.sort.uniq == movies.sort
=> false
You can try to create virtual attribute and check it uniqueness:
def full_name
[first_name, last_name].joun(' ')
end
def full_name=(name)
split = name.split(' ', 2)
self.first_name = split.first
self.last_name = split.last
end
You can check uniqueness on the database level by fix your migration:
CREATE TABLE properties (
namespace CHAR(50),
name CHAR(50),
value VARCHAR(100),
);
execute <<-SQL
ALTER TABLE properties
ADD CONSTRAINT my_constraint UNIQUE (namespace, name)
SQL
Little more modern approach: validates method
validates :movie_name, :uniqueness => {:scope => : user_id}

How to update counter_cache when updating a model?

I have a simple relationship:
class Item
belongs_to :container, :counter_cache => true
end
class Container
has_many :items
end
Let's say I have two containers. I create an item and associate it with the first container. The counter is increased.
Then I decide to associate it with the other container instead. How to update the items_count column of both containers?
I found a possible solution at http://railsforum.com/viewtopic.php?id=39285 .. however I'm a beginner and I don't understand it. Is this the only way to do it?
It should work automatically. When you are updating items.container_id it will decreament old container's counter and increament new one. But if it isn't works - it is strange. You can try this callback:
class Item
belongs_to :container, :counter_cache => true
before_save :update_counters
private
def update_counters
new_container = Container.find self.container_id
old_container = Container.find self.container_id_was
new_container.increament(:items_count)
old_container.decreament(:items_count)
end
end
UPD
To demonstrate native behavior:
container1 = Container.create :title => "container 1"
#=> #<Container title: "container 1", :items_count: nil>
container2 = Container.create :title => "container 2"
#=> #<Container title: "container 2", :items_count: nil>
item = container1.items.create(:title => "item 1")
Container.first
#=> #<Container title: "container 1", :items_count: 1>
Container.last
#=> #<Container title: "container 1", :items_count: nil>
item.container = Container.last
item.save
Container.first
#=> #<Container title: "container 1", :items_count: 0>
Container.last
#=> #<Container title: "container 1", :items_count: 1>
So it should work without any hacking. From the box.
Modified it a bit to handle custom counter cache names
(Don't forget to add after_update :fix_updated_counter to the models using counter_cache)
module FixUpdateCounters
def fix_updated_counters
self.changes.each { |key, (old_value, new_value)|
# key should match /master_files_id/ or /bibls_id/
# value should be an array ['old value', 'new value']
if key =~ /_id/
changed_class = key.sub /_id$/, ''
association = self.association changed_class.to_sym
case option = association.options[ :counter_cache ]
when TrueClass
counter_name = "#{self.class.name.tableize}_count"
when Symbol
counter_name = option.to_s
end
next unless counter_name
association.klass.decrement_counter(counter_name, old_value) if old_value
association.klass.increment_counter(counter_name, new_value) if new_value
end
} end end
ActiveRecord::Base.send(:include, FixUpdateCounters)
For rails 3.1 users.
With rails 3.1, the answer doesn't work.
The following works for me.
private
def update_counters
new_container = Container.find self.container_id
Container.increment_counter(:items_count, new_container)
if self.container_id_was.present?
old_container = Container.find self.container_id_was
Container.decrement_counter(:items_count, old_container)
end
end
here is an approach that works well for me in similar situations
class Item < ActiveRecord::Base
after_update :update_items_counts, if: Proc.new { |item| item.collection_id_changed? }
private
# update the counter_cache column on the changed collections
def update_items_counts
self.collection_id_change.each do |id|
Collection.reset_counters id, :items
end
end
end
additional information on dirty object module http://api.rubyonrails.org/classes/ActiveModel/Dirty.html and an old video about them http://railscasts.com/episodes/109-tracking-attribute-changes and documentation on reset_counters http://apidock.com/rails/v3.2.8/ActiveRecord/CounterCache/reset_counters
Updates to #fl00r Answer
class Container
has_many :items_count
end
class Item
belongs_to :container, :counter_cache => true
after_update :update_counters
private
def update_counters
if container_id_changed?
Container.increment_counter(:items_count, container_id)
Container.decrement_counter(:items_count, container_id_was)
end
# other counters if any
...
...
end
end
I recently came across this same problem (Rails 3.2.3). Looks like it has yet to be fixed, so I had to go ahead and make a fix. Below is how I amended ActiveRecord::Base and utilize after_update callback to keep my counter_caches in sync.
Extend ActiveRecord::Base
Create a new file lib/fix_counters_update.rb with the following:
module FixUpdateCounters
def fix_updated_counters
self.changes.each {|key, value|
# key should match /master_files_id/ or /bibls_id/
# value should be an array ['old value', 'new value']
if key =~ /_id/
changed_class = key.sub(/_id/, '')
changed_class.camelcase.constantize.decrement_counter(:"#{self.class.name.underscore.pluralize}_count", value[0]) unless value[0] == nil
changed_class.camelcase.constantize.increment_counter(:"#{self.class.name.underscore.pluralize}_count", value[1]) unless value[1] == nil
end
}
end
end
ActiveRecord::Base.send(:include, FixUpdateCounters)
The above code uses the ActiveModel::Dirty method changes which returns a hash containing the attribute changed and an array of both the old value and new value. By testing the attribute to see if it is a relationship (i.e. ends with /_id/), you can conditionally determine whether decrement_counter and/or increment_counter need be run. It is essnetial to test for the presence of nil in the array, otherwise errors will result.
Add to Initializers
Create a new file config/initializers/active_record_extensions.rb with the following:
require 'fix_update_counters'
Add to models
For each model you want the counter caches updated add the callback:
class Comment < ActiveRecord::Base
after_update :fix_updated_counters
....
end
Here the #Curley fix to work with namespaced models.
module FixUpdateCounters
def fix_updated_counters
self.changes.each {|key, value|
# key should match /master_files_id/ or /bibls_id/
# value should be an array ['old value', 'new value']
if key =~ /_id/
changed_class = key.sub(/_id/, '')
# Get real class of changed attribute, so work both with namespaced/normal models
klass = self.association(changed_class.to_sym).klass
# Namespaced model return a slash, split it.
unless (counter_name = "#{self.class.name.underscore.pluralize.split("/")[1]}_count".to_sym)
counter_name = "#{self.class.name.underscore.pluralize}_count".to_sym
end
klass.decrement_counter(counter_name, value[0]) unless value[0] == nil
klass.increment_counter(counter_name, value[1]) unless value[1] == nil
end
}
end
end
ActiveRecord::Base.send(:include, FixUpdateCounters)
Sorry I don't have enough reputation to comment the answers.
About fl00r, I may see a problem if there is an error and save return "false", the counter has already been updated but it should have not been updated.
So I'm wondering if "after_update :update_counters" is more appropriate.
Curley's answer works but if you are in my case, be careful because it will check all the columns with "_id". In my case it is automatically updating a field that I don't want to be updated.
Here is another suggestion (almost similar to Satish):
def update_counters
if container_id_changed?
Container.increment_counter(:items_count, container_id) unless container_id.nil?
Container.decrement_counter(:items_count, container_id_was) unless container_id_was.nil?
end
end

ruby object array... or hash

I have an object now:
class Items
attr_accessor :item_id, :name, :description, :rating
def initialize(options = {})
options.each {
|k,v|
self.send( "#{k.to_s}=".intern, v)
}
end
end
I have it being assigned as individual objects into an array...
#result = []
some loop>>
#result << Items.new(options[:name] => 'name', options[:description] => 'blah')
end loop>>
But instead of assigning my singular object to an array... how could I make the object itself a collection?
Basically want to have the object in such a way so that I can define methods such as
def self.names
#items.each do |item|
item.name
end
end
I hope that makes sense, possibly I am overlooking some grand scheme that would make my life infinitely easier in 2 lines.
A few observations before I post an example of how to rework that.
Giving a class a plural name can lead to a lot of semantic issues when declaring new objects, as in this case you'd call Items.new, implying you're creating several items when in fact actually making one. Use the singular form for individual entities.
Be careful when calling arbitrary methods, as you'll throw an exception on any misses. Either check you can call them first, or rescue from the inevitable disaster where applicable.
One way to approach your problem is to make a custom collection class specifically for Item objects where it can give you the information you need on names and such. For example:
class Item
attr_accessor :item_id, :name, :description, :rating
def initialize(options = { })
options.each do |k,v|
method = :"#{k}="
# Check that the method call is valid before making it
if (respond_to?(method))
self.send(method, v)
else
# If not, produce a meaningful error
raise "Unknown attribute #{k}"
end
end
end
end
class ItemsCollection < Array
# This collection does everything an Array does, plus
# you can add utility methods like names.
def names
collect do |i|
i.name
end
end
end
# Example
# Create a custom collection
items = ItemsCollection.new
# Build a few basic examples
[
{
:item_id => 1,
:name => 'Fastball',
:description => 'Faster than a slowball',
:rating => 2
},
{
:item_id => 2,
:name => 'Jack of Nines',
:description => 'Hypothetical playing card',
:rating => 3
},
{
:item_id => 3,
:name => 'Ruby Book',
:description => 'A book made entirely of precious gems',
:rating => 1
}
].each do |example|
items << Item.new(example)
end
puts items.names.join(', ')
# => Fastball, Jack of Nines, Ruby Book
Do you know the Ruby key word yield?
I'm not quite sure what exactly you want to do. I have two interpretations of your intentions, so I give an example that makes two completely different things, one of them hopefully answering your question:
class Items
#items = []
class << self
attr_accessor :items
end
attr_accessor :name, :description
def self.each(&args)
#items.each(&args)
end
def initialize(name, description)
#name, #description = name, description
Items.items << self
end
def each(&block)
yield name
yield description
end
end
a = Items.new('mug', 'a big cup')
b = Items.new('cup', 'a small mug')
Items.each {|x| puts x.name}
puts
a.each {|x| puts x}
This outputs
mug
cup
mug
a big cup
Did you ask for something like Items.each or a.each or for something completely different?
Answering just the additional question you asked in your comment to tadman's solution: If you replace in tadman's code the definition of the method names in the class ItemsCollection by
def method_missing(symbol_s, *arguments)
symbol, s = symbol_s.to_s[0..-2], symbol_s.to_s[-1..-1]
if s == 's' and arguments.empty?
select do |i|
i.respond_to?(symbol) && i.instance_variables.include?("##{symbol}")
end.map {|i| i.send(symbol)}
else
super
end
end
For his example data you will get following outputs:
puts items.names.join(', ')
# => Fastball, Jack of Nines, Ruby Book
puts items.descriptions.join(', ')
# => Faster than a slowball, Hypothetical playing card, A book made entirely of precious gems
As I don't know about any way to check if a method name comes from an attribute or from another method (except you redefine attr_accessor, attr, etc in the class Module) I added some sanity checks: I test if the corresponding method and an instance variable of this name exist. As the class ItemsCollection does not enforce that only objects of class Item are added, I select only the elements fulfilling both checks. You can also remove the select and put the test into the map and return nil if the checks fail.
The key is the return value. If not 'return' statement is given, the result of the last statement is returned. You last statement returns a Hash.
Add 'return self' as the last line of initialize and you're golden.
Class Item
def initialize(options = {})
## Do all kinds of stuff.
return self
end
end

Resources