I have a nested model form that throws the error "undefined method 'media_type' for #<Array:0x1060460d0>" when calling update_attributes. What is wrong with the media_type association?
class Publication < ActiveRecord::Base
has_many :products
accepts_nested_attributes_for :products, :allow_destroy => true
end
class Product < Offering
belongs_to :media_type
end
class Offering < ActiveRecord::Base
belongs_to :publication
end
class MediaType < ActiveRecord::Base
belongs_to :meaning
has_many :products
end
Here is what I am submitting to the form.
{"commit"=>"Commit changes",
"_method"=>"put",
"authenticity_token"=>"e2/62ffmRVuNsCVP65zy4SLprWgRSa+DdLc2RXzM+UQ=",
"id"=>"628",
"publisher_publication"=>{"edition_attributes"=>{"title"=>"this is the title",
"short_description"=>"this is the description",
"abstract"=>"",
"subtitle"=>"",
"id"=>"200",
"long_description"=>"",
"title_prefix"=>"",
"work_attributes"=>{"id"=>"200"}},
"volume"=>"",
"issue"=>"",
"date_published"=>"2006-09-20",
"products_attributes"=>{"1289147822429"=>{"price"=>0,
"document"=>#<File:/var/folders/e9/e965IrazFgu0fm-rjRtvIk+++TI/-Tmp-/RackMultipart20101107-638-1vffwzk-0>,
"media_type_id"=>"1"}},
"imprint_id"=>"3"}}
Here is my controller action.
def update
#publication = Publisher::Publication.find(params[:id])
if #publication.update_attributes(params[:publisher_publication])
flash[:notice] = "Successfully updated publication and products."
redirect_to(publisher_publication_url(#publication))
else
render :action => 'edit'
end
end
I just solved a similar problem by troubleshooting it with rails console...
Maybe this will help you too.
Related
I have a Store object that has an email_address attribute. Using the logic from and How To Build A Form and Handling Inbound Email Parsing with Rails, I'm trying to figure out how to structure a Conversation where a visitor can email the Store, and the Store can reply through email - their replies would post a Message to the Conversation.
When a visitor inquires to the store (via form), I create a Reservation record with their name and email, and start a Conversation like this:
#conversation = Conversation.create(sender_id: self.id, recipient_id: self.store_id)
I wanted to model the notifications similar to this, where everyone but the sender receives an email, but I'm stumped on how to map the User, since it's two different objects (Reservation and Store):
def send_notifications!
(forum_thread.users.uniq - [user]).each do |user|
UserMailer.new_post(user, self).deliver_now
end
end
The Conversation model looks like this, may be wrong, any guidance on what I could use to make the messages unique and structure the notifications?
class Conversation < ApplicationRecord
belongs_to :sender, :foreign_key => :sender_id, class_name: "Reservation"
belongs_to :recipient, :foreign_key => :recipient_id, class_name: "Store"
belongs_to :reservation
has_many :messages, dependent: :destroy
end
The simplest and most flexible way would be to set this up as a many-to-many association:
class User < ApplicationRecord
has_many :messages
has_many :conversations, through: :messages
end
class Message < ApplicationRecord
belongs_to :user
belongs_to :conversation
end
class Conversation < ApplicationRecord
has_many :messages
has_many :users, through: :messages
end
Here Message actually works as the join table that ties it together. Conversion is the recipient. When sending an initial message to a user you would POST to /users/:user_id/messages:
<%= form_with(model: [#user, #message || Message.new]) do |f| %>
# ...
<% end %>
module Users
class MessagesController < ApplicationController
# POST /users/:user_id/messages
def create
#user = User.find(params[:user_id])
#conversation = Conversation.joins(:users)
.where(users: { id: [current_user, #user]})
.first_or_create
#message = #conversation.messages.new(message_params.merge(user: current_user))
if #message.save
redirect_to #coversation
else
render :new
end
end
end
end
And then you would handle the views and controllers (such as a chat window) for conversations in a separate controller:
<%= form_with(model: [#conversation, #message || #conversation.messages.new]) do |f| %>
# ...
<% end %>
module Conversations
class MessagesController < ApplicationController
# POST /conversations/:conversation_id/messages
def create
#conversation = Conversation.find(params[:conversation_id])
#message = #conversation.messages.new(message_params.merge(user: current_user))
if #message.save
redirect_to #coversation
else
render :new
end
end
end
end
I can't get rails to update my nested attributes, though regular attributes work fine. This is my structure:
unit.rb:
class Unit < ApplicationRecord
has_many :unit_skill_lists
has_many :skill_lists, through: :unit_skill_lists, inverse_of: :units, autosave: true
accepts_nested_attributes_for :skill_lists, reject_if: :all_blank, allow_destroy: true
end
unit_skill_list.rb:
class UnitSkillList < ApplicationRecord
belongs_to :unit
belongs_to :skill_list
end
skill_list.rb:
class SkillList < ApplicationRecord
has_many :unit_skill_lists
has_many :units, through: :unit_skill_lists, inverse_of: :skill_lists
end
And this is (part of) the controller:
class UnitsController < ApplicationController
def update
#unit = Unit.find(params[:id])
if #unit.update(unit_params)
redirect_to edit_unit_path(#unit), notice: "Unit updated"
else
redirect_to edit_unit_path(#unit), alert: "Unit update failed"
end
end
private
def unit_params
unit_params = params.require(:unit).permit(
...
skill_list_attributes: [:id, :name, :_destroy]
)
unit_params
end
end
The relevant rows in the form (using formtastic and cocoon):
<%= label_tag :skill_lists %>
<%= f.input :skill_lists, :as => :check_boxes, collection: SkillList.where(skill_list_type: :base), class: "inline" %>
Any idea where I'm going wrong? I have tried following all guides I could find but updating does nothing for the nested attributes.
Edit after help from Vasilisa:
This is the error when I try to update a Unit:
ActiveRecord::RecordInvalid (Validation failed: Database must exist):
This is the full unit_skill_list.rb:
class UnitSkillList < ApplicationRecord
belongs_to :unit
belongs_to :skill_list
belongs_to :database
end
There is no input field for "database". It is supposed to be set from a session variable when the unit is updated.
If you look at the server log you'll see something like skill_list_ids: [] in params hash. You don't need accepts_nested_attributes_for :skill_lists, since you don't create new SkillList on Unit create/update. Change permitted params to:
def unit_params
params.require(:unit).permit(
...
skill_list_ids: []
)
end
UPDATE
I think the best options here is to set optional parameter - belongs_to :database, optional: true. And update it in the controller manually.
def update
#unit = Unit.find(params[:id])
if #unit.update(unit_params)
#unit.skill_lists.update_all(database: session[:database])
redirect_to edit_unit_path(#unit), notice: "Unit updated"
else
redirect_to edit_unit_path(#unit), alert: "Unit update failed"
end
end
I'm having a bit of trouble understanding how to setup the contributions controller and the form in the view. I've set some forms in the view so i know the join tables work.
As of right now a post belongs_to user && a user has_many posts
Objective:
1. user1 creates post - which belongs to user1
2. user2 requesting to join the user1_post as a contributor
3. user1 accepts or declines request
4. user2 is now a contributor to user1_post
5. user1 can remove user2 as a contributor
Got the has_many :through setup properly and have tested it in the console
contribution.rb
class Contribution < ActiveRecord::Base
belongs_to :post
belongs_to :user
def accept
self.accepted = true
end
end
post.rb
class Post < ActiveRecord::Base
belongs_to :author, class_name: 'User'
has_many :contribution_requests, -> { where(accepted: false) }, class_name: 'Contribution'
has_many :contributions, -> { where(accepted: true) }
has_many :contributors, through: :contributions, source: :user
end
user.rb
class User < ActiveRecord::Base
has_many :posts, foreign_key: 'author_id'
has_many :contribution_requests, -> { where(accepted: false) }, class_name: 'Contribution'
has_many :contributions, -> { where(accepted: true) }
has_many :contributed_posts, through: :contributions, source: :post
end
contributions_controller.rb
class ContributionsController < ApplicationController
def create
#contribution = current_user.contributions.build(:user_id => params[:id])
if #contribution.save
flash[:notice] = "Added contributor."
redirect_to posts_path(#post)
else
flash[:error] = "Unable to add contributor."
redirect_to posts_path(#post)
end
end
def destroy
#contribution = current_user.contributions.find(params[:id])
#contribution.destroy
flash[:notice] = "Removed contributor."
redirect_to root_url
end
end
Without much context, this is what I'd do:
#config/routes.rb
resources :posts do
resources :contributions, only: [:create, :destroy] #-> can use posts#edit to add extra contributions
end
#app/controllers/posts_controller.rb
class PostsController < ApplicationController
def edit
#post = Post.find params[:id]
end
end
#app/views/contributions/edit.html.erb
<%= form_for #post do |f| %>
# #post form
<% end %>
## contributor add / remove form (select boxes)
#app/controllers/contributions_controller.rb
class ContributionsController < ApplicationController
def create
#post = Post.find params[:post_id]
#contribution = current_user.contributions.new contribution_params
#contribution.post = #post
notice = #contribution.save ? "Added Contributor" : "Unable to add contributor"
redirect_to #post, notice: notice
end
def destroy
#contribution = current_user.contributions.find params[:id]
#contribution.destroy
redirect_to root_url, notice: "Removed Contributor"
end
private
def contribution_params
params.require(:contribution).permit(:user, :post, :accepted)
end
end
As an aside, you should look at an ActiveRecordExtension to give you some methods for your conbtributions association (instead of having multiple associations):
#app/models/post.rb
class Post < ActiveRecord::Base
has_many :contributions, -> { extending ContributionExtension }
end
#app/models/user.rb
class User < ActiveRecord::Base
has_many :contributions, -> { extending ContributionExtension }
end
#app/models/concerns/contribution_extension.rb
class ContributionExtension
def requests(status=false)
where accepted: status
end
def accepted(status=true)
where accepted: status
end
end
#post.contirbutions.requets
#post.contributions.accepted
#user.contributions.requests
#user.contributions.accepted
--
And also, you should look at implementing a state_machine for your Contribution model:
#app/models/contribution.rb
class Contribution < ActiveRecord::Base
state_machine :accepted, initial: :pending do
event :accept do
transition [:pending, :denied] => :accepted
end
event :deny do
transition [:pending, :accepted] => :denied
end
end
end
Great article about it here.
This will allow you to call:
#contribution = current_user.contributions.find params[:id]
#contribution.accept
It will also give you several other cool methods:
#contribution.accepted?
#contribution.state
Sorry for noob question, I'm just trying RoR, but I have an issue with passing to strong params (I guess) nested resources items.
controller:
def update
if #product.update(product_params)
redirect_to #product, notice: "product has been updated"
end
end
models:
class Image < ActiveRecord::Base
belongs_to :products
end
class Product < ActiveRecord::Base
has_one :image, dependent: :destroy
accepts_nested_attributes_for :image
end
view:
= p.fields_for :image do |img|
= img.text_field :path
params:
"image_attributes"=>{"path"=>"1.jpg", "id"=>"13"},
"category_id"=>"2"}
As result only image path not updating...
Please give me solution.
Thank You!
class Job < ActiveRecord::Base
has_many :employments, :dependent => :destroy
has_many :users, :through => :employments
class User < ActiveRecord::Base
has_many :employments
has_many :jobs, :through => :employments
class Employment < ActiveRecord::Base
belongs_to :job
belongs_to :user # Employment has an extra attribute of confirmed ( values are 1 or 0)
In my view i am trying to update the confirmed fied from 0 to 1 on user click.
<%= link_to "Confirm Job", :action => :confirmjob, :id => job.id %>
In my job Controller I have
def confirmjob
#job = Job.find(params[:id])
#job.employments.update_attributes(:confirmed, 1)
flash[:notice] = "Job Confirmed"
redirect_to :dashboard
end
I am sure this is all wrong but I seem to be guessing when it comes to has_many: through.
How would I do update the confirmed field in a joined table?
I think that a job is assigned to a user by the employment. Thus, updating all employments is not a good idea, as Joel suggests. I would recommend this:
class Employment
def self.confirm!(job)
employment = Employment.find(:first, :conditions => { :job_id => job.id } )
employment.update_attribute(:confirmed, true)
end
end
from your controller
#job = Job.find(params[:id])
Employment.confirm!(#job)
This implies that one job can only be taken by one user.
Here is a stab at it (not tested):
def confirmjob
#job = Job.find(params[:id])
#jobs.employments.each do |e|
e.update_attributes({:confirmed => 1})
end
flash[:notice] = "Job Confirmed"
redirect_to :dashboard
end