validation and accepts_nested_attributes_for - ruby-on-rails

i have two models :
class Article < ActiveRecord::Base
belongs_to :league
has_many :photos, :dependent => :destroy
attr_accessible :content, :lead, :title, :title_slug,
:created_at, :updated_at,
:league_id, :photos_attributes
accepts_nested_attributes_for :photos
validates :content, :league, :presence => true
validates :lead , :length => {maximum: 1000}, :presence => true
validates :title ,:length => {maximum: 200}, :presence => true
validates_associated :photos
and
class Photo < ActiveRecord::Base
belongs_to :article
attr_accessible :photo
validates :photo, presence: true
has_attached_file :photo , :styles => { :medium => '440x312#', :small => '209x105!'}
end
My ArticlesController is
...
def new
#article = Article.new
#article.photos.build
end
def create
#article = Article.new(params[:article])
if #article.save
redirect_to([:admin,#article])
else
render 'new'
end
end
...
form view is :
= form_for([:admin,#article] , :html => {:multipart => true}) do |f|
- if #article.errors.any?
= render 'errors'
= f.fields_for :photos do |builder|
= builder.label :photo
= builder.file_field :photo
...
i have some question about it :
1) I dont want to save an article without empty photo but now when i dont choose a file my article saves.
2) When i have some errors on article's fields and render 'new' ,my photo field dissapear , what is the rails way to resolve it.
3) in the future i want to add another model: photo_type and assosciate it with photo. Each article will have two photo fields , each with own type (for example: small , big) . I wonder how to render that fields and what can i do to save article with two photos with different types.

Answer for 1: Use validates_associated :photos. Documentation
Answer for 2: I guess that is an file attachment field. For that, this is generally done by setting up a hidden cache field and by using some callbacks. MountUploader uses the same principle.
Answer for 3: Little skeptical, but I guess something will work along this way:
In your Article model, have two associations with Photo as:
has_one :small_photo, :class_name => "Photo"
has_one :big_photo, :class_name => "Photo"
This will enable you to have two sub-form fields present for both types while opening up the form for Article.
Hope it helps. Do comment if last one can work for you in this way. It looks like the good deal to me :)

Related

Rails - activeadmin, duplicating has_many records upon updating "parent" record

My models are as follows:
class Project < ActiveRecord::Base
has_many :project_images
accepts_nested_attributes_for :project_images
end
class ProjectImage < ActiveRecord::Base
belongs_to :project
mount_uploader :image, ImageUploader
end
Here's the activeadmin file:
ActiveAdmin.register Project do
remove_filter :projects_sectors
permit_params :title, :info, :case_study, project_images_attributes: [:image, :cover]
index do
column :title
actions
end
form :html => { :enctype => "multipart/form-data" } do |f|
f.inputs "Project" do
f.input :title
f.input :info
f.input :case_study, :as => :file
end
f.inputs "Images" do
f.has_many :project_images, :allow_destroy => true, :heading => false, :new_record => true do |img_f|
img_f.input :image, :as => :file , :hint => f.template.image_tag(img_f.object.image)
img_f.input :cover
end
end
f.actions
end
end
The problem is that when i simply edit a project and click on update project, it simply duplicates all the records that exist for relationship at that point. Eg. if i have 2 images under 1 project, after changing say, the project title, i will end up with 4 images.
Hope it's clear what the issue is. Would appreciate greatly if anybody could give me a litle help.
Thanks a lot in advance.
You have to permit the id of the images: project_images_attributes: [:id, :image, :cover]
If you don't permit the id, it will be null in the action, and rails thinks it's a new record and save it.
I think this is the same problem as this one discussed on the CarrierWave wiki. Instead of generating input fields for existing images, generate an image tag and a 'remove?' option. If you generate an input field for them, then you'll end up with duplicate results.
ActiveAdmin.register Project do
controller do
def apply_filtering(chain)
super(chain).distinct
end
end
# your code
end

