update not propagated in nested models - ruby-on-rails

I have a nested models set :
class Event < ActiveRecord::Base
belongs_to :place
:place
attr_accessible :place_attributes, :reject_if => :all_blank, :allow_destroy => false
class Place < ActiveRecord::Base
has_many :events
validates :label, :presence => true,
:uniqueness => {:case_sensitive => true, :on => :create }
validates :description, :presence => {:on => :create},
:uniqueness => {:case_sensitive => true , :on => :create}
In a test scenario, w the nested form, the user can update only the Place#label attributes, keeping all the other information..
test "should_update_event_place_data" do
put :update, :locale => I18n.locale, :id => #event[:id],
:event => { :place_attributes => { label: "a very beautiful place" } }
which leads to a request to EventsController#update, receiving the parameters :
params
{"event"=>{"place_attributes"=>{"label"=>"a very beautiful place"}}, "locale"=>"en",
"id"=>"145", "controller"=>"backoffice/events", "action"=>"update"}
(rdb:1) #event.update_attributes(params[:event])
false
#messages={:"place.description"=>["cannot be blank"]
But the validation is on create , not update .... no validation error should be detected ..
what could be wrong ?
thanks for help
I did more testing
debugger , right after the test setup ( before sending the put request)
#event_0
#<Event id: 161, account_id: 3, place_id: 249, slug: "my-new-event-on-2013-01-01-at- edinburgh-united-king...", title: "My New Event"
#event_0.place
#<Place id: 249, label: "new fake place",..
test request:
put :update, :locale => I18n.locale, :id => #event_0[:id], :event => { :place_attributes => { label: "a very beautiful place"} }
params in request are OK, #request/method = PUT
In EventsController#update
#event.update_attributes(params[:event])
.... I inserted a debug in the Place model...
(before_validation :i_am_on_create, :on => :create)
def i_am_on_create
debugger
p "CREATING"
end
and it's creating !! don't understand why it's not updating the parent nested model

