Nested form to invoke custom factory method instead of new() - ruby-on-rails

This problem is related to this question: Hashing an IP for saving
I have a model called Post. I have to pass in the IP address during post creation. I was told not to override the initialize(). So I used a factory method as suggested here: how to override new method for a rails model:
#Post.rb model
def self.new_with_ip(ip, attributes={})
self.new(attributes['one_day_id'] = do_some_conversion_on(ip))
end
However this does not get invoked, because Post is nested within a Discussion, and the nested form will not call this factory method. How can I make the form to invoke this instead of the traditional Post.new()?

If this is being passed through as a nested attribute of a form then you would have to override the posts_attributes= method of the Discussion model:
def posts_attributes=(attribute_sets)
attribute_sets.each do |attributes|
Post.new_with_ip(ip_goes_here, attributes)
end
end
Of course you're going to need to modify that a little if you're going to be getting nested posts in an update kind of fashion, as you'll want to update existing posts rather than creating new ones. Sounds like a good exercise in learning :)

Related

Rails Custom Validation With Request Params?

Rails noob ,
I am trying to write a custom validation but I am dependent on parameters that come from the post request. This example is similar to what I have - in this post model I want to validate that a user didn't already post on some topic, but to do that I have to have the topic id from the post request:
class Post < ActiveRecord::Base
...
validate :user_already_posted, on: :create
def user_already_posted
topic=Topic.where(id: params[:topicId]).first
//some code that runs on topic users and makes sure the user hasn't posted there
However , I learned that params isn't global and models can't access it (I last used laravel where this isn't the case , so it's confusing for me).
Are custom validations not suited for what I need ? do I need to look at filters or have the controller run a validating function of it's own?
You could do this:
validates_uniqueness_of :user, scope: :topic_id
May not be exactly what you need, but the point is that you can validate within a scope.
You can also pass an array to validate on multiple parameters.
validates_uniqueness_of :user, scope: [:topic_id, :post_id]
I think you are having a bit of a design problem here. You are creating a post validator, but the validation itself is not about the post instance. I mean, the model validator must evaluate all of the post attributes, using the default validators built-in ActiveRecord or creating custom ones, like you did, acessing the attributes via self.
I believe a class method receiving all your parameters and checking the conditions of the post creation is more clear:
Class method on the model
def self.user_already_posted(topic_id, user_id)
# Do your thing
end
Usage on the controller
can_create = Post.user_already_posted(params[:topic_id], params[:user_id)

Trouble with Rails MVC concepts

Ugh - not sure why I'm having so much trouble with this.
Writing a simple question and answer app (see Rails - Where should this code go to stay true to MVC design? for some details)
Trying to stick to MVC principles and proper design - this app is simply a learning experience, so I want to make sure I'm doing things in a generally accepted way
The referenced question gave me advice to split up my functionality into the different models. However, trying to implement this, I find myself passing parameters all over the place and I just get a feeling that I'm not doing something right. So, here's the basic layout of the app and the tasks I am trying to accomplish - if someone could let me know if I'm on the right track...
Question Model: contains id(pkey), question_number(int), question_text(string), answer_text(string)
User Model: contains: id(pkey), uid(string), current_question(int), name(string)
I created both of the above with scaffold so they have all the default routes, controller actions, etc...
I created a gateway controller and set it to be my default page through routes.rb
The idea is, user browses to localhost:3000/?uid="whatever" and the index page displays the current question (Question.find_by_question_number(#user.current_question))
User enters answer in a form, which POSTs it to an action. In my first draft, this called an action in the gateway controller which checked if the answer was correct.
Now, I am trying to take vadim's advice in my last question and keep the user login in user and the question logic in question. So now my form POSTs to the users controller.
Here's where I get mixed up. The logic code shouldn't be in the controller, right? So I call a method in the model, passing it stuff like the user's answer and the question id since I can't read the session in the model. This works fine, I can now take care of the logic in the user model - so now the user model calls a method in the question model to actually check the answer. That means I have to instantiate my question object using the ID I passed, then call another method, passing (again!) the answer. Etc...etc...
See what I mean? I definitely understand the value of MVC in theory, but whenever I try and implement it I wind up with a mess like this. It this correct, and it just seems like overcomplicating things because my program is so simple?
Can someone walk me through how you would split the logic up? You don't need to post actual code, just what you would put where, like:
Gateway Controller:
-display question to user
-take answer and pass to XXX controller
XXX controller:
-call method Foo in XXX model, passing X and Y
The basic flow should be, user is shown a question, user answers question, answer is compared to the correct answer in the question model, message is returned based on result, and it answer was correct, user's current_question is incremented.
Thanks so much for the help, I have books and Google and have been reading my a$$ off, just lost in the sauce here. This is my first attempt to venture outside the safety of pre-written example code, so please be gentle!!
Thanks!!
In most cases in a Q&A app, you would have a Question Model, an Answer Model, and a User model. Your actions are:
displaying answers (the show method for the Questions controller)
Showing the new answer form
Posting to the create method on the Answers controller.
Some code:
class Question
has_many :answers
end
class Answer
belongs_to :question
has_many :users
validates_presence_of :user
validates_presence_of :question
validates_uniqueness_of :question_id, :scope => :user_id
end
class User
has_many :answers
end
Routes
resources :questions do
resources :answers
end
answers_controller
class AnswersController < ApplicationController
def create
#answer = Answer.new(params[:answer])
#answer.user = current_user
#answer.question = Question.find(params[:question_id])
if #answer.save
flash[:success] = "Saved!"
redirect_top #answer.question
else
render :new
end
end
end
The basic flow should be:
a method in the controller selects the question and displays a view with a form in it
the users submits this form
this is a POST to the controller
in the controller you check the result and display it to the user in another/the same view.
Now to the model. You can put methods in there that check certain things. Still, the controller handles the work, it calls the method and processes the results in the controller.

whats the difference between these create methods

Hey I am stuck with my orientation in rails.
I got a User model, a Course Model and a CourseEnrollment Model.
When I want to add a link in my Course Index View like
link_to 'join' CourseEnrollment.create(:course_id => course.id, :user_id => current_user)
Does this create method belong to my Model? I am confused because in my User Model I defined a method that uses role_assignments.create(.....). What is the difference between these 2 create methods? I cant use course_enrollments.create by the way. Thx for your time
I'm a bit confused as to what you're asking, but I'll try my best.
(First of all, in your example, current_user should probably be current_user.id.)
When you call CourseEnrollment.create, you are simply creating a new CourseEntrollment model with the specified attributes.
Assuming that your User model has_many :role_assignments:
When you call #role_assignments.create from within your User model, Rails automatically creates the association for you (e.g. sets the user_id to the id of the user). This doesn't have to be done within the model itself, though:
current_user.role_assignments.create(...) # automatically sets the association
Assuming that your User model also has_many :course_enrollments, the following will create a CourseEnrollment model and automatically associate it with the current user:
current_user.course_enrollments.create(...)

Using STI path with same controller

I am using STI and am wondering, do I have to have a separate controller for each model? I have a situation where I only use the create and edit actions for one model in the STI relationship, but I get an 'undefined method' error if I try to do a form for. More specifically, I have two models that inherit from List:
class RegularList < List
class OtherList < List
and I have a lists controller that handles these actions, but I only create new models with RegularList using forms. i.e. the only situation where I use a form_for to create a new List object is with RegularList. What I would like to do is something like:
class ListsController < ApplicationController
def new
#list = RegularList.new
end
otherwise the route for creating a new list looks like regular_list/new but I would like it to just be list/new. Thoughts?
EDIT: The problem is when I use the above code, I get an 'undefined method' error. My view looks like this:
...
So it seems that there is some problem with using a RegularList object in the Lists controller and this is the main problem I am trying to address. Sorry, I realize that was not the clearest explanation.
I know this is kind of late, but maybe this will be helpful for other people. You want to use the becomes method. Let's say you are editing #list which is an instance of RegularList. Then do
form_for #list.becomes(List)
I am using STI and am wondering, do I have to have a separate controller for each model?
No, you don't.

Rails: How can I access the parent model of a new record's nested associations?

Suppose we have the standard Post & Comment models, with Post having accepts_nested_attributes_for :commments and :autosave => true set.
We can create a new post together with some new comments, e.g.:
#post = Post.new :subject => 'foo'
#post.comments.build :text => 'bar'
#post.comments.first # returns the new comment 'bar'
#post.comments.first.post # returns nil :(
#post.save # saves both post and comments simultaneously, in a transaction etc
#post.comments.first # returns the comment 'bar'
#post.comments.first.post # returns the post 'foo'
However, I need to be able to distinguish from within Comment (e.g. from its before_save or validation functions) between
this comment is not attached to a post (which is invalid)
this comment is attached to an unsaved post (which is valid)
Unfortunately, merely calling self.post from Comment doesn't work, because per above, it returns nil until after save happens. In a callback of course, I don't (and shouldn't) have access to #post, only to self of the comment in question.
So: how can I access the parent model of a new record's nested associations, from the perspective of that nested association model?
(FWIW, the actual sample I'm using this with allows people to create a naked "comment" and will then automatically create a "post" to contain it if there isn't one already. I've simplified this example so it's not specific to my code in irrelevant ways.)
I think it is strange that Rails does not let you do this. It also affects validations in the child model.
There's a ticket with much discussion and no resolution in the Rails bug tracker about this:
Nested attributes validations
circular
dependency
And a proposed resolution:
nested models: build should directly
assign the
parent
Basically, the deal is, the nested attributes code doesn't set the parent association in the child record.
There's some work-arounds mentioned in the second ticket I linked to.
I don't think you can do this. On the other hand, your validations shouldn't be failing, as the order of the transaction will create the post record before saving the comment.

Resources