Passing parameters for nested relationship - ruby-on-rails

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.

Related

Rails NoMethodError undefined method `data' for nil:NilClass (Controller#update)

Edit: it turns out I made a very simple mistake and had a Template that was associated with a LocalTemplate id that no longer existed. If anyone has this problem and thinks that they somehow are unable to unable to associate the id of another model in their update action, make sure that you didn't accidentally delete the parent object causing that id to no longer exist!
The code below, while dramatically simplified did work for me.
I have a Template model in my rails app. It has a method "data" defined in it.
I am able to access this method in the create and show actions with #template.data, however when using the same #template.data in the update action of my controller I get a no method error because I am not showing the correct local template id to it. This line can be found in the model where it reads base_data = YAML.load(local_template.data)
I stored an id of the associated local_template when initially saving a new template, but how can I make sure I reference that id again in the update action so I do not get a no method error?
Here is a simplified version of the Template model and controller
Model:
class Template < ActiveRecord::Base
def data
base_data = YAML.load(local_template.data)
# couldn't pass the correct LocalTemplate here because
# the local_template_id I had in my Template model no
# longer existed. Changing the id to a LocalTemplate
# that did exist fixed the issue.
end
end
Controller:
class TemplatesController < ApplicationController
def index
#business = Business.find(params[:business_id])
#templates = #business.templates.all
end
def new
#business = Business.find(params[:business_id])
#local_templates = LocalTemplate.all
#template = #business.templates.build
end
def create
#business = Business.find(params[:business_id])
#local_templates = LocalTemplate.all
#template = #business.templates.build(template_params)
if #template.save
#template.data #works fine here
redirect_to business_url(#template.business_id)
else
render 'new'
end
end
def show
#business = Business.find(params[:business_id])
#template = #business.templates.find(params[:id])
#template.data #works fine here too
end
def edit
#business = Business.find(params[:business_id])
#local_templates = LocalTemplate.all
#template = #business.templates.find(params[:id])
end
def update
#business = Business.find(params[:business_id])
#template = #business.templates.find(params[:id])
if #template.update_attributes!(pass_template_params)
Api.new.update_template(#template.data.to_json) #this is where I had a problem
redirect_to business_url(#template.business_id)
else
render 'edit'
end
end
end
You are mixing a lot. There is a lot to refactor in your controller...
First of all, your TemplatesController should be about the template resources, but your controller looks more like a BusinessesController. In general your update action for example should look more like:
def update
#template = Template.find params[:id]
#template.attributes = template_params # though this should raise a NoMethodError, because you dind't define it; I'd prefer params[:template] if possible
if #template.save
redirect_to business_url(#template.business_id)
else
#local_templates = LocalTemplate.all
render 'edit'
end
end
Instantiating #business and #local_templates makes non sense, because you don't use it at all. Speed up your responses if you can! :)
Fixed that, there is no need for the overhead of a nested resource in update (as you did).
If saving #template fails for validation reasons, you better should load the business object late by:
#template.business
in your /templates/edit.html.erb partial. Then you also do not need a nested route to your edit action... You see, it cleans up a lot.
As a general guideline you should create as less as possible controller instance variables.
If you cleaned up your controller and views, debugging your data issue will be easier.
I assume:
local_template
in your Template model to be an associated LocalTemplate model object. So it should no issue to call that anywhere if you ensured the referenced object exists:
class Template < ActiveRecord::Base
def data
return if local_template.nil?
YAML.load(local_template.data)
end
end
or validate the existence of the local_template object. or even b
You should confirm #template is not nil, if #template is nil, you can't use data method.
1.9.3-p547 :024 > nil.data
NoMethodError: undefined method `data' for nil:NilClass
from (irb):24
from /Users/tap4fun/.rvm/rubies/ruby-1.9.3-p547/bin/irb:12:in `<main>'
And you should use update_attributes!, it can raise an exception if record is invalid.
You can do like this.
if #template
#template.update_attributes!(template_params)
#template.data
end

Rails STI not saving "type" field

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.

Duplicating a record with a Paperclip attachment

I'm creating an action which duplicates an item and then allows the user to edit it and save it back to the database.
I've written the following method in my controller and it mostly works apart from the Paperclip attachment which won't move across for some reason.
def duplicate
existing_event = Event.find(params[:id])
#event = Event.new(existing_event.attributes)
render action: 'new'
end
I've seen this question where the person is using .dup but I can't seem to get that working in a situation where the user edits the new item before saving.
I also tried using something like #event.image = existing_event.image but that didn't have any effect either.
This is what my create method looks like:
def create
#event = Event.create(event_params)
if #event.save
redirect_to events_path, notice: "Event was successfully created."
else
render action: 'new'
end
end
If it makes any difference I'm using S3 for the image uploads too and it doesn't really matter to me if there are multiple copies of the image up there.
Can anyone help? Thanks!
Passing the attachment params does just that: pass the params.
You need to pass the file itself.
Below you get the idea how to do it, not tested it, but you can play around it and make it work, it shouldn't be that hard.
On new action:
existing_event = Event.find(params[:id])
#event = Event.new(existing_event.attributes)
#event.image = File.open(existing_event.image.path,'rb')
render :action => 'new'
Also:
Check in your create action, you have a slight mistake, calling create and save for the same record - this is redundant. You should call #event=Event.new(event_params) and then if #event.save.
Here's a little snippet I use in an initialiser:
module Paperclip
class HasAttachedFile
def define_with_extensions
define_without_extensions
define_dup_override
end
alias_method_chain :define, :extensions
private
def define_dup_override
name = #name
#klass.send :define_method, "dup" do
copy = super()
self.class.attachment_definitions.each do |name, options|
ivar = "#attachment_#{name}"
copy.instance_variable_set(ivar, nil)
copy.send(name).assign send(name)
end
copy
end
end
end
end
This will assign the files from the old record to the new record programatically without knowing what the actual attachment definitions are.

Getting "undefined method for Nil Class" error when calling the reciprocal model method

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??

Should I use class method or instance method, and why?

In my Rails app, when creating a business I have a form that has the following field:
<%= check_box_tag(:default_company) %>
<%= label_tag(:default_company, "Set Company as Default") %>
Essentially when I create a business, if they check this box, I need it to run something like the following code:
def set_default_company(company, user)
exists = DefaultCompany.find(user.id)
if exists
exists.update_attributes(company: company)
else
DefaultCompany.create(company: company, user: user)
end
end
While learning, I would usually do that stuff in my controller but i'm trying to follow best practices and use a fat model, skinny controller, so I'm wanting to use logic like this:
def create
#company = Company.new(params[:company])
if #company.save
if params[:default_company]
Company.set_default_company(#company.id, current_user.id,)
end
flash[:notice] = "Company was successfully created."
redirect_to #company
else
redirect_to new_company_path
end
end
Here is where I am getting confused on whether to use a class method or an instance method, to call set_default_company. They both seem like they would work and I can't see a benefit to one or the other.
In addition to giving me any information as to which method to use, if someone could show me a brief implementation of writing that as a class method vs. instance method it may give me a better understanding as to why.
Here is how I would write them:
def self.set_default_company(company, user)
# Logic here
end
def set_default_company(company, user)
# Logic here
end
Writing them that way I don't see a benefit to either.
As their name suggests, instance methods on a model should be used for logic/operations that relate to a specific instance of a user (the one on which the method is called.) So you could think of setting the default company for a user as an instance method on User. Class methods are for things which don't operate on an individual instance of a model or for cases where you don't have the instance available to you. e.g. you might have a class method to tidy up your database such as User.purge_expired_users which would not apply to an individual user object.
e.g.
class User
def set_default_company(company)
exists = DefaultCompany.find(self.id)
if exists
exists.update_attributes(company: company)
else
DefaultCompany.create(company: company, user: self)
end
end
end
then your controller method would look like:
def create
#company = Company.new(params[:company])
if #company.save
if params[:default_company]
current_user.set_default_company #company
end
flash[:notice] = "Company was successfully created."
redirect_to #company
else
redirect_to new_company_path
end
end
Alternatively, you could think of the relationship from the other perspective and put an instance method on Company e.g. company.set_as_default_for(user).
I would actually make set_default_company an instance method on User. A User has a default Company; why should a Company need to what users it is default for?
class User
def set_default_company(company)
exists = DefaultCompany.find(id)
if exists
exists.update_attributes(company: company)
else
DefaultCompany.create(company: company, user: self)
end
end
end
In my opinion, I always create a class method if the method in question represents information/behavior that is quite generic among all the objects instantiated, different from the instance methods, that I use when I believe it's more like a specific action of the instantiated object in question.
But that is my point-of-view.
A few things: do you have a separate table for DefaultCompany? This seems like it should be a boolean flag on the company table.
Next, is there an association between companies and users? If so, it seems the best way to do it would be
In the user model
def set_default_company(company)
self.companies.each do |c|
c.update_attributes(:default => false)
end
company.update_attributes(:default => true)
end
Or in the Company model
def set_as_default
update_attributes(:default_company => true)
end

Resources