Rails 4 concern errors - ruby-on-rails-4

I've notice the new features of concern in rails4, and I read the document of it in http://edgeapi.rubyonrails.org/classes/ActiveSupport/Concern.html.
But it seems doesn't work as I expect. Here is my code of my /models/concerns/current_user.rb.
require 'active_support/concern'
module CurrentUser
extend ActiveSupport::Concern
module ClassMethods
def accessor_current_user
attr_accessor :current_user
end
end
end
class ActiveRecord::Base
include CurrentUser
end
You probably notice that last 3 lines of my code, that's because I want all my models can call the method and I think it may be a good way to achieve that. But when i start rails server, it just cannot call the accessor_current_user method. So I am confusing about this. I really don't know the reason. Hope someone can help me. :)
Update!
Finally, i found it's maybe a good way to create a ActiveRecord::Base class in the initializers fold, and then include the CurrentUser in the class.

Try:
require 'active_support/concern'
module CurrentUser
extend ActiveSupport::Concern
included do
attr_accessor :current_user
end
module ClassMethods
end
end
class ActiveRecord::Base
include CurrentUser
end
Then you should be able to do something like this:
foo = Foo.new
foo.current_user
Where Foo is:
class Foo < ActiveRecord:Base
include CurrentUser
end

The way you are calling accessor_current_user is wrong. This way you can call ActiveRecord::Base.accessor_current_user method and it will execute the code without any error. And then you can call
ar = ActiveRecord::Base.new
ar.current_user = "foo"
ar.current_user # this will return 'foo'
But the correct way to implement the attr_accessor using ActiveSupport::Concern is
module CurrentUser
extend ActiveSupport::Concern
included do
attr_accessor :current_user
end
end
If you are writing your code in rails, then you don't need require 'active_support/concern'. Please refer http://blog.neerajk.com/articles/2014-11-26-is-active-support-concern-really-another-concern/

Related

How to structure namespaced modules

I am having trouble with Ruby class (constant) lookup within the context of a Rails engine gem.
I have a gem MyGem that is a Rails engine. It defines non-namespaced models that are expected to be overridden by the MainApp that would include the gem and namespaced modules, which are included in gem's and main_app's models to have a DRY way of defining reusable code.
Here is a sample code structure:
Two models
# in /app/models/user.rb
class User < ActiveRecord::Base
include MyGem::User::CommonExt
end
# in /app/models/comment.rb
class Comment < ActiveRecord::Base
include MyGem::Comment::CommonExt
end
Their two modules
# in /app/models/concerns/my_gem/user/common_ext.rb
module MyGem::User::CommonExt
def load_comment(id)
return Comment.find(id)
end
end
# in /app/models/concerns/my_gem/comment/common_ext.rb
module MyGem::Comment::CommonExt
def load_user(id)
return User.find(id)
end
end
Now, if I call
User.new.load_comment(1)
I get undefined method #find for MyGem::Comment::Module
I think I understand why this is happening - in the context of #load_comment definition, which is namespaced under MyGem, Comment constant lookup returns MyGem::Comment, rather than the more distant ::Comment
I would prefer not to have to prepend every model instance with ::.
Is there a file structure, model/class definition or configuration change I could use to make a call to Comment return the model Comment, not the MyGem::Comment module?
I would use inheritance instead of mixin in this case.
So in your gem/engine you could define your common class similar to this:
module MyGem
module Common
class Base < ActiveRecord::Base
# common functionality goes here
def load(record_type, id)
record_type.find(id)
end
end
end
end
Then in your main_app code:
class User < MyGem::Common::Base
...
end
Now you could do this:
User.new.load(Comment, 1)
This violates the Law of Demeter but hopefully it illustrates the point.
Doing it like this is DRY and has the added benefit that it prevents your gem from having to know about classes which are outside it's own scope.

correct way to use routes.rb namespace rails?

