In my Rails app, I have Steps and Questions. A user can generate a question for any step.
Step.rb
class Step < ActiveRecord::Base
has_one :question, :dependent => :destroy
accepts_nested_attributes_for :question, :allow_destroy => :true
end
Question.rb
class Question < ActiveRecord::Base
belongs_to :step
end
I used a nested form to generate a question:
<%= semantic_form_for [#project,#step] do |f| %>
<%= f.fields_for :question do |question_f| %>
<%= question_f.text_area :description %>
<% end %>
<% end %>
First, I only want to create a new question if the user actually enters text into the question text area. How can I prevent the step form from automatically saving an empty question? Here is my current step controller:
class StepsController < ApplicationController
def new
#step = #project.steps.build(:parent_id=> params[:parent_id])
#step.build_question
...
end
def create
#step = #project.steps.build(params[:step])
respond_to do |format|
if #step.save
...
end
end
Second, I want to run some ruby code when a new question is created. In particular, I want to update the updated_at date for the project that contains the question. Where would I put this in my controller? I tried creating a controller for the Question model and creating create and new methods, but they weren't called when the step form was submitted.
Your first issue can be solved by modifying the nested attributes line in your Step class to reject a submitted question if its attributes are all blank, as follows. See the accepts_nested_attributes_for documentation under :reject_if.
accepts_nested_attributes_for :question, :reject_if => :all_blank, :allow_destroy => true
Your second issue could be solved in a number of ways. If you always want to update the updated_at for a project (also called "touching" it) whenever a question is added to it, you could add a callback to your Question class that touches the project. See this guide on callbacks.
class Question < ActiveRecord::Base
belongs_to :step
has_one :project, :through => :step
after_create :touch_project
private
def touch_project
project.touch
end
end
On the other hand, if you only want to touch a question's project in the context of a specific controller action, you can just check whether a question was created and touch the project if so:
def create
#step = #project.steps.build(params[:step])
if #step.save
if #step.question.present?
#project.touch
end
# do other stuff for successful save
else
# handle failed save
end
end
Also, for future reference, if you have two largely unrelated questions it's better to ask them separately. Combining questions makes it more difficult for others with the same problem to find an answer.
Related
Ok so I'm working on a quiz as part of my app. I have three nested models Quiz, Question, Answer.
class Quiz < ActiveRecord::Base
belongs_to :video
has_many :questions
has_many :answers, through: :questions
accepts_nested_attributes_for :questions,
:reject_if => lambda { |a| a[:content].blank? }, allow_destroy: true
end
class Question < ActiveRecord::Base
belongs_to :quiz
has_many :answers
accepts_nested_attributes_for :answers,
:reject_if => lambda { |a| a[:content].blank? }, allow_destroy: true
end
class Answer < ActiveRecord::Base
belongs_to :question
end
In the controller action quizzes#show I am able to retrieve all the questions for a particular quiz with
def show
#quiz = Quiz.find(params[:id])
#question = Question.where(quiz_id: #quiz)
end
I can show each question in quizzes/show.html.haml view with
- #question.each do |question|
= question.content
The problem is that the above code will show all the questions on the same page one below the other.
What I really want is to show each question individually in its own view, one question per page. So that once the user answers one question, they click on a button and it will re-render the view with the next question.
Anyway I'm having a hell of a time trying to figure out how to do this. Thank you in Advance! Oh and let me know if you need to see any more of my code!!
First of all: you've set up an association (has_many :questions), so you can just use #quiz.questions instead of a manual search (Question.where...).
You just need to specify a sort order and then fetch a single record based on a minimum-value. Let's say you choose the id (created_at or any custom field should work in a similar way).
def show
#min_id = params[:min_id]
#quiz = Quiz.find(params[:id])
#questions = #quiz.questions.order("id ASC")
if #min_id
#question = #questions.where("id > ?", #min_id).first
else
#question = #questions.first
end
end
You can then just add the current questions id the the link for the next question. Probably something like this (erb instead of haml, but you get the idea ;) ):
<%= #question.content %>
<%= link_to "Next question", quiz_path(#quiz, :min_id => #question.id) %>
What i want to do -
I've got 2 models Record and Author. when calling Record.create params i whant to pass params for associated Author model.
Record has column body and Author has column name
When i try to pass as follows
Record.create { body: "some text", author: { name: 'Some name'}}
i get error ActiveRecord::UnknownAttributeError: unknown attribute: author
How can i do what i need ?
UPDATE 1
association - Record has author
Nested Attributes
You'll probably be looking for accepts_nested_attributes_for, or inverse_of - both relying on an association between your two models:
#app/models/record.rb
Class Record < ActiveRecord::Base
has_one :author
accepts_nested_attributes_for :author
end
#app/models/author.rb
Class Author < ActiveRecord::Base
belongs_to :record
end
Essentially, you'll need to build the associative data, allowing you to send the associated attributes through to your other model. I'll explain this further down the page
This is what I would do if I were you:
#app/controllers/records_controller.rb
Class RecordsController < ApplicationController
def new
#record = Record.new
#record.author.build
end
def create
#record = Record.new record_params
#record.save
end
private
def record_params
params.require(:record).permit(:record, :attributes, author_attributes: [:name])
end
end
#app/views/records/new.html.erb
<%= form_for #record do |f| %>
<%= f.text_field :record %>
<%= f.fields_for :author do |a| %>
<%= a.text_field :name %>
<% end %>
<%= f.submit %>
<% end %>
This will allow you to save the author params / attributes upon save
--
inverse
Inverse attributes are also another idea for you.
I'm not sure whether they'll work directly in this instance, but you could use the following:
#app/models/record.rb
Class Record < ActiveRecord::Base
has_one :author, inverse_of: :author
before_create :build_record
end
#app/models/author.rb
Class Author < ActiveRecord::Base
belongs_to :record, inverse_of: :record
before_create :set_options
private
def set_options
self.draft = true unless self.record.draft.present?
end
end
This means you should be able to access the nested attribute data (I'm not sure whether you have to use accepts_nested_attributes_for still in this instance) in your other model
ActiveRecord Objects
Finally, you need to consider the role of ActiveRecord objects in this setup
Please remember you're not just passing single items of data here - you're constructing & passing objects. This means you have to consider how they work & what they mean. I'll give you a brief explanation:
Rails, because its built on Ruby, is an object-orientated framework. This means that every piece of data you create / use in this is an object. Objects are much different than variables - they are deeper & have much more data contained within them, allowing them to be used in a variety of different ways:
Rails makes use of objects in many different ways; the main one being that a lot of the helpers & other methods build themselves around the objects. That's why you get the resources directive in your routes, and can do the following: <%= link_to #user.name, #user %>
The problem many people have is they don't understand the value of object-orientation in a Rails app, and consequently try and think about their logic from the perspective of a disjointed system. Conversely, and this will help you tremendously, you need to consider that every time you create a record, you're building an object, and consequently, you need to ensure you build your app around them.
As noted, you have to ensure you have an association between the objects you wish to create. If you do that, you'll be able to build them both at the same time
Try this hopefully will solve your problem:
class Record < ActiveRecord::Base
has_one :author
accepts_nested_attributes_for :author, :reject_if => lambda { |a| a[:name].blank? }, :allow_destroy => true
end
And for more details see accepts_nested_attributes_for
I have a new form that creates an Item (all the codes are obviously simplified):
<%= simple_form_for #item do |f| %>
<%= f.input :brand_name %>
<%= f.button :submit %>
<% end %>
The current user will create an item and link it to a new or to an existing brand.
This field doesn't exist in the database; it'll be used as a way to associate all models. Hence, I create its getter and setter.
def Item < ActiveRecord::Base
belongs_to :user
belongs_to :brand
attr_accessible :brand_name
def brand_name
brand.try :name
end
def brand_name=(name)
if name.present?
brand = user.brands.find_or_initialize_by_name(name)
brand if brand.save
end
end
end
class ItemsController < ApplicationController
def new
#item = current_user.items.build
end
def create
#item = current_user.items.build(params[:item])
if #item.save
...
end
end
end
The problem is that when the form is submitted, I get this error, which lies in the product_name=() method. I've done some debugging through Rails' console and it goes all fine, but in the browser the setter method is called before the create action. That is, the record doesn't even have a user associated to it. I tried leaving the create method empty, for example, but nothing different happens.
undefined method `brands' for nil:NilClass
What is really weird is that this was working a couple of weeks ago (I've checked my git commits and the code is identical).
I though about calling the before_create callback, but there's no way to know which user should be linked.
UPDATE
I'm using Sorcery as the authentication handler. Everything always works fine, except for this create action.
class User < ActiveRecord::Base
authenticates_with_sorcery!
belongs_to :company
has_many :items
end
class Company < ActiveRecord::Base
has_many :users, dependent: :destroy
has_many :brands, dependent: :destroy
end
I have a question on rails3 nested_form.
These are my two models:
class User
belongs_to :shop
accepts_nested_attributes_for :shop
end
class Shop
has_many :users
end
In my register view(i am using Devise):
form_for(resourse,:url => registration(resource_name)) do |f|
=f.fields_for :shop do |s|
=s.text_fields :name
but i get nothing for this form. What should i do?
You need to add some objects first to it. Use build method on model in controller.
Example:
#shop = Shop.new
3.times { #shop.users.build }
More informations at Railscasts. AJAX is used in second part of this video.
I've upgraded to Rails 2.3.3 (from 2.1.x) and I'm trying to figure out the accepts_nested_attributes_for method. I can use the method to update existing nested objects, but I can't use it to create new nested objects. Given the contrived example:
class Product < ActiveRecord::Base
has_many :notes
accepts_nested_attributes_for :notes
end
class Note < ActiveRecord::Base
belongs_to :product
validates_presence_of :product_id, :body
end
If I try to create a new Product, with a nested Note, as follows:
params = {:name => 'Test', :notes_attributes => {'0' => {'body' => 'Body'}}}
p = Product.new(params)
p.save!
It fails validations with the message:
ActiveRecord::RecordInvalid: Validation failed: Notes product can't be blank
I understand why this is happening -- it's because of the validates_presence_of :product_id on the Note class, and because at the time of saving the new record, the Product object doesn't have an id. However, I don't want to remove this validation; I think it would be incorrect to remove it.
I could also solve the problem by manually creating the Product first, and then adding the Note, but that defeats the simplicity of accepts_nested_attributes_for.
Is there a standard Rails way of creating nested objects on new records?
This is a common, circular dependency issue. There is an existing LightHouse ticket which is worth checking out.
I expect this to be much improved in Rails 3, but in the meantime you'll have to do a workaround. One solution is to set up a virtual attribute which you set when nesting to make the validation conditional.
class Note < ActiveRecord::Base
belongs_to :product
validates_presence_of :product_id, :unless => :nested
attr_accessor :nested
end
And then you would set this attribute as a hidden field in your form.
<%= note_form.hidden_field :nested %>
That should be enough to have the nested attribute set when creating a note through the nested form. Untested.
check this document if you use Rails3.
http://api.rubyonrails.org/classes/ActiveRecord/NestedAttributes/ClassMethods.html#label-Validating+the+presence+of+a+parent+model
Ryan's solution is actually really cool.
I went and made my controller fatter so that this nesting wouldn't have to appear in the view. Mostly because my view is sometimes json, so I want to be able to get away with as little as possible in there.
class Product < ActiveRecord::Base
has_many :notes
accepts_nested_attributes_for :note
end
class Note < ActiveRecord::Base
belongs_to :product
validates_presence_of :product_id unless :nested
attr_accessor :nested
end
class ProductController < ApplicationController
def create
if params[:product][:note_attributes]
params[:product][:note_attributes].each { |attribute|
attribute.merge!({:nested => true})
}
end
# all the regular create stuff here
end
end
Best solution yet is to use parental_control plugin: http://github.com/h-lame/parental_control