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.
Related
I have multiple models that in practice are created and deleted together.
Basically I have an Article model and an Authorship model. Authorships link the many to many relation between Users and Articles. When an Article is created, the corresponding Authorships are also created. Right now, this is being achieved by POSTing multiple times.
However, say only part of my request works. For instance, I'm on bad wifi and only the create article request makes it through. Then my data is in a malformed half created, half not state.
To solve this, I want to send all the data at once, then have Rails split up the data into the corresponding controllers. I've thought of a couple ways to do this. The first way is having controllers handle each request in turn, sort of chaining them together. This would require the controllers to call the next one in the chain. However, this seems sorta rigid because if I decide to compose the controllers in a different way, I'll have to actually modify the controller code itself.
The second way splits up the data first, then calls the controller actions with each bit of data. This way seems more clean to me, but it requires some logic either in the routing or in a layer independent of the controllers. I'm not really clear where this logic should go (another controller? Router? Middleware?)
Has anybody had experience with either method? Is there an even better way?
Thanks,
Nicholas
Typically you want to do stuff like this -- creating associated records on object creation -- all in the same transaction. I would definitely not consider breaking up the creation of an Authorship and Article if creating an Authorship is automatic on Article creation. You want a single request that takes in all needed parameters to create an Article and its associated Authorship, then you create both in the same transaction. One way would be to do something like this in the controller:
class Authorship
belongs_to :user
belongs_to :article
end
class Article
has_many :authorships
has_many :users, through: :authorships
end
class ArticlesController
def create
#article = Article.new({title: params[:title], stuff: [:stuff]...})
#article.authorships.build(article: #article, user_id: params[:user_id])
if #article.save
then do stuff...
end
end
end
This way when you hit #article.save, the processing of both the Article and the Authorship are part of the same transaction. So if something fails anywhere, then the whole thing fails, and you don't end up with stray/disparate/inconsistent data.
If you want to assign multiple authorships on the endpoint (i.e. you take in multiple user id params) then the last bit could become something like:
class ArticlesController
def create
#article = Article.new({title: params[:title], stuff: [:stuff]...})
params[:user_ids].each do |id|
#article.authorships.build(article: #article, user_id: id)
end
if #article.save
then do stuff...
end
end
end
You can also offload this kind of associated object creation into the model via a virtual attribute and a before_save or before_create callback, which would also be transactional. But the above idiom seems more typical.
I would handle this in the model with one request. If you have a has_many relationship between Article and Author, you may be able to use accept_nested_attributes_for on your Article model. Then you can pass Authorship attributes along with your Article attributes in one request.
I have not seen your code, but you can do something like this:
model/article.rb
class Article < ActiveRecord::Base
has_many :authors, through: :authorship # you may also need a class_name: param
accepts_nested_attributes_for: :authors
end
You can then pass Author attributes to the Article model and Rails will create/update the Authors as required.
Here is a good blog post on accepts_nested_attributes_for. You can read about it in the official Rails documentation.
I would recommend taking advantage of nested attributes and the association methods Rails gives you to handle of this with one web request inside one controller action.
I have an app with the following tables:
Courses,
Exercises,
Answers, and
Users (with Devise).
class Answer < ActiveRecord::Base
belongs_to :exercise
belongs_to :user
end
Each exercise has an html answer box and a "submit" button, but I want to know if the data submitted by users through the answers in each exercise goes somewhere or what can I do to retrieve it?
What would I need to do to have the answers saved somewhere where they can reviewed, or even maybe sent by email?
I know like html forms that are sent by email when you hit submit, but would that indicate which user submitted the answer?
I appreciate your help!
For your current scenario you must have exercise_id and user_id column in your answers table.
When you submit a form for answer you must have exercise_id present. And you will easily get that from controller while you are showing exercise to answer.
Now for user_id, If you are using devise than you are getting current_user object every where in your application after login.
So to save answer you must write something like this:
current_user.answers.create(params_answer)
And params_answer should be something like:
def params_answer
params.require(:answer).permit(:exercise_id, :answer_text, :xxxx, :yyyy)
end
To get answer of any qustion that current user submitted:
curren_user.answers
To get answers of perticular exercise
curren_user.answers.where(:exercise_id, '##')
For more info go through http://guides.rubyonrails.org/association_basics.html and http://guides.rubyonrails.org/active_record_querying.html
Although I don't understand your words clearly.
Using session may help.
I'm learning Rails by doing an app for a college project that is basically a SO copy. Consider the following routes:
resources :questions do
resources :answers
post :vote_up, :vote_down, :on => :member
end
resources :answers do
post :vote_up, :vote_down, :on => :member
end
While this works fine, I'm sure it isn't the best way to do it. I got a lot of duplicated code between the vote_up and vote_down actions on both controllers. My specs have a lot of duplication too.
I would like to know how can I approach this in the most DRY way possible. I guess a VotesController is needed, but I played around with routing and didn't get a practical solution. All I got was some big URL's and not what I really hoped.
Can you please point me in the right direction ?
It sounds like you're describing a vote_controller which can take a vote in either direction, rather than splitting out up and down into their own controllers.
The question to ask as regards splitting the up and down vote into its own controller is how different they are. If you had a vote_up and vote_down controller both of them would have to find the votes of the entity being voted on, add a new vote and then store the data. Almost everything there would be the same whether or not you are voting up or down. In fact, the way I would manage votes would be to have a vote type a little like this:
class Vote
attr_reader :direction, :user, :timestamp
end
That way a single vote type can record whether it is an up or down vote as well as recording who made the vote ( so if someone is trolling you can undo all their work at a go ) and the time the vote was made, which is always useful.
Then when you call vote_controller.up_vote you create a new Vote object with a positive direction, for vote_controller.down_vote you create one with a negative direction. Everything else is totally common, so all you would have in the vote controller would be vaguely like this:
def vote(direction)
myVote = vote.new(direction, Request.userId, Time.now )
voteOnObject.votes.add(myVote)
end
def up_vote
vote( 1 )
end
def down_vote
vote( -1 )
end
You will notice that this is nothing like working code. That is partly because I don't want to do your homework for you, partly because I haven't done much Ruby lately, so I'm a little creaky. The important thing is the principle here.
If you want to be properly DRY you would also design this in a way that the same vote controls could be attached to questions, answers, comments and so on ( so they have a parent object of a given type/implementing a given mixin whose votes they adjust rather than affecting a model level vote object ) without having to make any changes.
The Question
I have a parent that accepts_nested_attributes_for a child. So, when I have a form for the parent, I need to build the child so I can display form fields for it as well. What I want to know is: where should I build the child? In the Model, View, or Controller?
Why I Am Asking This
You may be shaking your head and thinking I'm a madman for asking a question like this, but here's the line of thinking that got me here.
I have a Customer model that accepts_nested_attributes_for a billing_address, like so:
class Customer
belongs_to :billing_address, class_name: 'Address'
accepts_nested_attributes_for :billing_address
end
When I present a form for a new Customer to the user, I want to make sure there is a blank billing_address, so that the user actually sees fields for the billing_address. So I have something like this in my controller:
def new
#customer = Customer.new
#customer.build_billing_address
end
However, if the user doesn't fill out any of the billing_address fields, but tries to submit an invalid form, they will be presented with a form that no longer has fields for the billing_address, unless I put something like this in the create action of my controller:
def create
#customer = Customer.new(params[:customer])
#customer.build_billing_address if #customer.billing_address.nil?
end
There is another issue, which is that if a user tries to edit a Customer, but that Customer doesn't have an associated billing_address already, they won't see fields for the billing_address. So I have to add somethign like this to the controller:
def edit
#customer = Customer.find(params[:id])
#customer.build_billing_address if #customer.billing_address.nil?
end
And something similar needs to happen in the controller's update method.
Anyway, this is highly repetitive, so I thought about doing something in the model. My initial thinking was to add a callback to the model's after_initialize event, like so:
class CustomerModel
after_initialize :build_billing_address, if: 'billing_address.nil?'
end
But my spidey sense started tingling. Who's to say I won't instantiate a Customer in some other part of my code in the future and have this wreak havoc in some unexpected ways.
So my current thinking is that the best place to do this is in the form view itself, since what I'm trying to accomplish is to have a blank billing_address for the form and the form itself is the only place in the code where I know for sure that I'm about to show a form for the billing_address.
But, you know, I'm just some guy on the Internet. Where should I build_billing_address?
Though this advice by Xavier Shay is from 2011, he suggests putting it in the view, "since this is a view problem (do we display fields or not?)":
app/helpers/form_helper.rb:
module FormHelper
def setup_user(user)
user.address ||= Address.new
user
end
end
app/views/users/_form.html.erb:
<%= form_for setup_user(#user) do |f| %>
Note that I had to change the helper method to the following:
def setup_user(user)
user.addresses.build if user.addresses.empty?
user
end
The controller remains completely unchanged.
If you know your model should always have a billing address, you can override the getter for this attribute in your model class as described in the docs:
def billing_address
super || build_billing_address
end
Optionally pass in any attributes to build_billing_address as required by your particular needs.
You would use build if you want to build up something and save it later. I would say, use it in nested routes.
def create
#address = #customer.billing_addresses.build(params[:billing_address])
if #address.save
redirect_to #customer.billing_addresses
else
render "create"
end
end
Something like that. I also use the build when I'm in the console.
You have to remember the principles of MVC, which is to create DRY(don't repeat yourself) code, which is efficiently distributed between the various moving parts of the app
accepts_nested_attributes_for Is Great For Keeping Things DRY
accepts_nested_attributes_for is a model function which allows you to pass data through an association to another model. The reason why it exists is to give you the ability to populate another model's data based on a single form, and is excellent for extending functionality without too much extra code
The problem you're citing is that if you want to use the code in other areas of the app, you'll end up having all sorts of problems
My rebuttal to that is in order to create as efficient an application as possible, you want to write as little code as possible - letting Rails handle everything. The accepts_nested_attributes_for function does allow you to do this, but obviously has a cost, in that you have to accommodate it every time you want to use it
My recommendation is to use what you feel is the most efficient code you can, but also keep to conventions; as this will ensure speed & efficiency
You should handle all these scenarios in controller, since it is not a responsibility of model.
Just in terms of keeping things DRY, you can write a method,
def build_customer(customer)
customer.build_billing_address if customer.billing_address.nil?
#add more code if needed
end
And inside controller you can call this method wherever it is needed. e.g.
def create
#customer = Customer.new(params[:customer])
if #customer.save
redirect_to #customer.billing_addresses
else
build_customer(#customer)
render "new"
end
end
Preface: I'm still a beginner to web development, let alone rails so I'm constantly in over my head.
In my rails application, I have a boolean called "accepted" in "Bids."
On the show page for Bids, I am trying to create a button_to called "Accept Bid" that will, obviously, update the boolean from false to true, and then later, I will make it do a few other things. I experimented a bit with this but ended up getting so confused, I thought I would come here for some inspiration/push in the right direction.
Here's my bid.rb
class Bid < ActiveRecord::Base
belongs_to :user
belongs_to :swarm_request
# Accepts a bid for a swarm request
def accept!
self.swarm_request.update_attributes(:accepted => true)
# also update the bid with any details here?
end
end
Am I on the right track with this? Or should I create an action in the bids controller instead? Is using button_to the best way to do this? My apologies if I'm using incorrect jargon, or not being clear enough. Like I said, newb.
Thanks in advance for any help!
yes, it's ok to have skinny controller and fat model. you can read about this at http://weblog.jamisbuck.org/2006/10/18/skinny-controller-fat-model
and i don't see anything wrong with button_to for this job.