I'm trying to create a back-end area to my application, so I've created a folder named backend and backend_controller.rb inside it. I need the folder because the backend area will have multiple folders, so it's better-separated from my others.
my routes.rb looks like:
namespace :backend do
get 'index'
end
my backend_controller.rb:
class BackendController < ApplicationController
def index
end
end
But in this mode Rails will search for my backend_controller.rb inside the controllers folder, not in controller>backend. I've tried many variations, and I get routing errors.
So what is correct way to do that? To set the path /backend to index action instead of /backend/index?
Thanks
What i've done:
based on all answers, principally the one from Cyril DD
I've created the backend_controller.rb on the app/controller folder and in the sub-folder app/controller/backend i created the static_pages_controller.rb and all files looks like this:
app/controllers/backend_controller.rb:
class BackendController < ApplicationController
end
app/controller/backend/static_pages_contter.rb:
class Backend::StaticPagesController < BackendController
def dashboard
end
end
routes.rb:
namespace :backend do
resource :static_pages, path: '', only: [] do
root to:'static_pages#dashboard'
end
this works fine, but cause i'm newbie on rails i must ask. This is a good or a conventional way to do that? to administrate the permissions which user can see on the backend i use the backend_controller.rb right? and at last wy i must use resource: instead just get ''
Answering your question
Alright, namespace :something is a shorthand for scope 'something', module: 'something', as: 'something'
Now your declaration is very ambiguous, because you don't specify a controller. Typical declarations look like (assume you have a controller controllers/backend/some_resources_controller.rb and you want to generate default routes)
namespace :backend do
resources :some_resources
end
Now what you did
namespace :backend
get 'index'
end
is really ambiguous and I'm not surprised it doesn't do what you want. Basically you just tell rails to "look inside subfolder 'backend' and define the route 'index'". oO ? Which file/controller are we even talking about ?
What is your backend_controller.rb supposed to do ? Is it some kind of Control Panel ? Dashboard ? If so you're probably gonna have a lot of non-CRUD actions, but anyways you should go for the following syntax
namespace :backend
# Below line of code will auto-generate the `index` for /backend/backend_controller
resource :backend, only: [:index], path: '' do # we need " path: '' " otherwise we'll have https://xxx/backend/backend/dashboard
# If you have non-CRUD actions, put them here !
get 'dashboard' # https://xxx/backend/dashboard
...
end
# However, this will create URLs like "https://xxx/backend/dashboard", etc.
# If you want to redirect https://xxx/backend/ to your backend_controller#index, use root
root to: 'backend#index' # https://xxx/backend/
end
Last thing as mentionned by other guys, when you namespace a file like your Backend_controller inside /backend/ subfolder, you must rename the class like (/controllers/backend/backend_controller)
class Backend::BackendController < ApplicationController
Remark : if you only have like one or two controller actions, instead of using the resource method, you can declare singular resources
namespace :backend do
root to: 'backend#dashboard'
get 'dashboard', to: 'backend#dashboard' # singular resource
end
An Example of what you may actually really want to do...
I'm not sure you are clear yourself about what you want to do. As an example, here is my architecture
Files
/controllers/application_controller.rb
/controllers/backend_controller.rb
/controllers/backend/static_pages_controller.rb
/controllers/backend/***.rb
The class /controllers/backend_controller.rb will not serve any action, but will override ApplicationController to tune it for backend access (but maybe you don't need to do so)
class BackendController < ApplicationController
# Do you need to change user_access method ? Or any other backend-wide config ?
# If so put this config here, otherwise leave empty
end
Now for every file that goes in the /backend/ subfolder, I inherit the backend_controller
class Backend::StaticPagesController < BackendController
def index
end
# Note : if your index is some kind of dashboard, instead I would declare
def dashboard
end
end
class Backend::SomeResourcesController < BackendController
...
end
Routes
namespace :backend do
root to 'static_pages#index' # https://xxxx/backend/
resource :static_pages, only: [:index], path: '' # https://xxxx/backend/index
resources :some_resources
end
If you choose the dashboard solution in your controller, write instead :
namespace :backend do
root to: static_pages#dashboard # https://xxxx/backend/
resource :static_pages, path: '', only: [] do
get 'dashboard' # https://xxxx/backend/dashboard
end
resources :some_resources
end
Then it's simply
# routes.rb
Rails.application.routes.draw do
namespace :backend, shallow: true do
resource :backend, path:''
end
end
Then in your app/controllers/backend/backend_controller.rb, it'd look like this.
class Backend::BackendController < ApplicationController
def index
end
end
When I use rake routes it shows
Prefix Verb URI Pattern Controller#Action
backend_backends GET /backend(.:format) backend/backends#index
Hope this helps.

Where should I place bestseller? method - in model or somewhere else?

I've got some simple model:
class Product < ActiveRecord::Base
has_many :categories
end
Now I would like to check in some service, if product is a bestseller and do other action for it:
class ProductService
def remind
Product.all.each do |product|
puts product unless bestseller?
end
end
end
So now what is the best place to put the bestseller? method - inside model or in the service as private method?
In future it may be used in some other services or actions.
Do you think the model is right place to put this method there?
Example of bestsellers method (bestsellers are picked manualy by adding to category 'bestsellers':
def bestseller?(product)
product.categories.include?(BESTSELLER_CATEGORY_ID)
end
or
def bestseller?(product_id)
Category.find(BESTSELLER_CATEGORY_ID).products.include?(product_id)
end
I still haven't decided which one is better (both do the same)

Calling super in overriden scope defined in concern

I'm looking to chain an additional query onto a scope in a model. The scope is defined in a concern.
module Companyable
extend ActiveSupport::Concern
included do
scope :for_company, ->(id) {
where(:company_id => id)
}
end
end
class Order < ActiveRecord::Base
include Companyable
# I'd like to be able to do something like this:
scope :for_company, ->(id) {
super(id).where.not(:status => 'cancelled')
}
end
However, that understandably throws a NameError: undefined method 'for_company' for class 'Order'
Here's the solution I came up with in my case:
Rather than scope, just go with a regular class method since scope is just "syntactic sugar" for a class method. This is easier to deal with when you need to override using super. In your case it would look like this:
module Companyable
extend ActiveSupport::Concern
module ClassMethods
def for_company(id)
where(:company_id => id)
end
end
end
class Order < ActiveRecord::Base
include Companyable
def self.for_company(id)
super(id).where.not(:status => 'cancelled')
end
end

Is "proc" required with conditional before_action/before_filter?

Behold, a before_filter:
class ThingController < ApplicationController
before_filter :check_stuff, :if => proc {Rails.env.production?}
end
During a recent code review, I was asked, "Is the proc is required for this to work?" The answer appears to be 'yes', but it's a reasonable question, and I had intended to answer it by referring to the Rails docs or guides or something on the use of conditionals with before_filter (now an alias of before_action).
I couldn't find any. The Action Controller Guide mentions :only/:except, but not :if/:unless.
Failing that, is there somewhere in the code I can point to that covers this? It's mentioned briefly here, but that's more about how :only and :except are handled, rather than :if or :unless.
Found it on Rails Guides: http://guides.rubyonrails.org/active_record_callbacks.html#conditional-callbacks
Turns out a Proc isn't always required for it to work.
the :if and :unless options, which can take a symbol, a string, a Proc or an Array.
So in your case you could probably get away with
before_action :check_stuff, if: "Rails.env.production?"
Finding things in Rails documentation can be a pain sometimes, but at least questions like this make things easier to find over time since StackOverflow is well indexed and has high search rankings.
From Rails 5.2 onwards, the current accepted answer is no longer be valid, and passing a string to the conditional will fail.
DEPRECATION WARNING: Passing string to :if and :unless conditional options is deprecated and will be removed in Rails 5.2 without replacement.
Going forward, a proc is now the best way to add a conditional like in the original question:
class ThingController < ApplicationController
before_action :check_stuff, :if => proc {Rails.env.production?}
end
i have done this on my code while ago. I hope that example helps to you. If you can use if statement but that should point to another method like I did here.
class Admin::ArticlesController < ApplicationController
before_filter :deny_access, :unless => :draft_and_admin?
def show
#article = Article.find(params[:id])
end
protected
def draft_and_admin?
Article.find(params[:id]).draft? && current_user.admin?
end
end
I would recommend using staby lambda. If you want know, WHY?
Please read this
class ThingController < ApplicationController
before_action :check_stuff, if: -> { Rails.env.production? }
end
which is almost equivalent to Upvote Me's answer.
Adding a method to check the if/unless conditions for a before_action should be the best way as this way you can easily accommodate any additional changes in the before_action conditions easily in future:
class ThingController < ApplicationController
before_filter :check_stuff, if: :check_stuff?
def check_stuff
end
private
def check_stuff?
Rails.env.production?
end
end