Active Admin Rails 4 has many

I am working with Rails 4, Active Admin and Paperclip to setup a has_many images association. When generating my has_many portion of the form I keep getting errors. Currently I am getting
undefined method `+' for nil:NilClass. Here is my code:
news model
class News < ActiveRecord::Base
validates :body, presence: true
validates :title, presence: true, length: { maximum: 140 }
has_many :news_images, dependent: :destroy
end
News image model
class NewsImage < ActiveRecord::Base
belongs_to :news
has_attached_file :photo, styles: {
small: "150x150>",
medium: "300x300>",
large: "600x600>"
}
validates_attachment_presence :photo
validates_attachment_size :photo, less_than: 5.megabytes
end
admin code
ActiveAdmin.register News do
index do
column :title
default_actions
end
form multipart: true do |f|
f.semantic_errors *f.object.errors.keys
f.inputs "News Details" do
f.input :title
f.input :body, :as => :rich
end
f.has_many :news_images do |p|
end
f.actions
end
controller do
def permitted_params
params.permit news: [:title, :body, news_images: [:photo]]
end
end
end
Ideally I would like the user to be able to upload multiple images to the form. Any one have experience with this issue?
The stack trace is saying insert_tag renderer_for(:new) which is being tripped on f.has_many :news_images do |p|
So the problem was with the news model. I thought accepts_nested_attributes_for was deprecated with the addition of strong params but I guess I was wrong adding this to the news model fixed my issue
accepts_nested_attributes_for :news_images,
:reject_if => lambda { |attributes| attributes[:photo].blank? },
:allow_destroy => true
There was another bug in Paperclip 4.1 that was fixed recently: https://github.com/thoughtbot/paperclip/issues/1457
I spent a ton of time tracking this down, but finally was able to find the connection between formtastic and paperclip 4.1.
The solution that worked for me was to switch to paperclip's master branch in my Gemfile as follows:
gem 'paperclip', github: 'thoughtbot/paperclip'

Rails 3 association error: undefined method

I've been puzzling over this for quite some time now and can't figure it out.
I've got 2 models:
class Vehicle < ActiveRecord::Base
attr_accessible :year, :capacity,
:size, :body, :model_id, :maker_id, :parameters_attributes
validates :year, numericality: { greater_than: 1900 }
validates :year, :capacity, :size, :body, presence: true
belongs_to :model
belongs_to :maker
has_many :parameters
accepts_nested_attributes_for :parameters
end
and
class Parameter < ActiveRecord::Base
attr_accessible :tag, :value
validates :tag, :value, presence: true
belongs_to :vehicle
end
in new vehicle view i've got:
= form_for [:admin, #vehicle], html: { multipart: true } do |f|
=# some other stuff in between
= f.text_field :value, size: 4
I get this error
undefined method `value'
Just can't seem to get it working. Help, anyone?
EDIT
routes.rb
resources :vehicles
resources :parameters
resources :makers do
resources :models
end
If you are using nested form, you should have something like
f.fields_for :parameters do |parameter|
and than:
parameter.text_field :value, size: 4
Also, remember to create the some parameters in the controller, for example:
def new
#vehicle = Vehicle.new
2.times { #vehicle.parameters.build } #it will create 2 parameters
...
end
f refers to #vehicle, it seems only Parameter bears this field. That's why it fails.
Sidenotes:
In Vehicle you have accepts_nested_attributes_for :parameters but you don't have parameters_attributes in the attr_accessible, can't be good.
If you want to call the relationship in the form consider using fields_for
Ok, I've made a mess of things.
Firstly I've been trying to
def new
#vehicle = #vehicle.parameters.build
end
hence the error undefined method. After a while I got to the correct syntax, which is the one gabrielhilal added after a while.
def new
#vehicle = Vehicle.new
#vehicle.parameters.build
end
No matter ;) Still had problems, because after clicking "create" he wouldn't add records in the database. Turned out that I've set the validates presence: true for tag, but didn't assign any value to it. After fixing that, it worked like a charm. Thanks a lot for all the help.
On to the next puzzle.

