Creating child instances at the same time as their parent - ruby-on-rails

I'm trying to create a polling system:
# models
class Poll < ActiveRecord::Base
has_many :answers
end
class Answer < ActiveRecord::Base
belongs_to :poll
end
# routes
resources :polls do
resources :answers
end
Poll having one string attribute, question, and Answer having one string attribute, answer, and one integer, votes.
I'd like to have it so when a user creates a Poll, a form for a nested Answer instance is displayed. Then have a button with an AJAX call for another form (allowing up to say 10) so multiple answers can be added.
I know how to do the AJAX part, but I don't know how to add child instances to a parent that doesn't exist yet.
The only way I can think of is to use a text_field_tag to pass it through params, then manually create it in the PollsController create method. Sort of like this:
def create
#poll = #poll.create(poll_params)
if #poll.save
if params[:poll_answer1].present?
#poll.answers.create!(answer: params[:poll_answer1])
end
if params[:poll_answer2].present?
#poll.answers.create!(answer: params[:poll_answer2])
end
flash[:notice] = "Poll created."
redirect_to #poll
else
flash[:error] = "Poll could not be created."
render :new
end
end
But it seems like there should be a better way. I'm not opposed to scrapping the entire setup, and maybe placing the questions in an array attribute on Poll instead. But then there's the issue of counting votes.

Related

Rails: first_or_create not saving

My goal for my application is to only show a form page with existing data or a blank form if new. I've accomplished this by using a callback that created a blank record when the user is created.
User model:
before_create :build_health_profile
However, if for whatever reason a users "health_profile" were to be destroyed or non-existant, it breaks my entire app with:
"undefined method `health_profile' for nil:NilClass"
It was mentioned to me that the "first_or_create" method could solve this by show a new form or finding the existing one, but I can't get it to save the fields. It directs to my root with my save alert like it saved, but nothing gets actually saved.
Controller:
class HealthProfilesController < ApplicationController
def new
#health_profile = current_user.build_health_profile
end
def create
#health_profile = HealthProfile.where(user_id: current_user).first_or_create(health_profile_params)
if #health_profile.save
flash[:success] = "Health profile saved."
redirect_to root_path
else
render 'new'
end
end
private
def health_profile_params
params.require(:health_profile).permit(
:age,
:weight,
:height,
:gender
)
end
end
I've seen where I could use a block for "first_or_create", but no luck getting that to work.
View:
<%= link_to "Health Profile", new_health_profile_path %>
Models:
class User < ActiveRecord::Base
has_one :health_profile, dependent: :destroy
end
class HealthProfile < ActiveRecord::Base
belongs_to :user
end
If you use first_or_create then that calls the save method as part of it on the record and tries to save that in the database. If it can't save the record, then the transaction is rolled back. So, you want to use: first_or_initialize here which is like new and does not save the record in the database immediately. It just loads the data. So, you can call save on it in the next line of your code.
So, in your code, where you have:
#health_profile = HealthProfile.where(user_id: current_user).first_or_create(health_profile_params)
Here you are not controlling the save part, that's already being done by the first_or_create method.
So, you actually want to just load the object (NOT save yet) by using first_or_initialize:
#health_profile = HealthProfile.where(user_id: current_user).first_or_initialize(health_profile_params)
and then, in the next line, you can call the save and based on it's return value you can take the decision:
if #health_profile.save
# do stuff if successfully saved health_profile
else
# otherwise
render 'new'
end
Because you have #health_profile.save,
You should change first_or_create into first_or_initialize
first_or_create immediately trigger save, whereas first_or_initialize would just assign the values to a New record or to an already existing record if record exists already
I was able to fix the problem of the record resetting itself when going back to the form by adjusting the new action. Thats everyone for the help.
def new
#health_profile = current_user.health_profile || HealthProfile.new
end
def create
#health_profile = HealthProfile.where(user_id: current_user).first_or_initialize(health_profile_params)
if #health_profile.save
flash[:success] = "Health profile saved."
redirect_to root_path
else
render 'new'
end
end

Neo4J Gem - Saving undeclared relationships

