I have two models
thing.rb
has_one :subthing
subthing.rb
belongs_to :thing
And am routing with
resources :thing do
resource :subthing
end
resources :subthing
However, my show method on my controller
def show
#subthing = Subthing.find(params[:id])
end
when I visit
http://example.org/things/1/subthing
is giving me an error
Couldn't find Subthing without an ID
I sort of feel like this should be being taken care of by the framework... i.e it should work out that the relevant Subthing is the one that belongs to Thing.
Am I missing something or can I not use the same controller method here for Subthings on their own and Subthings when they're part of a thing.
Or do I need to explicitly tell the controller for each potential association. i.e.
def show
if params[:thing_id].present?
#subthing = #thing.find(params[:thing_id]).subthing
else
#subthing = Subthing.find(params[:id])
end
end
If you are intending to use the same SubthingsController for both the nested resource as well as the top level resources, then yes, you need to do as proposed:
def show
if params[:thing_id].present?
#subthing = #thing.find(params[:thing_id]).subthing
else
#subthing = Subthing.find(params[:id])
end
end
But your controller gets complicated very quick and its not worth it. You are better off re-defining your routes or use two separate controllers.
Related
An Order has_many AItems and BItems. As you can tell, the items are basically identical but with an important business reason for categorizing them separately. Wondering what's the best strategy to DRY this up. I realize this is a little opinionated... but hoping to get some clear points of view and arguments.
View code
Currently I'm using a partial. Like this:
class AItemsController
def new
end
end
class BItemsController
def new
end
end
# view files layout
> views
> AItems
> new.html.erb
> BItems
> new.html.erb
# routing
get '/AItems/new'
get '/BItems/new'
# code for /views/AItems/new.html.erb
<%= render "layouts/items_new", object: "AItems" %>
# code for /views/BItems/new.html.erb
<%= render "layouts/items_new", object: "BItems" %>
I'm wondering if it'd be easier to get rid of the partial entirely and just do parameters like this:
class AItemsController
def new
end
end
class BItemsController
def new
end
end
# view files layout
> views
> Items
> new.html.erb
# routing
get '/items/new/:type'
# code for /views/Items/new.html.erb
# code from partial evaluating the param[:type] instead of a passed object
Controller code
Currently everything is duplicated... (I haven't made any attempt at DRYing yet) as in it looks like this (very illustrative, the point is to just show that short of the naming conventions literally everything is basically the same):
class AItemsController
def new
#items = AItems.joins(:order).where("orders.status_id IS NULL")
end
def do_something
a_items_params.each do |item_params|
key_var = item_params[:some_attribute]
...
end
end
end
class BItemsController
def new
#items = BItems.joins(:order).where("orders.status_id IS NULL")
end
def do_something
b_items_params.each do |item_params|
key_var = item_params[:some_attribute]
...
end
end
end
I haven't DRYed this yet because I'm a little conflicted as to how. Examples below are illustrative, forgive if the code isn't exact, but hopefully you get the gist.
Solution A: In one way, I could keep the action definitions in each controller, and then have the code within the action pull from a shared concern:
class AItemsController
include SharedCode
def new
shared_new
end
def do_something
shared_do_something
end
end
Solution B: abstract away the action definitions to the shared concern:
class AItemsController
included SharedAction
shared_action("AItems")
end
Solution C: route everything to a singular controller and again use params to differentiate (passed from view)
class ItemsController
def new
item_type = params[:item_type]
end
def do_something
item_type = params[:item_type]
end
end
Model code
This one is a little more cut and dry, and I don't need a ton of feedback here, I will just used shared concerns for key methods/ callback.
Obviously the answer for one will affect the other. For example if everything routes through a single controller, then I'll have a single view with parameters rather than a partial approach. But because the controller has multiple DRYing options, there's still room for debate.
If you've read this far, I will happily take angry comments about how this question is too loosely defined in exchange for at least some thoughts on what you would do. What's more understandable for you if you were taking over my code?
I am trying to learn and the best way to do that is to solicit multiple points of view and pros and cons to weigh out.
Check out the InheritedResources Gem: https://github.com/josevalim/inherited_resources
Inherited Resources speeds up development by making your controllers
inherit all restful actions so you just have to focus on what is
important. It makes your controllers more powerful and cleaner at the
same time.
Or the Responders Gem, a replacement to Inherited Resources: https://github.com/plataformatec/responders
A set of responders modules to dry up your Rails 4.2+ app.
I've got STI like this:
class Post
end
class Post::Confirmed < Post
end
class Post::Draft < Post
def confirm!
becomes Post::Confirmed
end
end
...# somewhere in controller
# POST /posts/1/confirm
# POST /posts/1/confirm.json
def confirm
#post = Post::Draft.first
#post = #post.confirm! # this is the only way I can reload #post with Post::Confrmed
end
Is it somehow possible to make:
#post.confirm! # I want this #post(Post::Draft) to become Post::Confirmed without reassigning
Or is it just nor RoR way?
Thanks in advance!
The pattern I've found that works best here is having a datetime type field that records when the record was flagged.
For example:
def confirm!
self.confirmed_at = DateTime.now
self.save!
end
Then you can tell when something was confirmed. This comes in especially handy for when you have a situation where something will be flagged but isn't yet, such as setting a publishing date in the future.
Although it might seem a little annoying to not have your STI bag of tricks available, STI is not always the appropriate tool. Generally STI is to differentiate between similar but different models that have a lot of commonality or are used in a common context. It's not supposed to be used to handle different states of a singular model.
What you want in that case is a state-machine type pattern.
Have STI classes:
class Page < ActiveRecord::Base
belongs_to :user
end
class FirstTypePage < Page
end
class SecondTypePage < Page
end
Controllers for each class,
class PageController < AplicationCorroller
end
class FirstTypePageController < PageController
end
class SecondTypePageController < PageController
end
And routings:
resources :user
resource :page
end
How to handle FirstTypePage by FirstTypePageController, SecondTypePage by SecondTypePageController on single path?
i.e.
user/1/page/2 is handled by:
FirstTypePageController if "page 2" type is "FirstTypePage",
and by SecondTypePageController if "page 2" type is "SecondTypePage" ?
UPDATE: My solution:
match 'user/:user_id/page/:action',
:controller=>'page/first_type_page',
:constraints=>PageConstraints.new('FirstTypePage')
match 'user/:user_id/page/:action',
:controller=>'page/second_type_page',
:constraints=>PageConstraints.new('SecondTypePage')
class PageConstraints
##cache ||= {}
def initialize o_type
##mutex = Mutex.new
#o_type = o_type
end
def matches?(request)
user_id = request.params[:user_id]
#add Mutex lock here
unless page_type = ##cache[user_id]
page_type = User.find(user_id).do_some_magik_to_suggest_type
##cache[page_id] = page_type
##cache.shift if ##cache.size > 1000
end
page_type == #o_type
end
end
I think this solution will work fast on a small amount of page types, and we can manage memory size, used for routings on a large amount of pages
I can see one option to do that - preload all pages in the routes.rb and define special routes for each page.
resources :users do |user|
Page.all do |page|
if page.first_type?
# ... routes to first_type_page_controller
else
# ...
end
end
Another solution could be to use strategy patter in the PageController (no need to use FirstTypePageController and other).
pages_controller.rb:
before_filter :choose_strategy
def show
#strategy.show
end
private
def choose_strategy
#strategy = PagesControllerStrategy.new(self, page)
end
def page
#page ||= Page.find params[:id]
end
pages_controller_strategy.rb:
class PagesControllerStrategy
def initialize(controller, page)
#controller = controller
#page = page
end
def show
# do what you what with controller and page
end
end
However, I'd suggest you to split the behavior on the view level only:
show.html.haml:
- if page.first_type?
= render 'pages/first_type'
- else
// ...
EDIT:
I just found another solution, that could help you - custom constraints.
http://railsdispatch.com/posts/rails-3-makes-life-better
I'm not sure if that works in your case, but I think it is worth to play with routes more.
you can do it with before_filter, but separating STI models into different controllers isn't good solution. I totally agree with next quote
This may not always apply, but I have yet to see a case where STI works well with multiple controllers. If we are using STI, our objects share a set of IDs and attributes, and therefore should all be accessed in basically the same way (find by some attribute, sort by some attribute, restrict to administrators, etc). If presentation varies greatly we may want to render different model-specific views from our controller. But if object access varies so much that it suggests separate controllers, then STI may not have been the correct design choice.
took here http://code.alexreisner.com/articles/single-table-inheritance-in-rails.html
I have a model that looks something like this:
class Comment < ActiveRecord::Base
...
#allow editing comment if it is moderated and the user passed-in
#is the one that owns the comment
def can_edit?(user)
moderated? and user.Type == User and user.id == self.user_id
end
...
end
And a call in a view:
<%= link_to 'Show Comment', #comment if #comment.can_show?(current_user) %>
I need to write many such methods in many different models - sort of validation checks to see if current_user is allowed to
do something on a model.
But it feels cumbersome - especially the need to check that the passed-in user is indeed a object of type User.
What's a clean, best-practice way to do this sort of thing? Am I on the right track? (i.e. should I be adding such methods to a model or somewhere else)
Note
I am using scoped queries to get the comments and other models, but in some cases I cannot scope the query so I have to use the can_xxxx? methods
Ps. Is what I'm doing considered a "fat model"?
Create a module containing all the authorization methods and include the module to all the classes requiring authorization.
Add a file called authorization.rb to app/models directory.
module Authorization
def can_edit?(user)
moderated? and user.is_a?(User) and user.id == self.user_id
end
def self.included(base)
base.send(:extend, ClassMethods)
end
module ClassMethods
# add your class methods here.
end
end
Add a file called authorization.rb to config/initializers directory.
%w(
Comment
Post
).each do |klass|
klass.constantize.include(Authorization)
end
Now Comment and Post models will have all the authorization methods.
Other approach is to use your current named_scope.
class Post
named_scope :accessible, lambda { |user|
{
:conditions => { :user_id => user.id, :moderated => true}
}
}
end
Post controller actions
class PostsController
def index
#posts = Post.acessible(current_user)
# process data
end
def show
# throws record not found when the record is not accessible.
#post = Post.acessible(current_user).find(params[:id])
# process data
end
end
I like this approach as it uses the same logic for accessing an array of objects or a single object.
You can add the named_scope to the module to avoid repeated definitions:
module Authorization
def self.included(base)
base.named_scope :accessible, lambda { |user|
{
:conditions => { :user_id => user.id, :moderated => true}
}
}
end
module ClassMethods
# add your class methods here.
end
end
Make sure to include the module in required classes as suggested earlier.
I don't think what you're doing is necessarily wrong. I see three ways to simplify, though:
1) track self.user as well as self.user_id. Then you can say:
def can_show?(user)
moderated ? and user == self.user
end
Note, this might add overhead either with DB lookup times and/or memory footprint.
2) Use #is_a? in order to check ancestry and not just class equality:
def can_show?(user)
moderated ? and user.is_a?( User ) and user.id == self.user_id
end
3) If passing in a non-user is wrong, you might want to raise an error when this happens:
def can_show?(user)
raise "expected User, not #{ user.class.to_s }" unless user.is_a?(User)
moderated ? and user.id == self.user_id
end
As for Q2, I haven't heard the terminology "fat model." Is it referenced anywhere in particular?
Re: fat model and skinny controller
This is the idea of pushing logic into the model rather than having it in the controller (or worse, the view).
A big benefit is to help with testing; also the focus of placing more logic in the model rather than in the controller. Remember that it is not uncommon to have controllers work with multiple models.
Putting the logic into a model rather than a controller often means that the business rules are being baked into the model--which is exactly where they belong.
A possible downside is that any information available to the controller that is not available in the model needs to be explicitly passed into the model's methods or "set" using a model's instance variables.
Your example of needing to pass the current user into the model illustrates the issue.
Overall though, I and many others have found that fat models tend to work out better than not.
I'm working on a multi-user, multi-account App where 1 account can have n users. It is very important that every user can only access info from its account. My approach is to add an account_id to every model in the DB and than add a filter in every controller to only select objects with the current account_id. I will use the authorization plugin.
Is this approach a good idea?
What is the best way to always set the account_id for every object that is created without writing
object.account = #current_account
in every CREATE action? Maybe a filter?
Also I'm not sure about the best way to implement the filter for the select options. I need something like a general condition: No matter what else appears in the SQL statement, there is always a "WHERE account_id = XY".
Thanks for your help!
This is similar to a User.has_many :emails scenario. You don't want the user to see other peoples emails by changing the ID in the URL, so you do this:
#emails = current_user.emails
In your case, you can probably do something like this:
class ApplicationController < ActionController::Base
def current_account
#current_account ||= current_user && current_user.account
end
end
# In an imagined ProjectsController
#projects = current_account.projects
#project = current_account.projects.find(params[:id])
I know, I know, if you access Session-variables or Instance variables in your Model you didn't understand the MVC pattern and "should go back to PHP". But still, this could be very useful if you have - like us - a lot of controllers and actions where you don't always want to write #current_account.object.do_something (not very DRY).
The solution I found is very easy:
Step 1:
Add your current_account to Thread.current, so for example
class ApplicationController < ActionController::Base
before_filter :get_current_account
protected
def get_current_account
# somehow get the current account, depends on your approach
Thread.current[:account] = #account
end
end
Step 2:
Add a current_account method to all your models
#/lib/ar_current_account.rb
ActiveRecord::Base.class_eval do
def self.current_account
Thread.current[:account]
end
end
Step 3: Voilá, in your Models you can do something like this:
class MyModel < ActiveRecord::Base
belongs_to :account
# Set the default values
def initialize(params = nil)
super
self.account_id ||= current_account.id
end
end
You could also work with something like the before_validation callback in active_record and then make with a validation sure the account is always set.
The same approach could be used if you always want to add the current_user to every created object.
What do you think?
To answer your second question, check out the new default_scope feature in Rails 2.3.
I understand that you don't want to bother about scoping you account all time. Lets be honest, it's a pain in the a**.
To add a bit magic and have this scoping done seamlessly give a look at the following gem
http://gemcutter.org/gems/account_scopper
Hope this helps,
--
Sebastien Grosjean - ZenCocoon