Nested (and Unested at-same-time) Resources better approach - ruby-on-rails

I have two models like that
class Plan < ActiveRecord::Base
belongs_to :profile
And
class Profile < ActiveRecord::Base
has_many :plans
And routes like: (I need to)
resources :profiles do
resources :plans
end
resources :plans
So, following up ruby-on-rails - Problem with Nested Resources, I've made my PLANS index controller like this, to works NESTED and UNESTED at same time (the only way I've found for now):
def index
if params.has_key? :profile_id
#profile = Profile.find(params[:profile_id])
#plans = #profile.plans
else
#plans = Plan.all
end
There is a cleaner approach to this?
I have another models in this situation, and putting all actions, in all controllers to behave like this is cumbersome.

You gave me an idea:
models/user.rb:
class User < ActiveRecord::Base
has_many :posts
attr_accessible :name
end
models/post.rb:
class Post < ActiveRecord::Base
belongs_to :user
attr_accessible :title, :user_id
end
controllers/posts_controller.rb:
class PostsController < ApplicationController
belongs_to :user # creates belongs_to_user filter
# #posts = Post.all # managed by belongs_to_user filter
# GET /posts
# GET /posts.json
def index
respond_to do |format|
format.html # index.html.erb
format.json { render json: #posts }
end
end
end
And now the substance:
controllers/application_controller.rb:
class ApplicationController < ActionController::Base
protect_from_forgery
def self.belongs_to(model)
# Example: model == :user
filter_method_name = :"belongs_to_#{model}_index" # :belongs_to_user_index
foreign_key = "#{model}_id" # 'user_id'
model_class = model.to_s.classify # User
class_eval <<-EOV, __FILE__, __LINE__ + 1
def #{filter_method_name} # def belongs_to_user_index
if params.has_key? :'#{foreign_key}' # if params.has_key? :user_id
instance_variable_set :"##{model}", # instance_variable_set :"#user",
#{model_class}.find(params[:'#{foreign_key}']) # User.find(params[:user_id])
instance_variable_set :"#\#{controller_name}", # instance_variable_set :"##{controller_name}",
##{model}.send(controller_name.pluralize) # #user.send(controller_name.pluralize)
else # else
instance_variable_set :"#\#{controller_name}", # instance_variable_set :"##{controller_name}",
controller_name.classify.constantize.all # controller_name.classify.constantize.all
end # end
end # end
EOV
before_filter filter_method_name, only: :index # before_filter :belongs_to_user_index, only: :index
end
end
The code is not complex to understand if you have notions of Ruby metaprogramming: it declares a before_filter which declares the instance variables inferring the names from the controller name and from the association. It is implemented just for the index actions, which is the only action using the plural instance variable version, but it should be easy to write a filter version for the other actions.

Related

CanCanCan refuses to load collection for :create

In my PromoCodesController I have this code:
load_and_authorize_resource :restaurant, find_by: :permalink
load_resource :discount, through: :restaurant
load_resource :promo_code, collection: [:create], through: :discount
It should be good since in #index, it loads the collection #promo_codes and in #create it loads #promo_code.
But it does not load the collection #promo_codes in #create. Where is the problem? In the documentation it says:
:collection argument: Specify which actions are resource collection actions in addition to :index.
Thank you
It's not working because Cancancan's method load_resource (controller_resource_loader.rb) assumes
only one resource variable to be set at a time: either resource_instance or collection_instance.
Your load_resource collection: [:create] can load #promo_codes in #create action via monkey patch to CanCan::ControllerResourceLoader:
# config/initializers/cancan.rb
module CanCan
module ControllerResourceLoader
def load_resource
return if skip?(:load)
# Original condition has been split into two separate conditions
if load_instance?
self.resource_instance ||= load_resource_instance
end
if load_collection?
self.collection_instance ||= load_collection
end
end
end
end
The common way in which this patch works is a create form integrated into index action:
class TicketsController < ActionController::Base
load_and_authorize_resource collection: [:create]
def index
#ticket = Ticket.new
end
def create
if #ticket.valid?
#ticket.create_for_user! params[:message]
redirect_to ticket_path(#ticket)
else
# #tickets are also defined here
render :index
end
end
end

Rails 5, active_model_serializers, where order includes and why I can't in the 'show' method of controller?

I'm using Rails 5, gem 'active_model_serializers' and I have this situation:
controllers/leagues_controller.rb:
class LeaguesController < ApplicationController
before_action :authenticate_user
before_action :set_league, only: [:show, :update, :destroy]
# GET /leagues
def index
#leagues = League.all
render json: #leagues
end
# GET /leagues/1
def show
render json: #league, include: ['teams'] # Here I need some way to order
end
private
# Use callbacks to share common setup or constraints between actions.
def set_league
#league = League.find(params[:id])
end
models/league.rb:
class League < ApplicationRecord
has_many :teams
end
serializers/league_serializer.rb:
class LeagueSerializer < ActiveModel::Serializer
attributes :id, :name, :address, :teams_num
has_many :teams
has_many :menus, serializer: ProductSerializer
end
I need to order teams by "goals" or other attribute in the team model. How to do that? And where? I can't do this below?
def show
render json: #league, include: ['teams'], :order => 'goals DESC'
end
What is the logic? I have to order in my serializer? In my model?
Why I can't use something like this? It gives to me "undefined method includes". Why?
def show
#league = #league.includes(:team).order(goals: :desc)
render json: #league
end

Ruby on Rails to_json function add parameter to methods for render in favorites or not?

How do I add parameters to methods for rendering the current place in favorites?
I tried this:
class Place < ActiveRecord::Base
has_and_belongs_to_many :users
def in_fav(user)
if user.places.include?Place.find(id)
return true
else
return false
end
end
end
class User < ActiveRecord::Base
has_and_belongs_to_many :places
end
class PlacesController < ApplicationController
places = Place.all
user = User.first
render json: {desc:true, status:1; data: places}.to_json(:methods => :in_fav(user))
end
I find same problem here
attr_accessor :current_user
def is_favorited_by_user?(user=nil)
user ||= current_user
end
#drops.current_user = current_user
render :json => #drops.to_json(:methods => :is_favorited_by_user?)
I don't understand current_user - it's assocciations? and how to use method current_user for collection #drops

Rails: Better way to create variables for views

I am totally new to Ruby, and Rails. Currently, I am using helper methods. How can I write the same code as this in my Model 'User' so as to access all these variables from controller and view?
Writting code this way in helper is 100% functional:
module HomeHelper
def init(user_id)
#friends = Array.new
#followers = Array.new
#user = User.find_by_id(user_id) #Get User
#friends = #user.users #Get all his friends
#
#statuses = Array.new #
#friends.each do |friend| #
#statuses += friend.statuses #Get all statuses for 'a' friend, then loop
end #
#statuses += #user.statuses #
#statuses = #statuses.sort_by {|status| status.created_at}.reverse!
#friendsof = Array.new
#filtered_friendsof = Array.new
#friends.each do |friend|
#friendsof += friend.users
end
#friendsof.each do |friendof|
unless (#friends.include?(friendof))
if #user != friendof
#filtered_friendsof << friendof
end
end
end
end
#filtered_friendsof = #filtered_friendsof.uniq
end
Controller
class HomeController < ApplicationController
def index
#user_id=3
end
end
Model:
class User < ActiveRecord::Base
has_many :statuses
has_and_belongs_to_many(:users,
:join_table => "user_connections",
:foreign_key => "user1_id",
:association_foreign_key => "user2_id")
#has_many :user_connections
end
Home controller:
class HomeController < ApplicationController
def index
#user = User.find(3)
end
end
User model:
class User < ActiveRecord::Base
has_many :statuses
has_and_belongs_to_many :friends,
:class_name => 'User'
:join_table => "user_connections",
:foreign_key => "user1_id",
:association_foreign_key => "user2_id"
def combined_statuses
(friends.map(&:statuses) + statuses).flatten.
sort_by {|status| status.created_at}.reverse!
end
end
Now, you don't need your helper method and in your view you can use:
#user.friends # instead of #friends
#user.combined_statuses # instead of #statuses
I'll let you figure out the rest, but I hope you get the general idea of pushing the logic into the model.
Most of that logic belongs in the User model. Nothing else needs to be actually doing those computations, and the User model has access to all the relevant pieces. There are additionally several other improvements that can be made. I'll try to add comments below to indicate these improvements.
Model
class User < ActiveRecord::Base
has_many :statuses
has_and_belongs_to_many :friends, # now you can just say user.friends
:class_name => 'User', # makes more sense semantically
:join_table => "user_connections",
:foreign_key => "user1_id",
:association_foreign_key => "user2_id"
def friends_statuses
(friends.map(&:statuses).flatten + statuses).sort_by!(&:created_at).reverse
# Ruby has many great methods for Arrays you should use.
# You can often avoid instantiating variables like the empty Arrays you have.
end
def second_order_friends
(friends.map(&:friends).flatten.uniq - friends) - [self]
end
end
Controller
class HomeController < ApplicationController
def index
user = User.find(7) # how do you decide which user you're displaying things for?
# this might be better off in 'show' rather than 'index'
# here you can call all the methods you have for 'User', such as:
# user.friends, user.statuses, user.friends_statuses, user.second_order_friends
# to make things accessible in the view, you just need an #variable, e.g.:
#friends = user.friends
#latest_statuses = user.friends_statuses.first(10)
end

Routing: Combining resources and nested resources

/items should list all items
/user/items should list only items for the current user
I have defined a relationship between users and items, so current_user.items works fine.
My question is:
How does the items#index action know whether it has been called directly or via a user resource in order to know what to populate #items with?
# routes.rb
resources :items
resource :user do
resources :items
end
# items_controller.rb
class ItemsController < ApplicationController
def index
#items = Item.all
end
end
# users_controller.rb
class UsersController < ApplicationController
def show
#user = current_user
end
end
It looks hacky, but it was my first idea :)
resource :user do
resources :items, :i_am_nested_resource => true
end
class ItemsController < ApplicationController
def index
if params[:i_am_nested_resource]
#items = current_user.items
else
#items = Item.all
end
end
end
Or you can go brute way: parse your request url :).
I solved this by separating the code into two controllers (one namespaced). I think this is cleaner than adding conditional logic to a single controller.
# routes.rb
resources :items
namespace 'user' do
resources :items
end
# items_controller.rb
class ItemsController < ApplicationController
def index
#items = Item.all
end
end
# user/items_controller.rb
class User::ItemsController < ApplicationController
def index
#items = current_user.items
end
end

Resources