devise: instance the current_user using single table inheritance - ruby-on-rails

I am using rails 3.0.9 and devise for authentication. Now I'm trying to use single table inheritance because I need to use polymorphism, so I have two classes: UserType1 and UserType2, which inherit from User class. I need that Devise instance correctly the current_user depending the type of user.
For example,
class User < ActiveRecord::Base
#devise and other user logic
end
class UserType1 < User
def get_some_attribute
return "Hello, my type is UserType1"
end
end
class UserType2 < User
def get_some_attribute
return "Hello, my type is UserType2"
end
end
In controller
class MyController < ApplicationController
def action
#message = current_user.get_some_attribute #depending the type using polymorphism
render :my_view
end
end

it's exactly what you need : http://blog.jeffsaracco.com/ruby-on-rails-polymorphic-user-model-with-devise-authentication
you need to override the sign in path method in your application controller, hope it help.

You will need to add get_some_attribute method inside User model
Module User < ActiveRecord::Base
#devise and other user logic
def get_some_attribute
#You can put shared logic between the two users type here
end
end
then, to override it in the user sub types, like this:
Module UserType1 < User
def get_some_attribute
super
return "Hello, my type is UserType1"
end
end
Module UserType2 < User
def get_some_attribute
super
return "Hello, my type is UserType2"
end
end
Then, current_user.get_some_attribute will work as you expecting, if you like to read more about overriding methods in Ruby, you can read about it here
I added super as I assumed that you have some shared logic in get_some_attribute, as it will call get_some_attribute in User model, you can remove it if you don't need it.
Good luck!

Related

How to extend model in rails 4.2

I have a user model
class User
def fname
#fname
end
def fname=(str)
#fname = str
end
def greeting
"Hello #{#fname}"
end
end
But I want to remove the greeting method to somewhere else so that my user model don't include the business logic.
How should I achieved that?
I try to create a module(foo.rb) in lib but its not working. Should I include in User model?
Updated Info:
I updated my code
module UserBusinessEntity
def speak(sound)
return "#{sound} is its sound"
end
def greeting
"#{self.id} Hello, #{self.fname} #{self.lname} you are #{self.age} years old"
end
end
class User < ActiveRecord::Base
include UserBusinessEntity
end
This works if both code in same file.i.e. app/models/User.rb
But I want to move the module UserBusinessEntity code to app/services/
Do I have to add require at User Model. If so I added like require UserBusinessEntity But Its gives uninitialized constant UserBusinessEntity
Just create a module like this:
module Foo
def greeting
"Hello #{self.fname}"
end
end
Then include the module in your User module:
class User
include Foo
# ...
end
Then you can call in a controller or a view
#user = User.new
#user.greeting
I believe you may use greeting to render in views or mailers. So this is a showcase of using presenter. A good article is here.
Basically, defining a presenter will be:
app/presenters/user_presenter.rb
class UserPresenter < DelegateClass(User)
def greeting
"Hello #{fname}"
end
end
There are many ways to define, the above is just basic, check out above article for detail.
Then, you can use it anywhere you want to:
#user = User.first
UserPresenter.new(user).greeting
Or even in a view
example.html.erb
<p><%= UserPresenter.new(user).greeting %><p>
Moreover, people may use concern to implement this, but with me that is not a good practice!

Rails, place to put user input parsing