rails fields_for parent id not being set on child model

I have been working from the rails api documents for NestedAttributes and FormHelper and searching stackoverflow.
I have the following code in my new.html.haml view:
=form_for listing, :html => {:id => :listing_form, :multipart => :true} do |f|
=f.fields_for :main_picture, (listing.main_picture || listing.build_main_picture) do |fmp|
=fmp.hidden_field :main, :value => 1
=fmp.file_field :image, :class => :picture_select
And the following code in my controller:
def create
#listing = Listing.new(params[:listing])
#listing.save ? redirect_to(:root) : render('listings/new')
end
Here is my listing.rb:
class Listing < ActiveRecord::Base
has_one :main_picture, :class_name => "Picture", :conditions => {:main => true}
attr_accessible :main_picture_attributes
accepts_nested_attributes_for :main_picture, :allow_destroy => true
end
And my picture.rb:
class Picture < ActiveRecord::Base
belongs_to :listing
validates_presence_of :listing
attr_accessible :listing, :main
end
And I get the following error message when I try and submit my form:
main_picture.listing: can't be blank
I can't work out why the framework is not automatically setting the listing_id field of the main_picture (object Picture) to id value of parent Listing object.
Is there something I am doing wrong?
Do you need the validates_presence_of :listing? I suspect that the child record is getting created before the parent object, and so it doesn't have an ID yet.
Removing that line and adding :dependent => :destroy to your has_one :main_picture would ensure you don't end up with orphan picture records.
Alternatively, rewrite your controller:
p = params[:listing]
#listing = Listing.new(p)
#picture = Picture.new(p.delete(:main_picture).merge({:listing => #listing})
etc.

Application design of controller (Rails 3)

