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.
Related
Imagine we have an Article and a Comment model. We setup our routes like:
# routes.rb
resources :articles do
resources :comments
end
Now, we can destroy a comment via the CommentController, but there are a number of approaches that I've seen been implemented.
# method 1
def destroy
Comment.where(article_id: params[:article_id]).find(params[:id]).destroy
end
# method 2
def destroy
Comment.find(params[:id]).destroy
end
# method 3
def destroy
article = Article.find(params[:article_id])
comment = article.comments.find(params[:id])
comment.destroy
end
Which is better and why?
I've seen in old Railscasts episodes and blogs that we should do the former for "security" reasons or because it is better to ensure that comments can only be found within their respective article, but why is that better? I haven't been able to find anything that goes too deep into the answer.
When you are working with nested data in this way, it's better to scope your model lookup under the parent to avoid people iterating through ids in a simplistic way. You generally want to discourage that, and it will protect you against more serious security problems if it's a habit.
For instance, say you have some sort of visibility permission on Article. With method 2, it is possible to use an article_id that you are allowed to see to access Comments that you aren't.
Methods 1 & 3 are ostensibly doing the same thing, but I would prefer 1 because it uses fewer trips to the DB.
I have read a lot about soft deletes and archive and saw all the pros and cons. I'm still confused on which approach would work best for my situation. I'll use the concept of posts and comments to see if I can explain it a bit easier
Post -> Comments
Post.all
Outside RSS Feeds -> Post -> Comments
RSSFeed.posts (Return the ones that are deleted or not)
Post gets "deleted" but I need the posts still accessible from say an RSS Feed but not the admin of the application.
I hear a lot of headaches with soft deletes but think it might make the most sense for my application and feel if I use Archive then I would have to run multiple queries
RSSFeed.posts || RSSFeed.archived_posts
not sure which would be more efficient or more a pain in the #$$. Thoughts or examples? I know this example sounds stupid but trying to think of multiple situations that could be used to figure out which way to go.
Just add another column in your database and call it archivated.
Use link_to_if for the links:
<%= link_to_unless #post.archivated?, #post.name, post_path(#path) %>
Some more rails goodness:
app/models/post.rb
class Post < ActiveRecord::Base
default_scope where( active: true )
def archivate
unless self.archivated?
self.archivated = true
self.save
end
end
def dectivate
if self.archivated?
self.archivated = false
self.save
end
end
end
app/models/archive.rb
class Archive < Post
set_table_name :posts # make this model use the posts table
default_scope where( active: false )
end
Now you can do stuff like this:
#post = Post.find(some_id)
#post.archivate
Archive.find(some_id) # should return the post you just archivated
Definitely you will get idea , take a look :
http://railspikes.com/2010/2/26/acts-as-archive
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.
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.
Let's say I have two different controller/view sets that are basically identical.
Business hours and Staff hours
The first contains a business_id, the latter a staff_id.
Otherwise they look the same. id, mon_start_time, mon_stop_time, tues_start_time, tues_stop_time, etc...
1)
Is there a way I would use the same controller for these since they are so similar? It doesn't seem to make THAT much sense, but I just keep thinking about how similar they are and how much duplicate code there is.
2)
Additionally, for each one I have a form in a partial. I'm trying to use the same one for both business hours and staff hours. The partial, in the most simplified state looks like:
-form_for(obj) do |f|
=f.error_messages
%p
=select obj.class, :mon_start_time, hours
to
=select obj.class, :mon_stop_time, hours
=link_to 'Back', obj_path
So there are 3 unique things I need to pass in. The 'obj', either business_hours or staff_hours. That's okay in the form_for, but how do I get the lowercase controller name for the first parameter of the selects? I need 'business_hour', and 'staff_hour', from 'obj'.
Then I need it to know the correct 'Back' link.
I know I can pass parameters into the partial, but I'm just curious if there's a slicker way of going about this.
Thanks!
Duplicate code has a carrying cost, a cost to maintain it. We sometimes don't know how high that cost is until we refactor the duplicate code and find outselves breathing a sigh of relief: Now I can change the business rule in just one place. Now I can stop typing things twice.
You can use two controllers but still refactor the duplicate code. One way is to put the common code in a module included by both controllers.
module CommonStuff
def stuff_that_is_the_same
end
end
controller FooController < ApplicationController
include CommonStuff
def stuff_that_is_different
# Stuff specific to Foo
stuff_that_is_the_same
# More stuff specific to Foo
end
end
controller BarController < ApplicationController
include CommonStuff
def stuff_that_is_different
# Stuff specific to Bar
stuff_that_is_the_same
# More stuff specific to Bar
end
end
As far as getting the name of controller is concerned you can get it in any controller action by calling the method
controller_name
and the controller state is available in #controller instance variable to your views, so if you want to access it in your views then you can do
#controller.controller_name
Now looking at your BusinessHours and StaffHours classes, I would say the best thing to do here to make them polymorphic. The first thing you will achieve here is to get rid of an almost identical table. So check out the rails core polymorphic docs
NOTE: But the has_many_polymorphs mentioned by #amurmann is not yet available in rails core, though you can use it as a plugin. Pratik wrote a blog post about it here
For removing duplicate code from the controller, you can either put that in a module (as #Wayne said) or create a base controller from which your Business and Staff hours controllers inherit all the common functionality. Now the solution totally depends what makes more sense in your application. Personally, I will create a base controller as it is more OO, keep my classes structured and code will not be hidden in some module. But some people may think otherwise.
Have you looked into Single Table Inheritance? I think it's the all around best solution for you here.
Essentially you define an hours table and model, that looks exactly like either staff hours or business hours and includes a string column named type. Then you create subclasses of Hours to define methods and validations specific to Business and Staff hours respectively. You also might want to redefine your association to something more generic. In the example, I've called it keeper, as on one who keeps those hours.
In a nutshell rails is handling all the polymorphic behaviour for you.
Models:
class Hour < ActiveRecord::Base
# common associations/methods etc go here
end
class BusinessHour < Hour
belongs_to :keeper, :class_name => "Business"
#business hour specific logic
end
class StaffHour < Hour
belongs_to :keeper, :class_name => "Staff"
#staff hour specific logic
end
class Business < ActiveRecord::Base
has_many :business_hours, :foreign_key => :keeper_id
...
end
class Staff < ActiveRecord::Base
has_many :staff_hours, :foreign_key => :keeper_id
...
end
Route:
map.resources :business_hours, :controller => "hours", :class => "BusinessHour"
map.resources :staff_hours, :controller => "hours", :class => "StaffHour"
Controller:
class HoursController < ApplicationController
before_filter :select_class
def select_class
#class = Kernel.const_get(params[:class])
end
def new
#hour = #class.new
...
end
def show
#hour = #class.find(params[:id])
...
end
...
end
Views will look almost exactly what you have now, but you're going to want some helpers to keep the output straight.
module HourHelper
def keeper_type
#class == "BusinessHours" ? "Business" : "Staff"
end
end
-form_for(obj) do |f|
=f.error_messages
%p =select keeper_type, :mon_start_time, hours
to
=select keeper_type, :mon_stop_time, hours
=link_to 'Back', obj_path
You might also want to consider creating a custom form builder to simplify the form creation.
It sounds to me like you might be able to combine both into one resource. Let's just call the resource "hours", for this discussiion.
The problem that "hour" might belong to either a business or staff could potentially be solved by making the relation polymorph.
You can for sure solve that relation problem with has_many_polymorphs:
class Hours < ActiveRecord::Base
has_many_polymorphs :participants, :from => [:staff, :business], :through => :hours_participants
end
This would require a new table and might not be the solution with the best performance. However, it should be a DRY solution.