update_attributes does not propagate the updates to associations. If you watch the source code (http://apidock.com/rails/ActiveRecord/Base/update_attributes) you'll see that the #save
is called in the end. And this is the default behaviour:
# existing resource 'mazeratti car'
car.name = "Wheelz"
car.brand.label = "Ferrari"
car.save
car.reload
car.name #=> "Wheelz"
car.brand.label #=> "Mazeratti"
if you want associations to be updated all the time the object is updated, look into using "autosave" (http://apidock.com/rails/ActiveRecord/Associations/ClassMethods/belongs_to : Options)

If you're only wanting to test that the label attribute is updated, why not try doing update_attribute on that field only rather than the whole 'event'? Something like:
#event.place_attributes.update_attribute(
:label => params[:event][:place_attributes][:label]
)
Not tested - but you get the idea...

SOLVED
in order to update the nested model, I need to add the model instance id :
put :update, :locale => I18n.locale, :id => #event_0[:id], :event => { :place_attributes => { id: #event_0.place[:id], label: "a very beautiful place"} }
so in :place_attributes , I added the existing #event_0.place[:id] , and it's now updating
I found it in Anson answer on Feb 17 at 17:04 bottom page at
accepts_nested_attributes_for with find_or_create?

Related

Rails 3.1: Trying to update ActiveRecord: NoMethodError: undefined method `keys' for #<ActiveRecord::Relation:0x007fdfb82aca98>

I'm trying to update an ActiveRecord, which seems like it SHOULD be easy enough. Methods I've tried in my
Controller (metadata_controller.rb):
def update_metadata_type
#metadata_type = MetadataType.find(params[:id])
if #metadata_type.update_attributes(params)
render :template => 'metadata/show_metadata_type'
else
# return error template
end
end
def update_metadata_type
if MetadataType.update(1, { :name => params[:name] })
render :template => 'metadata/show_metadata_type'
else
# return error template
end
end
def update_metadata_type
#metadata_type = MetadataType.find(params[:id])
#metadata_type.name = params[:name]
#metadata_type.save
render :template => 'metadata/show_metadata_type'
end
Model (metadata_type.rb):
class MetadataType < ActiveRecord::Base
has_many :metadata_type_attributes, :dependent => :destroy
has_many :attributes, :through => :metadata_type_attributes, :dependent => :destroy
attr_accessible :name, :attribute_type_id
validates :name, :uniqueness => true
end
Different model with same problem (attribute_type.rb):
class AttributeType < ActiveRecord::Base
belongs_to :attributes
attr_accessible :data_type
validates :data_type, :uniqueness => true
end
routes.rb:
match '/metadata_type/:id' => 'metadata#update_metadata_type', :via => [:put], :as => 'update_metadata_type'
Each time I'm sending the data using RESTClient, with the appropriate headers to send json, with the following data in a PUT command:
{
"name" : "NewName"
}
I've also used binding.pry in the controller to make sure #metadata_type is found (and not nil) and it always looks right:
[1] pry(#<MetadataController>)> #metadata_type = MetadataType.find(params[:id])
=> #<MetadataType id: 1, name: "Category", created_at: "2011-11-15 16:02:53", updated_at: "2011-11-15 16:02:53">
Params also looks right:
[7] pry(#<MetadataController>)> params
=> {"name"=>"NewName",
"controller"=>"metadata",
"action"=>"update_metadata_type",
"id"=>"1",
"metadatum"=>{"name"=>"NewName"}}
But for some reason, no matter how I try and save, I get the same error:
NoMethodError (undefined method `keys' for #<ActiveRecord::Relation:0x007fdfb7637fd8>):
app/controllers/metadata_controller.rb:50:in `update_metadata_type'
Any idea on what's causing this? All help is appreciated
Sorry I'm a little late to the party but you can't define an association with the name attributes as it ends up overriding the built in attributes accessor method defined and used by ActiveRecord itself.
Well hopefully this helps anyone else who happens to come across this obscure error.
The problem is with these lines:
has_many :metadata_type_attributes, :dependent => :destroy
has_many :attributes, :through => :metadata_type_attributes, :dependent => :destroy
I'm a little confused as to what exactly you're trying to do, but I suspect you mean:
has_many :attributes, :class_name => "MetadataTypeAttributes", :dependent => :destroy

Trouble on updating record using an 'has_many :through' Record Association

I am using Ruby on Rails 3.0.7 and I have a problem on an associated model validation for an updating controller action. I am using an "has_many :through" association so that I can handle user-category relationships.
In the User model I have:
has_many :user_category_relationships,
:autosave => true,
:dependent => :destroy
has_many :user_categories,
:through => :user_category_relationships,
:source => :user_category,
:dependent => :destroy
In the _form view file I have:
<% #current_user.user_categories.each do |user_category| %>
<%= check_box_tag :user_category_ids, user_category.id, #user.user_categories.include?(user_category), :name => 'user_relationships[user_category_ids][]' %>
<%= label_tag "user_category_#{user_category.id}", user_category.name %>
<% end %>
In the UserCategoryRelationship model I have:
belongs_to :users
belongs_to :categories
# Validation
validates :name_id,
:presence => true,
:uniqueness => {
:scope => [:created_by_user_id, :category_id]
}
validates :category_id,
:presence => true,
:uniqueness => {
:scope => [:created_by_user_id, :name_id]
}
validates :created_by_user_id,
:presence => true,
:uniqueness => {
:scope => [:category_id, :name_id]
}
In the UsersController I have:
def create
...
# `params[:user_relationships][:user_category_ids]` used below are id values
# related to user-category relationships.
unless params[:user_relationships][:user_category_ids]
# Set default user category if not selected.
#user.user_category_relationships.build(
:category_id => '1',
:created_by_user_id => #current_user.id,
:name_id => #name.id
)
else
params[:user_relationships][:user_category_ids].each { |user_category_id|
#user.user_category_relationships.build(
:category_id => user_category_id,
:created_by_user_id => #current_user.id,
:name_id => #name.id
)
}
end
if #user.save
...
end
end
def update
...
# Note: The following code is equal to that in the controller 'create'
# action. However here is my problem because validation (read after
# for more information about).
unless params[:user_relationships][:user_category_ids]
# Set default user category if not selected.
#user.user_category_relationships.build(
:category_id => '1',
:created_by_user_id => #current_user.id,
:name_id => #name.id
)
else
params[:user_relationships][:user_category_ids].each { |user_category_id|
#user.user_category_relationships.build(
:category_id => user_category_id,
:created_by_user_id => #current_user.id,
:name_id => #name.id
)
}
end
if #user.update_attributes(params[:user])
...
end
end
The above code works except for the update action: when I try to run that I get always the same error, as well.
# Validation errors on updating the 'user_category_relationships' associated model
:"user_category_relationships.category_id" =>["has already been taken"]
:"user_category_relationships.name_id" =>["has already been taken"]
:"user_category_relationships.created_by_user_id" =>["has already been taken"]
What I would like to do, since I am not using the "RoR magical\automatic way" at all (that is, I don't use the collection_singular_ids method - see also this and this), is to make properly work the update controller action so that I can still use the "automatic" creation of user-category relationships (triggered on save and update_attributes(...) methods) but avoiding to generate above validation errors (that is, create valid data in the database table). How can I do? What do you advice about this issue?
P.S.: Where I am in trouble is in the update action, precisely figuring out how to "choose"\"filter" and "coding" records to update, to create and to delete in the database having params[:user_relationships][:user_category_ids] as input data.

Whats wrong with this form routing?

I have a users model and a book model. Users can read books (as a reader) which creates an entry in the Readings model:
id | reader_id | book_id
Users also have a list of books that they have read. These are stored in the Red (I use Red because the present and past tense of the word 'read' are the same) model which looks the same as the Reading model above.
Now when a user is reading a book, I would like to display a button which represents finishing the book.
The finish action is in the ReadingsController and looks like this:
def finish
#book = current_user.readings.find(params[:id]).book
current_user.stop_reading!(#book)
current_user.make_red! #book
redirect_to :back
end
As you can probably tell, this takes in the id of a record in the readings table, destroys it and makes a new record in the table for recording books red.
The form helper for the "Finish Reading" button currently looks like this:
<%= form_for :reading, current_user.readings.find_by_book_id(book.id), :url => { :controller => :readings, :action => "finish" }, :method => :delete do |f| %>
<div class="actions"><%= f.submit button_text %></div>
<% end %>
But for some reason this renders a form with the wrong id because "9781440506604" is not the id of a record in the readings table, it's the id of a record in the books table (the ISBN-13 of a book to be precise).
<form accept-charset="UTF-8" action="/readings/9781440506604/finish" method="post">
</form>
What is it I'm doing wrong?
EDIT to add reading.rb
class Reading < ActiveRecord::Base
attr_accessible :book_id
# one person reading a new book may cause feed_item creations in multiple users feeds
has_many :feed_items, :as => :event
has_many :comments, :as => :parent, :dependent => :destroy
scope :from_users_followed_by, lambda { |user| followed_by(user) }
# need to pass the class name here because there is no Reader model
belongs_to :reader, :class_name => "User"
belongs_to :book
validates :reader_id, :presence => true
validates :book_id, :presence => true
def self.followed_by(user)
...
end
end
# and user.rb
class User < ActiveRecord::Base
attr_accessible :name, :email, :password, :password_confirmation, :avatar, :remember_me, :avatar_url
has_many :readings, :dependent => :destroy,
:foreign_key => "reader_id"
has_many :reads, :through => :readings, :source => :book
has_many :reds, :foreign_key => "reader_id",
:dependent => :destroy
has_many :red, :through => :reds, :source => :book
def reading? book
self.readings.find_by_book_id(book)
end
def read! book
self.readings.create!(:book_id => book.id)
end
def stop_reading! book
self.readings.find_by_book_id(book).destroy
end
def red? book
self.reds.find_by_book_id(book)
end
def make_red! book
unless red? book
self.reds.create!(:book_id => book.id)
end
end
end
By the way I tried making a user who is reading book 1 and doing user.readings.find_by_book_id(1) in the console and it returns a record from the readings table.
as requested
# routes.rb
resources :readings, :only => [:create, :destroy, :show] do
member do
post :create_comment
delete :finish
end
end
Looks like you have got to_param method in your Reading model
try to call id clearly:
current_user.readings.find_by_book_id(book.id).id
UPD
remove :only => [:create, :destroy, :show] from your routes
use this <%= form_for :reading, current_user.readings.find_by_book_id(book.id), :url => { :controller => :readings, :action => "finish", :id => current_user.readings.find_by_book_id(book.id).id }, :html => {:method => :delete} do |f| %>
I'm not particularly knowledgeable about rails 3 (still using rails 2), but shouldn't you be passing more information to the :url param?
This doesn't seem to mention anything about the ID you want to post to:
:url => { :controller => :readings, :action => "finish" }
Shouldn't it be something closer to this:
:url => { :controller => :readings, :action => "finish", :id => reading_id }
(Assuming reading_id to be substituted for the actual ID)

automatically generate instances of a model when a different one is created?

I really didn't know how to phrase the question better but here goes:
Lets say i have 2 models
class Record < ActiveRecord::Base
has_many :roles, :dependent => :destroy
end
class Role < ActiveRecord::Base
belongs_to :record
end
I want a way to make sure that every time a new record instance is created 2 new roles are created automatically by the system for that record.
each role will have a name, record_id, Boolean called edit and Boolean called review.
so if i create a record called Hello and it has an ID 1 then the system should create these 2 new roles at the same time:
Role 1: name: Hello edit, record_id: 1, edit: true, review: false
Role 2: name: Hello review, record_id: 1, edit: false, review: true
ActiveRecord callbacks to the rescue:
class Record < ActiveRecord::Base
after_create :create_roles
def create_roles
Role.create :name => "Hello edit", :record_id => self.id, :edit => true, :review => false
Role.create :name => "Hello review", :record_id => self.id, :edit => false, :review => true
end
end
Try this:
class Record < ActiveRecord::Base
has_many :roles
after_create :add_roles
def add_roles
roles.create [
{:name => "Hello edit", :edit => true, :review => false},
{:name => "Hello review", :edit => false, :review => true}]
end
end

How do I make a grouped select box grouped by a column for a given model in Formtastic for Rails?

In my Rails project I'm using Formtastic to manage my forms. I have a model, Tags, with a column, "group". The group column is just a simple hardcoded way to organize my tags. I will post my Tag model class so you can see how it's organized
class Tag < ActiveRecord::Base
class Group
BRAND = 1
SEASON = 2
OCCASION = 3
CONDITION = 4
SUBCATEGORY = 5
end
has_many :taggings, :dependent => :destroy
has_many :plaggs, :through => :taggings
has_many :monitorings, :as => :monitorizable
validates_presence_of :name, :group
validates_uniqueness_of :name, :case_sensitive => false
def self.brands(options = {})
self.all({ :conditions => { :group => Group::BRAND } }.merge(options))
end
def self.seasons(options = {})
self.all({ :conditions => { :group => Group::SEASON } }.merge(options))
end
def self.occasions(options = {})
self.all({ :conditions => { :group => Group::OCCASION } }.merge(options))
end
def self.conditions(options = {})
self.all({ :conditions => { :group => Group::CONDITION } }.merge(options))
end
def self.subcategories(options = {})
self.all({ :conditions => { :group => Group::SUBCATEGORY } }.merge(options))
end
def self.non_brands(options = {})
self.all({ :conditions => [ "`group` != ? AND `group` != ?", Tag::Group::SUBCATEGORY, Tag::Group::BRAND] }.merge(options))
end
end
My goal is to use Formtastic to provide a grouped multiselect box, grouped by the column, "group" with the tags that are returned from the non_brands method. I have tried the following:
= f.input :tags, :required => false, :as => :select, :input_html => { :multiple => true }, :collection => tags, :selected => sel_tags, :group_by => :group, :prompt => false
But I receive the following error:
(undefined method `klass' for
nil:NilClass)
Any ideas where I'm going wrong?
Thanks for looking :]
I'm not sure we support :group_by with a custom :collection. In fact, that who part of the code was a messy contribution. So, try omitting the :collection for starters, and see where you end up. If there's a bug with Formtastic, please add an issue on Github.
I'd first move your Group class out of this file, and just inherit from where you want, or use a Module in this class. This is the preferred way of getting methods an constants into a class and staying organized.

Resources