This is hard to explain, but I will do my best:
I am building a system where user's can take courses. Courses are made up of steps, that must be taken in order. In the system there are 6 step types (Download, Presentation, Video, Text, Quiz, and Survey)
The way a user accesses a STEP currently is:
http://example.com/courses/2/course_steps/1
As you can tell course_steps are nested under courses.
Below is the show method in course steps:
def show
render "show_#{#course_step.step.step_type.name.downcase}"
end
As you can tell it basically picks a view to show_[TYPE] (quiz, survey, text..etc)
This works fine for simple steps such as a text, video, or download, but for complicated steps such as a quiz, this model does not work well for the following reasons:
How do I validate a form for a quiz or survey as I would be using a different controller (QuizAttemptsController).
It seems to break the REST principal as a quiz, survey..etc should be treated separately. (I know they are step types, but they can have their own actions and validations)
Step Model
class Step < ActiveRecord::Base
belongs_to :step_type
belongs_to :client
has_one :step_quiz, :dependent => :destroy
has_one :step_survey, :dependent => :destroy
has_one :step_text, :dependent => :destroy
has_one :step_download, :dependent => :destroy
has_one :step_video, :dependent => :destroy
has_one :step_presentation, :dependent => :destroy
has_many :course_steps, :dependent => :destroy
has_many :courses, :through => :course_steps
has_many :patient_course_steps, :dependent => :destroy
attr_accessible :step_type_id, :client_id, :title, :subtitle, :summary
validates :title, :presence=>true
validates :summary, :presence=>true
def getSpecificStepObject()
case self.step_type.name.downcase
when "text"
return StepText.find_by_step_id(self.id)
when "quiz"
return StepQuiz.find_by_step_id(self.id)
when "survey"
return StepSurvey.find_by_step_id(self.id)
when "download"
return StepDownload.find_by_step_id(self.id)
when "video"
return StepVideo.find_by_step_id(self.id)
when "presentation"
return StepPresentation.find_by_step_id(self.id)
end
end
end
Step Quiz Model:
class StepQuiz < ActiveRecord::Base
belongs_to :step, :dependent => :destroy
has_many :step_quiz_questions, :dependent => :destroy
has_many :quiz_attempts, :dependent => :destroy
accepts_nested_attributes_for :step
accepts_nested_attributes_for :step_quiz_questions, :allow_destroy => true
attr_accessible :step_id, :instructions, :step_attributes, :step_quiz_questions_attributes
validates :instructions, :presence=>true
end
CourseStep Model
class CourseStep < ActiveRecord::Base
belongs_to :step
belongs_to :course
validates_uniqueness_of :step_id, :scope => :course_id
def next_step()
Course.find(self.course.id).course_steps.order(:position).where("position >= ?", self.position).limit(1).offset(1).first
end
def previous_step()
Course.find(self.course.id).course_steps.order("position DESC").where("position <= ?", self.position).limit(1).offset(1).first
end
end
How would you suggest fixing this?
What you want to do is implement your Model as a Finite State Machine and continually reload the new or edit action until the desired state is reached, then your controller can display different views depending on state to allow multiple steps to happen.
One way I have solved the problem is by adding a member action of "submit_quiz"to the course_steps controller. I am not sure if I like this, as the code looks kind of ugly. I would appreciate feedback.(Note: I Am using CanCan so #course_step is created automagically in the course_steps_controller)
The things I don't like are:
show_quiz view has a lot of code in it
submit_quiz is in the course_steps_controller
quiz_attempt model has virtual attribute of quiz_questions (for validation purposes)
show_quiz.html.erb
<%= form_for (#quiz_attempt.blank? ? QuizAttempt.new(:started => Time.now.utc, :step_quiz_id => #course_step.step.step_quiz.id) : #quiz_attempt, :url => submit_quiz_course_course_step_path(#course_step.course, #course_step)) do |f| %>
<%= render :partial => 'shared/error_messages', :object => f.object %>
<% #course_step.step.step_quiz.step_quiz_questions.each do |quiz_question| %>
<h3><%= quiz_question.value %></h3>
<% quiz_question.step_quiz_question_choices.each do |quiz_question_choice| %>
<%= radio_button_tag("quiz_attempt[quiz_questions][#{quiz_question.id}]", quiz_question_choice.value, f.object.get_quiz_question_choice(quiz_question.id) == quiz_question_choice.value)%>
<%= quiz_question_choice.value %><br />
<% end %>
<% end %>
<%= f.hidden_field(:step_quiz_id)%>
<%= f.hidden_field(:started)%>
<%= submit_tag("Submit Quiz")%>
<% end %>
course_steps_controller.rb
def show
PatientCourseStep.viewed(current_user.id, params[:course_id], #course_step.step.id )
render "show_#{#course_step.step.step_type.name.downcase}"
end
def submit_quiz
#quiz_attempt = QuizAttempt.new(params[:quiz_attempt])
if !#quiz_attempt.save()
render 'show_quiz'
end
end
quiz_attempt.rb
class QuizAttempt < ActiveRecord::Base
belongs_to :step_quiz
belongs_to :patient
attr_accessor :quiz_questions
attr_accessible :step_quiz_id, :patient_id, :started, :ended, :correct, :incorrect, :quiz_questions
validate :answered_all_questions?
def get_quiz_question_choice(quiz_question_id)
unless self.quiz_questions.blank?
quiz_questions[quiz_question_id.to_s]
end
end
private
def answered_all_questions?
#Making sure they answered all the questions
if self.quiz_questions.blank? or self.quiz_questions.try(:keys).try(:count) != self.step_quiz.step_quiz_questions.count
errors.add_to_base "Not all questions were answered"
end
end
end
def submit_quiz
#quiz_attempt = QuizAttempt.new(params[:quiz_attempt])
if !#quiz_attempt.save()
render 'show_quiz'
end
end

Resources