In my Rails app there is a view with a simple user form consisting of a text box and a submit button.
When the user submits the form, depending on his input, different models are created:
class MessageController < ApplicationController
def create
if is_foo params[:text]
Foo.create
else
Bar.create
end
end
def is_foo(text)
# Here the message gets parsed
# i.e if text[0] == "M"
end
end
My question is, do you think that it's a better design to put the "is_foo" logic inside the Foo model instead of the controller like so?
Model:
class Foo < ActiveRecord::Base
def self.is_foo(text)
# Here the message gets parsed
# i.e if text[0] == "M"
end
end
Controller:
class MessageController < ApplicationController
def create
if Foo.is_foo params[:text]
Foo.create
else
Bar.create
end
end
end
On one hand, the model should take care of the logic. On the other, this isn't really logic, its more of an input rule... What do you think guys?
Helper
I'd leave the is_foo out of the model, as model logic should be to do with the model directly, not determining which model should be created / saved
I would personally look at using a helper method for the test - calling the file ControllerHelper or similar:
#app/helpers/controller_helper.rb
class ControllerHelper
def is_foo? text
# Here the message gets parsed
# i.e if text[0] == "M"
end
end
This will allow you to call the helper in your controller, giving you the ability to use the logic to form the fixes:
#app/controllers/messages_controller.rb
class MessagesController < ApplicationController
include ControllerHelper
def create
model = is_foo?(params[:text]) ? "foo" : "bar"
model.constantize.send(:create)
end
end
I wouldn't call it a ControllerHelper module as mentioned in Rich Pecks answer (since helpers in Rails are view-related), but something like
# app/lib/foo_bar_creator.rb
FooBarCreator = Struct.new(:params) do
def create
build.save
end
def build
klass.new
end
def is_foo?
params[:text] == 'foo'
end
def klass
is_foo? ? Foo : Bar
end
end
(some call these kind of classes "Service Objects")
This way I could just call FooBarCreator.new(params).create in my controller.

What's the best way to make a new model when overriding the Devise User Controller?

I'm trying to make it so that when a new User is created (through Devise), a new Household(essentially a group) model will be created if no previous Household model with that name exists.
pseudocode:
if Household.find(params[:household_name))
# allow current_user to join household
else
# create new Household model with User's household_name parameter
end
I've overwritten the base user controller from Devise::RegistrationsController with controllers/registerhousehold_controller.rb:
class RegisterhouseholdController < Devise::RegistrationsController
But I'm not sure how to implement the actual creation here. Any suggestions?
No changes in controller required as far as I see.
User.rb
after_create :create_or_join_to_household
def create_or_join_to_household
household = Household.find(params[:household_name])
if household.present?
self.join_to_household
else
Household.create(name: params[:household_name])
#or self.households.create(name: params[:household_name])
#if you have a household - user relation somehow
end
p.s.
join_to_household would be another method in your user model that will create a household_users relation.
Simple - use the before_create callback in the user model to build the object, then you'll be able to use it when you save:
#app/models/user.rb
Class User < ActiveRecord::Base
before_create :set_household, if: Proc.new {|user| user.household_id.present? }
private
def set_household
if house = Household.find(self.household_id)
#if it is set
else
#create a new houshold
end
end
end
I had to call custom method after successful sign up, on my previous task.
U also need something similar.
I'm not sure about overriding.
Try this in App. controller
class ApplicationController < ActionController::Base
def after_sign_in_path_for(resource)
if Household.find(params[:household_name))
# allow current_user to join household
else
#create new Household model with User's household_name parameter
end
root_path
end
end
Check this

Global variable for language_id available in views and in models

I'm trying to share a session variable in both the controllers, the views and the model.
With the following code, it is working in the controllers and in the views :
class ApplicationController < ActionController::Base
protect_from_forgery
helper_method :best_language_id
# Returns the ID of the language to use to display the description.
def best_language_id
#best_language_id ||= session[:best_language_id]
#best_language_id ||= current_project.default_language.id
return #best_language_id
end
end
But I can't call it from the model.
I would like to be able to call best_language_id either in the controllers, views and in one model, to get a fallback of the best_language_id if a translation is not found.
Example in my model (not working) :
class Point < ActiveRecord::Base
# Retuns the attached word in the given language if exists.
# Otherwise, falls back on another translation
def word(preffered_language_id)
word = Word.find(:translation_id => self.translation_id, :language_id => preffered_language_id)
if word.blank?
word = translations.where(:translation_id => self.translation_id, :language_id => best_language_id)
end
return word
end
end
I know that model should not include applicationcontroller method calls, but how is it possible to share my best_language_id accross controllers and model ?
Edit : using i18n is not the question here. Translations are not fixed string but variables in a database.
Thanks for helping !
In your rails app, you have a base module in config/application.rb. It should be named after your application. Let's say its called MyApp. What you could do is define two methods like this:
module MyApp
...
def self.language_id=(value)
#language_id = value
end
def self.language_id
#language_id ||= 'en' # default vaule
end
...
end
Then, in app/controllers/application_controller.rb add a before_filter like this:
before_filter :language
def language
MyApp.language_id = session[:language_id] if session[:language_id]
end
Then, from all over the app, you can access the value via
MyApp.language_id
Needless to say that the approach is not thread safe so don't use it in a threaded environment.
I would suggest you switch the situation around, store the best_language_id in the model as a class accessor, then you can set and get it from your controllers and it will still be available in the models.
class Point < ActiveRecord::Base
cattr_accessor :best_language_id # to store the variable
end
# Persist the content of that variable at the start of every action
class ApplicationController < ActionController::Base
before_filter :set_best_language
def set_best_language
Point.best_language_id = session[:best_language_id]
Point.best_language_id ||= current_project.default_language.id
end
end
# Use the variable in a controller
class SomeOtherController < ActionController::Base
def show
#best_language = Language.find(Point.best_language_id)
...
end
end
# Use the variable in a model
class SomeOtherController < ActiveRecord::Base
def some_method
best_language = Language.find(Point.best_language_id)
...
end
end

