I'm trying to set up a single table inheritance for Questions table. I've followed some advices adding a route this way :
resources :vfquestions, :controller => 'questions', :type =>
'Vfquestion'
And the model :
class Vfquestion < Question
end
It works, saving the question in the database, but the type field stays empty.
Here is my controller :
class QuestionsController < ApplicationController
before_action :authenticate_user!
def index
#user = current_user
#category = Category.find(params[:category_id])
#questions = #user.questions.where(:type => params[:type])
end
def new
#category = Category.find(params[:category_id])
#question = #category.questions.new
#question.type = params[:type]
end
def show
#user = current_user
#category = #user.categories.find(params[:category_id])
#question = #category.questions.find(params[:id])
end
def create
#user = current_user
#category = Category.find(params[:category_id])
#question = #category.questions.new(question_params)
#question.user_id = current_user.id
#question.save
end
private
def question_params
params.require(:question).permit(:title, :body)
end
end
Am I missing something to save this param ?
As far as I know, type is not saved for the base class. It also can't be overridden via params, as that would mean X.new would potentially yield an instance of a class other than X.
What you need to do is create the correct type on the way in:
#question =
case (params[:question][:type])
when 'Vfquestion'
Vfquestion.new(params[:question])
else
Question.new(params[:question])
end
#category.questions << #question
The relationship is also defined in terms of a singular base class, so all objects built in that scope will default to the base class.
I'm guessing in your Category model you have a line:
has_many :questions
The problem is that this relationship is pointing to the parent Question model; it has no idea you want to create or find any of its subtypes (Remember that Rails operates on convention over configuration; in this case, rails is locating your Question model because of the convention for naming has_many relationships).
One way to solve this is add the appropriate subtypes like so:
has_many :vfquestions
has_many :some_other_question_subtype
And then to create, for example, a new VFQuestion for a particular category, you would simply do:
#question = #category.vfquestions.new(question_params)
Side Note
Part of the problem in your situation is, in your create method, you have no way of distinguishing between a VFquestion, or some other question sub type when you go to create it. You'll have to figure out the best way to handle this for your particular domain, but possibly the simplest way to handle this is to pass a type parameter from the form. So, for example, if you have some kind of radio button that flops between the different question types, make sure it is named appropriately to it is sent when the form is submitted. Then simply check that piece of data in the params and either invoke .vfquestions, or some other question sub type.
Related
I'm trying to create and order that is associated with an item.
An Order has one item:
class Order < ActiveRecord::Base
has_one :item
end
An Item belongs to an order:
class Item < ActiveRecord::Base
belongs_to :user
end
According to the guide this should work:
build_association(attributes = {})
create_association(attributes = {})
I have this in my controller:
def create
#order = #current_item.build_order(order_params)
#order.save
redirect_to #order
end
And this is the error I'm getting:
undefined method `build_order' for nil:NilClass
I know this has to do with how I've defined current_items but I've tried many different things and all lead to this same error message.
I have this in my application helper:
def current_item
Item.find(params[:id])
end
Can anyone point me in a better direction for how to define this or what I'm doing wrong here. Thanks for your help!
1) You don't have access to a helper method from the controller. You can include the helper class in your controller but it's a really bad practice. You must use helper methods only in the views.
2) You can move current_item method from the helper to the controller. Then there will be another problem. In your create method, you are trying to access instance variable #current_item which is not initialized at the moment, not the method. You can do it this way:
#order = #current_item.build_order(order_params)
to
#order = current_item.build_order(order_params)
Then current_item will return you Item object.
3) I am not sure what are your params, but you can implement it this way:
def create
#order = Order.new(params[:order])
#order.save
redirect_to #order
end
where params[:order] is for example:
{name: "order 1", item_id: 1}
You should change your create to use a method, rather a variable, so modify it as follows:
def create
#order = current_item.build_order(order_params)
#order.save
redirect_to #order
end
# rest of code
def current_item
Item.find(params[:id])
end
This should help.
Good luck!
The error you're getting is being caused by trying to run Item.find(params[:id]) but not passing it a valid value. It seems that params[:id] is maybe nil? Can you confirm this using a debugger or by temporarily adding raise "Params[:id] is set to #{params[:id]} to the first line of the method, running the code and seeing what it says in the terminal output?
All you need to do make this work is have a parameter value for the item come from the form that is being submitted. Normally rails uses the route/url to populate the value of params[:id]. For example, when the request is GET /items/1, params[:id] is 1.
In this case though, unless you've done some custom routing that you haven't shown in your question, creating a new order would usually be a POST to /orders and since there is no id in the url, params[:id] is nil.
It's up to you to add the item id from the order form. It would make sense that it would be sent with the rest of the order params as item_id, rather than just id, since id is usually used to reference the current object, which is a new order and therefore doesn't get have an id.
You'll need to make sure that item_id is whitelisted in your strong params with the rest of the values in the order_params method (I assume you defined this in the same controller but did not show it in the code), and then the code would look something like this.
def create
#order = current_item.build_order(order_params)
#order.save
redirect_to #order
end
#note the changes the the argument
def current_item
Item.find(order_params[:item_id])
end
def order_params
params.require(:order).permit(:item_id, :other_values_that_you_send)
end
I'm using Rails 3.2.19 and Ruby 2.1.2. I've been googling around trying to figure this out, but perhaps I'm not searching for the right thing. Anyway, I'll try and be as concise as possible.
I have a few different models that all have a name attribute. In my views I want to somehow be able to access that name attribute regardless of the instance name passed into the view. Currently my various controllers create instances of their respective models. For instance:
class PagesController < ApplicationController
def show
#page = Page.find(params[:id])
respond_to do |format|
format.html
end
end
end
-
class ProductsController < ApplicationController
def show
#product = Product.find(params[:id])
respond_to do |format|
format.html
end
end
end
While I understand I could simply re-name the instances something generic, I was wondering if there was some way of accessing any/all instances while maintaining unambiguous instance names.
Basically something like this:
page.html.haml
%h1= resources[0].name #equates to #page.name
%h2= #page.some_other_attribute
or
product.html.haml
%h1= resources[0].name #equates to #product.name
%h2= #product.price
Where in each of the above resources[0] would be either #page or #product
You will have to define a route with an additional resource_type parameter to a generic controller or otherwise just include the resource_type into the url query parameter
/resources/product/17
or
/resources/17?resource_type=product
This will allow you to do the following in the controller
class ResourcesController < ApplicationController
def show
#resource = find_resource(params)
respond_to do |format|
format.html
end
end
private
def find_resource(params)
resource_klass = {
product: Product,
page: Page
}[params[:resource_type]]
resource_klass.find(params[:id])
end
end
Another Option would be to introduce another ResourceType Entity and define a polymorphic :has_one :belongs_to association to the actual resource entity (product, page). Then always search for ResourceTypes and load the polymorphic resource entity
class ResourceType < ActiveRecord::Base
belongs_to :resource, polymorphic: true
end
class Product < ActiveRecord::Base
has_one :resource_type, as: :resource
end
class Page < ActiveRecord::Base
has_one :resource_type, as: :resource
end
product_resource_type = ResourceType.create(...)
product = Product.create(resource_type: product_resource_type)
page_resource_type = ResourceType.create(...)
page = Page.create(resource_type: page_resource_type)
ResourceType.find(product_resource_type.id).resource
=> product
ResourceType.find(page_resource_type.id).resource
=> page
I figured this out after discovering instance_variables and instance_variables_get
Those methods will return all instance variables being passed into the view. From there I discovered that the :#_assigns instance variable contained the instances that I was looking for. So I iterated over them to find if any had the name attribute.
- instance_variable_get(:#_assigns).each do |var|
- if var[1].respond_to?("name")
%h1= var[1].name
There is probably a better way of accomplishing this, so if anyone has any opinions, they are welcome.
I currently have two models School and Course where School has_many courses, and Course belongs_to school. Additionally, School and Course are nested resources, where School is the parent resource, and Course the child.
I have created several test records in the Rails Console so that a query such as when the child calls upon the parent Course.first.school successfully executes and returns all the relevant information of the school Course.first is associated with.
However, when put into a controller function, I would instead get an error "undefined method `school' for nil:NilClass" for the following line:
redirect_to school_course_path(#course.school, #course)
.. as if the .school part wasn't recognized (where as it was in the console). Why is this the case, and how do I get past this error? Thanks!
Edit - as suggested, it could be that my #course instance variable isn't passed from method to method in the controller. I have attempted at passing them through via a private method, but its still giving me the same error. Here is my code (background: the model Question belongs_to Course, with Course having many questions. Course isn't part of the nested routes)
class QuestionsController < ApplicationController
def new
#course = Course.find(params[:course]) #confirmed working
self.current_course = #course #I attempt to set current_course, a private method
#question = Question.new
end
def create
#question = Question.new(params[:question]) #also works, in rails console all the questions confirms to have rails id
if #question.save
redirect_to school_course_path(current_course.school, current_course) #source of my frustrations - continues to returns same error message
else
render 'new'
end
end
private
def current_course=(course)
#current_school = course
end
def current_course
#current_course
end
end
Should work if your relationships are set up the way I think they are:
def create
#question = Question.new(params[:question])
#course = #question.course
if #question.save
redirect_to school_course_path(#course.school, #course)
else
render 'new'
end
end
Make sure you have something like this in your create action:
#course = Course.new(params[:course])
your code is okay, it seems there is problem in your redirect.. redirect it to root_path and check whether it is working??
I am having trouble passing parameters
My application that is setup like this:
Fact belongs_to Source
Source has_many Facts
Source is nested under User in routes
I am using the Facts form to create the Source data. So I have getter and setter methods in the Facts model like this:
def source_name
source.try(:name)
end
def source_name=(name)
self.source = source.find_or_create_by_name(name) if name.present?
end
This is working great, but it is not setting the user_id for the parent User attribute. As a result, sources are created, but they are not associated with the User.
I have a hidden field with user_id in the form, but the user_id is still being set. What is the easiest way to pass and save the user_id so the nested relationship is set?
Here is the create method for the Source controller:
def create
#user = User.find(params[:user_id])
#source = #user.source.build(params[:source])
...
end
I think the problem is that you are creating source directly from the setter method in the Fact model. Unless you establish the chain by using something like build in the FactController, the user_id will not be set. What you are doing in SourceController needs to be done in the FactsController too. Also, it seems that the ids are set only for the immediate parent when you use the build command. You can try something as below:
def create
#source = current_user.sources.find_or_create_by_name(params["source_name"])
#fact = #source.facts.build(:user_id => #source.user_id)
....
end
Hope that helps.
If your user has a single Source, try the following as your create() method:
def create
#user = User.find params[:user_id]
#user.source = Source.new params[:source]
if #user.save
redirect_to #user, :flash => { :success => "Source updated!" }
else
flash[:error] = "Failed to update the source!"
render :action => "new"
end
end
Creating the Source as an attribute on the User object and then saving the User object should automatically associate the Source with the User.
This is a consistency problem that I'm running into often.
Let's consider a typical Forum:
User can create Posts
Posts belong to a Topic
Posts also belong to the User that created them
What's the best practice for choosing between these two options:
# Initialize #post on the User
def create
#post = current_user.posts.build(params[:post])
#post.topic_id = #topic.id
if #post.save
...
end
end
Or
# Initialize #post on the Topic
def create
#post = #topic.posts.build(params[:post])
#post.user_id = current_user.id
if #post.save
...
end
end
Or is there a better way, considering that, in the above examples, either #post's user_id or topic_id would have to be added to attr_accesssible (feels hacky)?
The cleanest approach I managed to find is using CanCan: when having a rule can :create, Post, :user_id => user.id and adding load_resource in your controller it will set the attributes.
But it is not always suitable. It would be nice to have some generic solution to initalize nested objects in one shot.
Update. I've come up with another option:
#post = #topic.posts.where(user_id: current_user.id).build(params[:post])
Generally speaking, all of these approaches break the Law of Demeter. It would be better to encapsulate in a method of the model, like this:
class Topic < ActiveRecord::Base
def new_post(params={}, author=nil)
posts.build(params).tap {|p| p.user = author}
end
end
Then in controller:
#post = #topic.new_post(params[:post], current_user)
You never need to monkey with IDs or attr_accessible. If a User has_many Posts and a Topic has_many Posts than you can do
# Initialize #post on the User
def create
#post = current_user.posts.build(params[:post])
#post.topic = #topic #assuming you've gotten the topic from somewhere
if #post.save
...
end
end
There really isn't a big difference in building from the user or from the topic, but going from the user seems more natural to me.
I prefer
#post = #topic.posts.build(params[:post])
#post.user = current_user
Although I dont see any problem with the other approach, building post via topic make more natural to me(as posts are mostly displayed in the context of its topic rather than the user itself).