This should be somewhat simple but cant seem to grasp the association.
I am using nested_form and paperclip. I have a model called photo to store all images and a post model. I am trying to show the photos associated to the relevant post but am getting 'undefined method avatar' when rendering the view.
class Post < ActiveRecord::Base
has_many :photos, :dependent => :destroy
accepts_nested_attributes_for :photos
attr_accessible :title, :comments, :photo_id, :avatar, :photos_attributes
end
Class Photo < ActiveRecord::Base
belongs_to :post
attr_accessible :avatar, :post_id
has_attached_file :avatar, :styles => { :medium => "300x300>", :thumb => "100x100>" }
end
Controller
def new
#post = Post.new
#post.photos.build
end
So i am under the impression that when a post gets built an association between the Post and Photo model is made? is that right?
So when i call this in the view i get the undefined method, can anyone advise where I am going wrong please
<% #posts.each do |f| %>
<ul>
<li><%= f.title %></li>
<li><%= f.department.name %></li>
<li><%= image_tag f.avatar.url(:thumb) %></li>
<li><%= link_to "Delete Post", post_path(f.id), :confirm => "Are you sure?", :method => :delete %></li>
</ul>
<% end %>
I have tried
<%= image_tag f.photo.avatar.url(:thumb) %>
but that doesnt work either
May be you are creating photo wrong.
Here you can see how the form looks: Nested form using paperclip
And also Post has_many :photos, so it must be somth. like
<% #posts.each do |f| %>
....
<% f.photos.each do |photo| %>
<%= image_tag photo.avatar.url(:thumb) %>
<% end %>
...
<% end %>
When I work with nested attributes I follow three steps. First, in the parent model you can use accepts_nested_attributes_for:
Class Post
has_many :photos, dependent: :destroy
accepts_nested_attributes_for :photos
attr_accessible :photos_attributes
end
Second, you can incorporate a nested form for photos so you can set the attributes of photos for that particular post:
<%= form_for(#post) do |f| %>
<%= f.fields_for :photos do |p| %>
...rest of form here...
Third, you can create a photo through the new action in the post model:
Class UserController
def new
#user = User.new(photos: Photo.new)
end
end
This last step is important. If you don't do this, you would not see the photo fields in the user form otherwise. If you follow these steps you should be able to set all the attributes from both photos and users in the users form.
I think in your controller you should first define which is the post object you are associating to :
def new
#post = Post.find(params[:post_id]
#photo = #post.photos.build
....
end
The same is in the create action of the PhotosController .
Related
Setup
I have a simple many to many relationship between a Submit and an Answer through SubmitAnswer.
Answers are grouped by a Question (in my case each question has three answers) - think of it as a multiple choice quiz.
I have been trying to use SimpleFormFor to make a form which renders a predetermined set of questions, where each question has a predetermined set of answers.
Something like this:
#form
<%= simple_form_for Submit.new, url: "/questionnaire" do |f| %>
<% #questions.each do |question| %>
<%= f.association :answers, collection: question.answers %>
<% end %>
<%= f.submit :done %>
<% end %>
#controller
def create
#submit = Submit.new(submit_params)
#submit.user = current_user
if #submit.save
redirect_to root_path
else
render :new
end
end
def submit_params
params.require(:submit).permit(answer_ids: [])
end
When I submit the form, Rails creates the join table, SubmitAnswers, automatically.
So here is the crux of the matter: Whats the easiest way to re-render the form, errors and all, if not all questions have been answered, ie if #submit.answers.length != #question.length ?
I can add a custom error with errors.add(:answers, 'error here'), but when I re-render, the correctly selected answers arent repopulated, which is suboptimal.
For completions sacke, here are my models:
class Submit < ApplicationRecord
belongs_to :user
has_many :submit_answers
has_many :answers, through: :submit_answers
end
class SubmitAnswer < ApplicationRecord
belongs_to :submit
belongs_to :answer
end
class Answer < ApplicationRecord
has_many :submit_answers
has_many :submits, through: :submit_answers
end
Alright, after some digging we did find the answer to make the form work, albeit with more pain that we anticipated a simple many-to-many should take.
#model
class Submit < ApplicationRecord
belongs_to :user
has_many :submit_answers
has_many :answers, through: :submit_answers
accepts_nested_attributes_for :submit_answers
end
#controller
def new
#submit = Submit.new
#questions.count.times { #submit.submit_answers.build }
end
def create
#submit = Submit.new(submit_params)
#submit.user = current_user
if #submit.save
redirect_to root_path
else
render :home
end
end
def submit_params
params.require(:submit).permit(submit_answers_attributes:[:answer_id])
end
#form
<%= simple_form_for #submit do |f| %>
<%= f.simple_fields_for :submit_answers do |sa| %>
<%= sa.input :answer_id, collection: #answers[sa.options[:child_index]], input_html: { class: "#{'is-invalid' if sa.object.errors.any?}"}, label: #questions[sa.options[:child_index]].name %>
<div class="invalid-feedback d-block">
<ul>
<% sa.object.errors.full_messages.each do |msg| %>
<li> <%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<%= f.submit :done %>
<% end %>
The solution is to use simple_fields_for/fields_for. Note that <%= sa.input :answer_id %> must be :answer_id, not :answer, which is something I had tried before.
Also one must allow accepts_nested_attributes_for :submit_answers, where :submit_answers is the join_table.
I prebuild my SubmitAnswers like so: #questions.count.times { #submit.submit_answers.build } which generates an input field for each question, all of which get saved on the form submit, a la build.
For the strong_params one needs to permit the incoming ids:
params.require(:submit).permit(submit_answers_attributes:[:answer_id]), so in this case submit_answers_attributes:[:answer_id].
For anyone wondering what the params look like:
{"authenticity_token"=>"[FILTERED]",
"submit"=>
{"submit_answers_attributes"=>
{"0"=>{"answer_id"=>""}, "1"=>{"answer_id"=>""}, "2"=>{"answer_id"=>""}, "3"=>{"answer_id"=>""}, "4"=>{"answer_id"=>""}, "5"=>{"answer_id"=>""}, "6"=>{"answer_id"=>""}}},
"commit"=>"done"}
As for the errors, im sure there might be a better way, but for now I have just manually added them with input_html: { class: "#{'is-invalid' if sa.object.errors.any?}"}.
On a final note, the sa.object # => SubmitAnswer allows me to retrieve the Model, the errors of that Model or whatever else one might want.
I've used the following technique to successfully upload multiple files using paperclip (without using nested attributes)
Project.rb
has_many :photos, :dependent => :destroy
Photo.rb
has_attached_file :asset, :styles => { :medium => "300x300>" }
belongs_to :project
photos/new.html.erb
<%= form_for #photo, :html => { multipart: true } do |f| %>
<div>
<%= f.label :asset, 'Project Photos', class: 'label1' %>
<%= file_field(:photo, :asset, :multiple => true) %>
</div>
<div>
<%= f.submit "Add photos to project" %>
</div>
Photos_controller:
def create
#project = Project.find(params[:id])
#client = Client.find(params[:id])
params[:photo][:asset].each do |file|
#photo = #project.photos.build(:asset => file)
if
#photo.save.......
photos/show.html.erb
<div>
<% #project.photos.each do |p| %>
<%= image_tag(p.asset.url(:square)) %>
<%= check_box_tag 'destruction[]', p.id %>
<% end %>
Routes file:
resources :photos do
collection do
delete 'destroy_multiple'
end
end
I'm trying to create an array of id's based on checkboxes to be passed to the destroy_multiple action in the photos controller. params[:destruction] does yield the correct ids.
What would be the most efficient way of telling the destroy_multiple action to delete only those assets whose id's are in the destruction array?
Many thanks for your time.
destroy_all
Paperclip is just a link between the saved asset (image/video), and your ORM (in our case ActiveRecord).
This means that you should still be able to perform all the queries you want through your standard AR methods (destroy_all etc), with Paperclip removing the relevant assets as you require.
As such...
#config/routes.rb
resources :photos do
delete :destroy_multiple, action: :destroy, on: :collection
end
#app/controllers/photos_controller.rb
class PhotosController < ApplicationController
def destroy
ids = params[:destruction] || params[:id]
Photo.destroy_all ids if ids
end
end
Paperclip will handle the rest!
I've got the following models in my Rails app:
class Post < ActiveRecord::Base
belongs_to :user
has_many :pictures, :dependent => :destroy
end
class Picture < ActiveRecord::Base
belongs_to :post
has_many :comments
has_attached_file :image, styles: { medium: "800x800>", thumb: "225x225#" }
validates_attachment_content_type :image, :content_type => ["image/jpg","image/jpeg","image/png"]
end
So a Post has_many Pictures. On my Post#index view, I use the following code to iterate through the posts and display the thumbnail for each of their pictures:
<% #posts.each do |post| %>
<% post.pictures.each do |picture| %>
<li>
<%= link_to image_tag(picture.image.url(:thumb)), picture_path(picture)%>
</li>
<% end %>
In order to limit the number of pictures displayed on each page, I'm attempting to implement pagination by using the following code in my Posts controller:
#pictures = #posts.pictures.paginate(:per_page => 5, :page => params[:page])
This gives the following error:
undefined method `pictures' for #<Post::ActiveRecord_Relation:0x007fc960f698a8
What am I doing wrong here? How could I fix this?
Also, I know it seems like it would be easier to just use the Pictures#index page, but I need this to work for other reasons.
Thanks!!
#posts is a relation (array-like object), and it doesn't have a method named pictures.
I'm not sure why you are paginating the pictures attached to each post on an index view, but you might be doing a carousel or something, so it may be valid, but if that's the case, you'll need to paginate in the view (or build a more complex data structure in your controller):
<% #posts.each do |post| %>
<% post.pictures.paginate(:per_page => 5, page => params["post_#{post.id}_pictures_page"]).each do |picture| %>
<li>
<%= link_to image_tag(picture.image.url(:thumb)), picture_path(picture)%>
</li>
<% end %>
<% end %>
I have a new problem, I Create a web where I upload many images, using nested attributes and polymorphic table, in my index.html I want to show only one image, but I can't find how. But I'm new in rails.
photography.rb
class Photography < ActiveRecord::Base
validates :title, :description, presence: true
belongs_to :user
has_many :images, as: :imageable, dependent: :destroy
accepts_nested_attributes_for :images, :reject_if => lambda { |a| a[:img_str].blank? }, :allow_destroy => true
end
image.rb
class Image < ActiveRecord::Base
belongs_to :imageable, polymorphic: true
mount_uploader :img_str, AssetUploader
end
index.html.erb
<% for photo in #photo %>
<%= link_to photo.title, photography_path(photo) %>
<% photo.images.each do |images| %>
<%= images.img_str %>
<% end %>
<% end %>
With the for method I show all the image, try add .first, but says undefined method first for 5:Fixnum. I think that I have to create a helper method, but I not sure. Can anyone help me?. Thanks
Try:
<% for photo in #photo %>
<%= link_to photo.title, photography_path(photo) %>
<%= photo.images.first.img_str if photo.images.any? %>
<% end %>
Also, for is very rarely used in ruby, instead do:
<% #photos.each do |photo| %>
I want a user to create an event, on this event they need to be able to create a dynamic form form for the user to 'register' for the event (creating fields on the fly). Data which is then submitted to this form will get stored in the database. I've used most of this tutorial to get me to where I am now.
My structure looks like this so far:
class Admin::Event < ActiveRecord::Base
attr_accessible :body, :date, :title, :end_date, :status, :event_location_id, :payment, :fields_attributes, :answers
belongs_to :event_location
has_many :fields, class_name: "Admin::EventField"
has_many :event_surveys
accepts_nested_attributes_for :fields, allow_destroy: true
end
I have been able to successfully dynamically create fields against an event which get stored in admin_event_fields table. Done exactly how the Rails Cast tutorial does it.
class Admin::EventField < ActiveRecord::Base
belongs_to :event
attr_accessible :field_type, :name, :required
end
I then have this model, my idea is to store the answers in a hash; so when one user submits a 'survey' it gets saved against the admin_event_id then the answers get stored in the answers hash. This should repeat when another user submits an answer... My Issue is, saving the answers to Admin::EventSurvey... I can't figure out a logical way to do this.
class Admin::EventSurvey < ActiveRecord::Base
attr_accessible :admin_event_id, :answers
has_one :admin_event, :class_name => "Admin::Event"
accepts_nested_attributes_for :admin_event, :allow_destroy => true
serialize :answers, Hash
end
Because the form is going to be submitable from the show action, i've put a form in show.html.erb.
Admin::EventsController:
# Admin::EventsController
# GET /events/1
# GET /events/1.json
def show
#event = Admin::Event.find(params[:id])
#event_survey_answer = Admin::EventSurvey.new(admin_event_id: params[:id])
respond_to do |format|
format.html # show.html.erb
format.json { render json: #event }
end
end
Show.html.erb:
<%= form_for #event_survey_answer do |f| %>
<% if #event_survey_answer.errors.any? %>
<div id="error_explanation">
<h2><%= pluralize(#event_survey_answer.errors.count, "error") %> prohibited this event_survey_answer from being saved:</h2>
<ul>
<% #event_survey_answer.errors.full_messages.each do |msg| %>
<li><%= msg %></li>
<% end %>
</ul>
</div>
<% end %>
<% f.fields_for :admin_event do %>
<%= #event.fields.each do |field| %>
<%= render "admin/events/fields/#{field.field_type}", locals: { field: field, f: builder } %>
<% end %>
<% end %>
<%= f.submit %>
<% end %>
At the moment with the above error I'm getting this error because of the loop on #event.fields:
undefined local variable or method `builder'