I am trying to create a realtionship between two nodes as described here
https://github.com/neo4jrb/neo4j/wiki/Neo4j-v3-Declared-Relationships
from_node.create_rel("FRIENDS", to_node)
I am getting an undefined method for create_rel
What am I doing wrong? I am trying to create a Q+A system inside another model. So both Questions and Answers are treated as models right now.
I'm getting a undefined methodcreate_rel' for #
event.rb
has_many :out, :event_questions
event_question.rb
has_one :in, :events
has_many :out, :event_answers
def create_questions_of(from_node,to_node)
from_node.create_rel("questions_of", to_node)
end
event_answer.rb
has_one :in, :event_questions
event_questions_controller.rb
def new
#is this needed
end
def create
#event_question = EventQuestion.new(event_question_params)
if #event_question.save
#event = Event.find(params[:id])
#event_question.update(admin: current_user.facebook_id)
#event_question.create_questions_of(self,#event)
redirect_to #event
else
redirect_to #event
end
end
private
def event_question_params
params.require(:event_question).permit(:question)
end
I have my new question sitting inside the event's index page since I wanted to list all the questions on the event after. I don't even need a new method in my controller right? I also don't really know how I would obtain the event that my question form is sitting on. Is that accessible through params?
UPDATE
Did you mean this
def create_questions_of(to_node)
self.create_rel("questions_of", to_node)
end
and
#event_question.create_questions_of(#event)
So I think I need to change my routes as well and nest questions inside to create
events/123/questions/
Then I can grab events_id and use find
UPDATE #2
events_controller.rb
def show
#event = Event.find(params[:id])
#event_question = EventQuestion.new
end
event.rb
has_many :out, :event_questions, type: 'questions_of'
event_question.rb
has_one :in, :events, origin: :event_questions
events/show.html.erb
<%= form_for [:event, #event_question] do |f| %>
#form stuff
<% end %>
event_questions_controller.rb
def create
#event_question = EventQuestion.new(event_question_params)
if #event_question.save
#event = Event.find(params[:event_id])
#event_question.update(admin: current_user.facebook_id)
#event_question.events << #event
redirect_to #event
else
redirect_to :back
end
end
routes.rb
resources :events do
resources :event_questions, only: [:create, :destroy]
end
create_rel worked fine when I tested it just now. Is it saying undefined method 'create_rel' for nil:NilClass? If so, it means that your from_node variable doesn't actually have a node set. Make sure your objects are what you think they are.
The better question here: why do you want to do this? When you create an undeclared relationship, you have to write your own Cypher queries whenever you want to use it. If it's part of your code and you are using it regularly, it should probably have has_many associations in your models. create_rel really only exists to provide interoperability with nodes that don't have models.
As for your other question, you don't need a new action unless there's a route and a view that corresponds with it. If you're loading the form for a new question on your index page, that's fine. If your URL is something like http://127.0.0.1:3000/events/123/questions/, then you can get the Event ID in params[:event_id]. Run the rake routes command from your project's directory and it'll spit out lots of information that includes the parameter names.
Finally, when you use self in #event_question.create_questions_of(self,#event), you're going to get the controller. If you want it to refer to the #event_question, just remove that first argument from create_questions_of and use self from within the method.
Edit: Part 2
You're getting the undefined method because self in #event_question.create_questions_of(self,#event) is the controller. You're trying to send #event_question to itself, I think. Don't do that, just call self from within create_questions_of and you'll get current EventQuestion.
You use ActiveRel if you want callbacks, validations, properties, etc,... If you just want a simple relationships, just setup the has_many associations in each model, omit rel_class, and either set them both to the same type or set origin on one.
class Event
include Neo4j::ActiveNode
has_many :in, :event_questions, type: 'questions_of'
end
class EventQuestion
include Neo4j::ActiveNode
has_many :out, :events, origin: :event_questions
end
origin says, "Look for this association in the reciprocal model and use the type it defines." It lets you not have to worry about synchronizing the type between associations.
After that, you can do #event_question.events << #event and it'll create a new relationship for you.

Rails has_one build_association deletes record before save

So this has been asked previously, but with no satisfying answers.
Consider two models, User, and Subscription associated as such:
class User < ActiveRecord::Base
has_one :subscription, dependent: :destroy
end
class Subscription < ActiveRecord::Base
belongs_to :user
end
Inside of SubscriptionsController, I have a new action that looks like this
def new
user = User.find(params[:user_id])
#subscription = user.build_subscription
end
Given that a subscription already exists for a user record, I'm faced with the following problem:
user.build_subscription is destructive, meaning that simply visiting the new action actually destroys the association, thereby losing the current subscription record.
Now, I could simply check for the subscription's existence and redirect like this:
def new
user = User.find(params[:user_id])
if user.subscription.present?
redirect_to root_path
else
#subscription = user.build_subscription
end
end
But that doesn't seem all that elegant.
Here's my question
Shouldn't just building a tentative record for an association not be destructive?
Doesn't that violate RESTful routing, since new is accessed with a GET request, which should not modify the record?
Or perhaps I'm doing something wrong. Should I be building the record differently? Maybe via Subscription.new(user_id: user.id)? Doesn't seem to make much sense.
Would much appreciate an explanation as to why this is implemented this way and how you'd go about dealing with this.
Thanks!
It depends on what you want to do
Thoughts
From what you've posted, it seems the RESTful structure is still valid for you. You're calling the new action on the subscriptions controller, which, by definition, means you're making a new subscription (not loading a current subscription)?
You have to remember that Rails is basically just a group of Ruby classes, with instance methods. This means that you don't need to keep entirely to the RESTful structure if it doesn't suit
I think your issue is how you're handling the request / action:
def new
user = User.find(params[:user_id])
#subscription = user.build_subscription
end
#subscription is building a new ActiveRecord object, but doesn't need to be that way. You presumably want to change the subscription (if they have one), or create an association if they don't
Logic
Perhaps you could include some logic in an instance method:
#app/models/user.rb
Class User < ActiveRecord::Base
def build
if subscription
subscription
else
build_subscription
end
end
end
#app/controllers/subscriptions_controller.rb
def new
user = User.find(params[:user_id])
#subscription = user.build
end
This will give you a populated ActiveRecord, either with data from the subscription, or the new ActiveRecord object.
View
In the view, you can then use a select box like this:
#app/views/subscriptions/new.html.erb
<%= form_for #subscription do |f| %>
<%= "User #{params[:user_id]}'s subscription: %>
<%= f.collection_select :subscription_id, Subscription.all,:id , :name %>
<% end %>
They are my thoughts, but I think you want to do something else with your code. If you give me some comments on this answer, we can fix it accordingly!
I also always thought, that a user.build_foobar would only be written to the db, if afterwards a user.save is called. One question: After calling user.build_subscription, is the old subscription still in the database?
What is the output user.persisted? and user.subscription.persisted?, after calling user.build_subscription?
Your method to check if a subscription is present, is IMHO absolutely ok and valid.
I came across this today and agree that deleting something from the db when you call build is a very unexpected outcome (caused us to have bad data). As you suggested, you can work around if very easily by simply doing Subscription.new(user: user). I personally don't think that is much less readable then user.build_subscription.
As of 2018 Richard Peck's solution worked for me:
#app/models/user.rb
Class User < ActiveRecord::Base
def build_a_subscription
if subscription
subscription
else
build_subscription
end
end
end
My issue was that a user controller didn't have a new method, because users came from an api or from a seed file.
So mine looked like:
#app/controllers/subscriptions_controller.rb
def update
#user = User.find(params[:id])
#user.build_a_subscription
if #user.update_attributes(user_params)
redirect_to edit_user_path(#user), notice: 'User was successfully updated.'
else
render :edit
end
end
And I was finally able to have the correct singular version of subscriptions in my fields_for, so :subscription verses :subscriptions
#app/views
<%= f.fields_for :subscription do |sub| %>
<%= render 'subscription', f: sub %>
<% end %>
Before I could only get the fields_for to show in the view if I made subscriptions plural. And then it wouldn't save.
But now, everything works.

accepts_nested_attributes_for alias?

In Topic model:
class Topic < ActiveRecord::Base
has_many :choices, :dependent => :destroy
accepts_nested_attributes_for :choices
attr_accessible :title, :choices
end
During a POST create, the params submitted is :choices, instead of :choices_attributes expected by Rails, and giving an error:
ActiveRecord::AssociationTypeMismatch (Choice(#70365943501680) expected,
got ActiveSupport::HashWithIndifferentAccess(#70365951899600)):
Is there a way to config accepts_nested_attributes_for to accept params passing as choices instead of choices_attributes in a JSON call?
Currently, I did the attributes creation in the controller (which seems not to be an elegant solution):
def create
choices = params[:topic].delete(:choices)
#topic = Topic.new(params[:topic])
if choices
choices.each do |choice|
#topic.choices.build(choice)
end
end
if #topic.save
render json: #topic, status: :created, location: #topic
else
render json: #topic.errors, status: :unprocessable_entity
end
end
This is an older question, but I just ran into the same problem. Is there any other way around this? It looks like that "_attributes" string is hardcoded in the nested_attributes.rb code (https://github.com/rails/rails/blob/master/activerecord/lib/active_record/nested_attributes.rb#L337).
Assigning "choices_attributes" to a property when submitting a form is fine, but what if it's being used for an API. In that case it just doesn't make sense.
Does anyone have a way around this or an alternative when passing JSON for an API?
Thanks.
UPDATE:
Well, since I haven't heard any updates on this I'm going to show how I'm getting around this right now. Being new to Rails, I'm open to suggestions, but this is the only way I can figure it out at the moment.
I created an adjust_for_nested_attributes method in my API base_controller.rb
def adjust_for_nested_attributes(attrs)
Array(attrs).each do |param|
if params[param].present?
params["#{param}_attributes"] = params[param]
params.delete(param)
end
end
end
This method basically converts any attributes that are passed in to #{attr}_attributes so that it works with accepts_nested_attributes_for.
Then in each controller that needs this functionality I added a before_action like so
before_action only: [:create] do
adjust_for_nested_attributes(:choices)
end
Right now I'm only worried about creation, but if you needed it for update you could add that into the 'only' clause of the before_action.
You can create method choices= in model as
def choices=(params)
self.choices_attributes = params
end
But you'll break your setter for choices association.
The best way is to modify your form to return choices_attributes instead choices
# Adds support for creating choices associations via `choices=value`
# This is in addition to `choices_attributes=value` method provided by
# `accepts_nested_attributes_for :choices`
def choices=(value)
value.is_a?(Array) && value.first.is_a?(Hash) ? (self.choices_attributes = value) : super
end

Any way around putting hidden field in forms for resources with belongs_to association

I'm learning Rails by writing simple TODO tasks aplication.
Two models are:
class List < ActiveRecord::Base
has_many :tasks, :dependent => :destroy
# ...
end
class Task < ActiveRecord::Base
belongs_to :list
# ...
end
Tasks are routed as a nested resources under Lists. So when a new Task is created by user a POST message is sent to /lists/:list_id/tasks. So far in Tasks#new view's form there is
f.hidden_field :list_id, :value => params[:list_id]
but it's a terrible solution, because anyone can change value of that hidden field.
What is the convention here? Should I put something like
#task.list_id = params[:list_id]
in Tasks#create action and get rid of the hidden field, or maybe
#task = List.find(params[:list_id]).tasks.new(params[:task])
if #task.save
# ...
end
or there is even a better way I don't know about?
Edit:
Yeah, well there was similar question and its answer is pretty much covering my question. If you have different one please post it.
You're right - that would be horrible. No need for hidden fields. Something like the following.
In your TasksController:
def new
#list = List.find(params[:list_id])
#task = #list.tasks.build
end
def create
#list = List.find(params[:list_id])
#task = #list.tasks.new(params[:task])
# etc
end
In your Task#new view:
<% form_for [#list, #task] ... %>
...
<% end %>
If you are concerned about security (like one user creating to-dos in another user's lists - and I assume you are, because you didn't want to use a hidden field stating that anyone can change value of that hidden field), I don't see how #bjg solution is any better then yours, since you're getting #list from params anyways, and anybody can manipulate params on the browser (changing the URL to post to is as easy as changing the hidden field value).
One common way to solve this without having to implement a more complex permission solution is to just use current_user association's, like this:
def new
#list = current_user.lists.where(id: params[:list_id]).take
#task = #list.tasks.build
end
def create
#list = current_user.lists.where(id: params[:list_id]).take
#task = #list.tasks.new(params[:task])
# etc
end
This way, no matter what is the value of params[:list_id] (it could have been manipulated by the user), you can rest assured the #task will end up on that user's account, since #list will only find a record that belongs to current_user.
You can evolve this in a real-world app by returning an error message if #list is not found.

Resources