Get current_user in Rails form validation by defining a virtual attribute

Rails form validation is designed to go in the model most easily. But I need to make sure the current user has the required privileges to submit a post and the current_user variable is only accessible in the controller and view.
I found this answer in a similar question:
You could define a :user_gold virtual attribute for Book, set it in the controller where you have access to current_user and then incorporate that into your Book validation.`
How can I set this up with my post and user controller so that the current_user variable is accessible in the model?
Solution:
This whole thing is wrong from an application design perspective as #Deefour's answer pointed out. I changed it so my view doesn't render the form unless the condition is true.
The "similar question" is saying you can do something like this
class YourModel < ActiveRecord::Base
attr_accessor :current_user
# ...
end
and then in your controller action you can do something like
#your_model = YourModel.find(params[:id])
#your_model.current_user = current_user
#your_model.assign_attributes(params[:your_model])
if #your_model.valid?
# ...
You can then use self.current_user within YourModel's validation methods.
Note I don't think this is what you should be doing though, as I don't consider this "validation" as much as "authorization". An unauthorized user shouldn't even be able to get the part of your action where such an update to a YourModel instance could be saved.
As for doing the authorization with Pundit as requested, you'd have a file in app/policies/your_model.rb
class YourModelPolicy < Struct.new(:user, :your_model)
def update?
user.some_privilege == true # change this to suit your needs, checking the "required privileges" you mention
end
end
Include Pundit in your ApplicationController
class ApplicationController < ActionController::Base
include Pundit
# ...
end
Then, in your controller action you can do simply
def update
#your_model = YourModel.find(params[:id])
authorize #your_model
# ...
The authorize method will call YourModelPolicy's update? method (it calls the method matching your action + ? by default) and if a falsy value is returned a 403 error will result.
Authorization shouldn't be done in models. Models have already many responsibilities don't you think?
That's a controller thing, and actually you can have the logic in other place using some gem like cancan and in your controller you would do something like:
authorize! :create, Post
You can define a "virtual attribute" in your model like this:
class Book < ActiveRecord::Base
attr_accessor :current_user
end
Its value can be set directly in your controller like this:
class BooksController < ApplicationController
def create
book = Book.new
book.current_user = current_user
book.save!
end
end
And inside your model's validation routine, you can access it like any other ActiveRecord field:
def validate_user_permission
errors[:current_user] = "user does not have permission" unless current_user.is_gold?
end
I can't remember if this is the case with ActiveRecord, but you might be able to set virtual attributes via the mass-assignment methods like create, update, and new in the controller:
def create
Book.create!(current_user: current_user)
end
In order to do that, you would probably have to add the following line to your model to enable mass-assignment of that virtual attribute:
attr_accessible :current_user
I agree with Ismael - this is normally done in the controller. It's not an attribute of the model, it's a permission issue and related to the controller business logic.
If you don't need all the power of a gem like CanCan, you can role your own.
class BooksController < ApplicationController
before_filter :gold_required, :only => :create
def create
book = Book.new
book.save!
end
# Can be application controller
private
def gold_required
return current_user && current_user.is_gold?
end
end
You may want to put the filter on the 'new' method as well.

Resources