I have a User model, which have voting methods. I want to write proxy methods for voting.
This is readable way:
def vote_up item
return false unless can? :vote, item
vote item, :up
end
def vote_down item
return false unless can? :vote, item
vote item, :down
end
And this is DRY way:
%w(up down).each do |vtype|
define_method "vote_#{vtype}" do |item|
return false unless can? :vote, item
vote item, vtype.to_sym
end
end
Which one is better and why?
Purely because OP seemed to like my comment, I'll put it as an answer:
Personally, considering you only have 2 methods here, and it's unlikely you'd ever add more (vote_sideways? vote_diagonally?) I would just go with the readable way. If you could potentially have many, many more though, I would go with the DRY way (because it becomes easily extendible) with a readable comment to explain to other developers (or to yourself later!).
Neither(sorry).
def vote_count(item,vtype)
return false unless can? :vote, item
vote item, vtype
end
Good luck
IMHO, in this case, readability trumps dry. It scans quickly, and is easily grokked. Having said that, if you start adding vote types the second approach may be more flexible. YMMV.
Both.
I'm with Anil; just pass in a type--meta-programming this as a first-resort is yucky.
That said, I am a fan of convenience methods--but they should call the generic method with the type.
This keeps the generated method concise--the real work is done in the generic method, but the API user still gets the same convenience methods.
Related
I have two models, User and Account.
# account.rb
belongs_to :user
# user.rb
has_one :account
Account has an attribute name. And in my views, I was calling current_user.account.name multiple times, and I heard that's not the great of a way to do it. So I was incredibly swift, and I created the following method in my user.rb
def account_name
self.account.name
end
So now in my view, I can simply call current_user.account_name, and if the association changes, I only update it in one place. BUT my question is, do I test this method? If I do, how do I test it without any mystery guests?
I agree there is nothing wrong with current_user.account.name - while Sandi Metz would tell us "User knows too much about Account" this is kind of the thing you can't really avoid w/ Active Record.
If you found you were doing a lot of these methods all over the User model you could use the rails delegate method:
delegate :name, :to => :account, :prefix => true
using the :prefix => true option will prefix the method in the User model so it is account_name. In this case I would assume you could write a very simple unit test on the method that it returns something just incase the attribute in account would ever change your test would fail so you would know you need to update the delegate method.
There's nothing wrong with current_user.account.name
There's no difference between calling it as current_user.account.name, or making current_user.account_name call it for you
You're probably not calling current_user in the model, like you say
You should have a spec for it if you use it
Personally I see no good reason for any of this. Just use current_user.account.name.
If you are worrying about efficiency, have current_user return a user that joins account.
This is going to be a bit off-topic. So, apologies in advance if it's not interesting or helpful.
TL;DR: Don't put knowledge of your models in your views. Keep your controllers skinny. Here's how I've been doing it.
In my current project, I've been working to make sure my views have absolutely no knowledge of anything about the rest of the system (to reduce coupling). This way, if you decide to change how you implement something (say, current_user.account.name versus current_user.account_name), then you don't have to go into your views and make changes.
Every controller action provides a #results hash that contains everything the view needs to render correctly. The structure of the #results hash is essentially a contract between the view and the controller.
So, in my controller, #results might look something like {current_user: {account: {name: 'foo'}}}. And in my view, I'd do something like #results[:current_user][:account][:name]. I like using a HashWithIndifferentAccess so I could also do #results['current_user']['account']['name'] and not have things blow up or misbehave.
Also, I've been moving as much logic as I can out of controllers into service objects (I call them 'managers'). I find my managers (which are POROs) a lot easier to test than controllers. So, I might have:
# app/controllers/some_controller.rb
class SomeController
def create
#results = SomeManager.create(params)
if #results[:success]
# happy routing
else
# sad routing
end
end
end
Now, my controllers are super skinny and contain no logic other than routing. They don't know anything about my models. (In fact, almost all of my controller actions look exactly the same with essentially the same six lines of code.) Again, I like this because it creates separation.
Naturally, I need the manager:
#app/managers/some_manager.rb
class SomeManager
class << self
def create(params)
# do stuff that ends up creating the #results hash
# if things went well, the return will include success: true
# if things did not go well, the return will not include a :success key
end
end
end
So, in truth, the structure of #results is a contract between the view and the manager, not between the view and the controller.
I'm new to Rails (I've worked in MVC but not that much) and I'm trying to do things the "right" way but I'm a little confused here.
I have a site navigation with filters Items by different criteria, meaning:
Items.popular
Items.recommended
User.items
Brand.items # by the parent brand
Category.items # by a category
The problem is that I don't know how to deal with this in the controller, where each action does a similar logic for each collection of items (for example, store in session and respond to js)
Either I have an action in ItemsController for every filter (big controller) or I put it in ItemsController BrandsController, CategoriesController (repeated logic), but neither provides a "clean" controller.
But I don't know witch one is better or if I should do something else.
Thanks in advance!
You're asking two separate questions. Items.popular and Items.recommended are best achieved in your Item model as a named scope This abstracts what Xavier recommended into the model. Then in your ItemsController, you'd have something like
def popular
#items = Item.popular
end
def recommended
#items = Item.recommended
end
This isn't functionally different than what Xavier recommended, but to me, it is more understandable. (I always try to write my code for the version of me that will come to it in six months to not wonder what the guy clacking on the keyboard was thinking.)
The second thing you're asking is about nested resources. Assuming your code reads something like:
class User
has_many :items
end
then you can route through a user to that user's items by including
resources :users do
resources :items
end
in your routes.rb file. Repeat for the other nested resources.
The last thing you said is
The problem is that I don't know how to deal with this in the controller, where each action does a similar logic for each collection of items (for example, store in session and respond to js)
If what I've said above doesn't solve this for you (I think it would unless there's a piece you've left out.) this sounds like a case for subclassing. Put the common code in the superclass, do the specific stuff in the subclass and call super.
There's a pretty convenient way to handle this, actually - you just have to be careful and sanitize things, as it involves getting input from the browser pretty close to your database. Basically, in ItemsController, you have a function that looks a lot like this:
def search
#items = Item.where(params[:item_criteria])
end
Scary, no? But effective! For security, I recommend something like:
def search
searchable_attrs = [...] #Possibly load this straight from the model
conditions = params[:item_criteria].keep_if do |k, v|
searchable_attrs.contains? k
end
conditions[:must_be_false] = false
#items = Item.where(conditions)
end
Those first four lines used to be doable with ActiveSupport's Hash#slice method, but that's been deprecated. I assume there's a new version somewhere, since it's so useful, but I'm not sure what it is.
Hope that helps!
I think both answers(#Xaviers and #jxpx777's) is good but should be used in different situations. If your view is exactly the same for popular and recommended items then i think you should use the same action for them both. Especially if this is only a way to filter your index page, and you want a way to filter for both recommended and popular items at the same time. Or maybe popular items belonging to a specific users? However if the views are different then you should use different actions too.
The same applies to the nested resource (user's, brand's and category's items). So a complete index action could look something like this:
# Items controller
before_filter :parent_resource
def index
if #parent
#items = #parent.items
else
#items = Item.scoped
end
if params[:item_criteria]
#items = #items.where(params[:item_criteria])
end
end
private
def parent_resource
#parent = if params[:user_id]
User.find(params[:user_id])
elsif params[:brand_id]
Brand.find(params[:brand_id])
elsif params[:category_id]
Category.find(params[:category_id])
end
end
I am doing something similar to these railscast episodes:
http://railscasts.com/episodes/165-edit-multiple
http://railscasts.com/episodes/52-update-through-checkboxes
The problem is that those are only trying to modify the selected models. I need to update every single model.
The first thing I found out is that id not in () does not give back everything like I expected so I had to make a special case for the empty list.
This code works, but it doesn't seem very DRY. At the very least I should be able to merge the normal case into one line.
def update_published
if params[:book_ids].empty?
Book.update_all(published: false)
else
Book.where(id: params[:book_ids]).update_all(published: true)
Book.where("id not in (?)", params[:book_ids]).update_all(published: false)
end
redirect_to books_path
end
Any ideas for improvement would be appreciated.
Why not just do the following:
def update_published
Book.update_all(published: false)
Book.where(id: params[:book_ids]).update_all(published: true)
redirect_to books_path
end
It'll be faster, and it's pretty straightforward and clean.
I finally figured it out.
def update_published
Book.update_all(["published = id in (?)", params[:book_ids]])
redirect_to books_path
end
I was trying to do something similar yesterday, but it kept giving me an error where the ? wasn't filled out and it was doing a where on my ids. Today, I finally realized I needed to wrap both parameters into an array.
One strange caveat about this is it messed up some of my specs. I was checking for false, but it was giving nil. In the database it seems to be set to false though. I changed my spec from be(false) to be_false and feel pretty safe now.
You could do it like this:
def update_published
Book.transaction do
Book.update_all(published: false)
Book.scoped.find(params[:book_ids]).update_all(published: true) #Should be Lazyloaded. Testing Now
end
end
Since it is a transaction it will be pretty fast. Also note how i use "find", and not "where". It is nicer in my opinion. Makes for slightly cleaner code.
NOTE: I would though question why you need to update every single book entity each time. Would it not be smarter to keep proper track of the published books one by one? You are bound to run into troubles if you need to pass every book ID in each time you want to update one single book. This solution isn't very scalable.
What you should have is this:
def publish_book(book)
book.published = true;
book.save!
end
Or even better:
#Book.rb
def publish
self.published = true
self.save #not 100% sure you need this. Anyone?
end
Whenever you can you (many agree) should follow the "skinny controller, fat model" approach. Meaning that you basicly put as much of the code inside the model class, and not anywhere else whenever you can spare it.
I have this loop in rails
- #companies.people.each do |person|
%p
Hello there :
= "#{person.manager.name} (#{person.manager.email})"
but i only want to print the managers name once.....but lots of people have the same manager and they are printing dupes...any idea how to not print dupes here
Wouldn't you rather do:
#companies.managers do |manager|
...
So you need to amend the underlying model (Company?) with a managers method. And whether that's done via a scope, or a model relation or alfonso's brute force answer, we don't have enough information to determine. But in any case this logic is best tucked away in the model and not exposed in the view.
class Company
scope :managers, ->(){where(manager: true)}
end
module EmployeeListViewHelper
def manager_list
Company.managers.each do |m|
content_tag(:p, "Hello There : #{m.name} #{m.email}")
end
end
end
Then just this in your view:
= manager_list
Well, it looks like you're going about this probably the wrong way. If you don't want the manager's name duplicated for each person, you might have to group people under managers.
Your view then should look hierarchical, people under the manager should be visually placed like that, as well.
You could do this with the uniq method:
#companies.people.map{|p| p.manager}.uniq
I have an admittedly ugly query to do, to find a particular role related to the current role. This line produces the correct result:
#person_event_role.event_role.event.event_roles.
joins(:mission_role).where(:mission_roles => {:title => 'Boss'}).
first.person_event_roles.first.person
(You can infer the associations from the plurality of those calls)
The only way to get this information requires a ton of knowledge of the structure of the database, but to remove the coupling... It would require filling in a bunch of helper functions in each step of that chain to give back the needed info...
I think the thing to do here is to create the helper functions where appropriate. I'm unclear what the beginning of your association chain is here, but I'd probably assign it a method #event that returns event_role.event. From there, an event has an #boss_role, or whatever makes sense semantically, and that method is
event_roles.joins(:mission_role).where(:mission_roles => {:title => 'Boss'}).first
Finally, also on the Event model, there's a #boss method, which gets
boss_roles.first.person_event_roles.first.person
So, your original query becomes
#person_event_role.event.boss
Each leg of the chain is then self-contained and easy to understand, and it doesn't require the beginning of your chain to be omniscient about the end of it. I don't fully comprehend the full reach of these associations, but I'm pretty sure that just breaking it into three or four model methods will give you the clean reading and separation of concerns you're looking for. You might even break it down further for additional ease of reading, but that becomes a question of style.
Hope that helps!
The following is by the original questioner
I think I followed this advice and ended up with:
#person_event_role.get_related_event_roles_for('Boss').first.filled_by.first
#person_event_role:
def get_related_event_roles_for(role)
event.event_roles_for(role)
end
def event
event_role.event
end
#event:
def event_roles_for(role)
event_roles.for_role(role)
end
#event_role:
scope :for_role, lambda {|role| joins(:mission_role).where(:mission_roles => {:title => role})}
def filled_by
person_event_roles.collect {|per| per.person}
end