Multi theme support for Rails app - ruby-on-rails

What will be a better approach to display labels in a web app based on a user.
For ex: We have User groups A and B. And labels and headers differ between groups. There will be no changes in the layout and only text differs.
I was looking at Rails themes. However looks like it works well for assets and themes.
Looking for suggestions here. App is on Rails 4.

Probably you may use decorator pattern with gem draper
Implementation will look something like this:
# app/decorators/group_decorator.rb
class GroupDecorator < Draper::Decorator
def name
end
end
# app/decorators/group_one_decorator.rb
class GroupOneDecorator < GroupDecorator
def name
'group one specific message'
end
end
# app/decorators/group_one_decorator.rb
class GroupTwoDecorator < GroupDecorator
def name
'group two specific message'
end
end
Then wherever you can just call decorate on group
user.group.decorate.name
or
GroupOneDecorator.new(user.group).name

Related

Two modules with same method names included in same class

I am working with ruby on rails and I am basically trying to include two modules into the same model/class with both modules having the same method names. An example will be demonstrated below, however my questions are:
Is there a way to include module conditionally? or
Is there a way to invoke based on the specific instance of the class.
An example is a simple complete profile wizard.
How its suppose to work
Case 1: If the user is lets say a Transporter, step_one is completed when the user has a company_name is present.
Case 2: On the otherhand if the user is a Client, step_one is completed when the user has a telephone present.
class User < ApplicationRecord
include ClientWizard
include TransporterWizard
end
module ClientWizard
def step_one_completed?
self.name.present?
end
end
module TransporterWizard
def step_one_completed?
self.company_name.present?
end
end
No, module methods all exist within the class's namespace. Consequently, this doesn't seem like a particularly good use case for modules.
You could give the methods module-specific names (client_wizard_step_one_completed?), but I'd recommend instead defining the wizards as separate classes, and passing the user instance as a parameter.
class User < ApplicationRecord
def client_wizard
ClientWizard.new(self)
end
end
class ClientWizard
def initialize(user)
#user = user
end
def step_one_completed?
#user.name.present?
end
end

Why do functions from my Rails plugin not work without specifically requiring?

I need some help with my plugin. I want to extend ActiveRecord::Base with a method that initializes another method that can be called in the controller.
It will look like this:
class Article < ActiveRecord::Base
robot_catch :title, :text
...
end
My attempt at extending the ActiveRecord::Base class with robot_catch method looks like following. The function will initialize the specified attributes (in this case :title and :text) in a variable and use class_eval to make the robot? function available for the user to call it in the controller:
module Plugin
module Base
extend ActiveSupport::Concern
module ClassMethods
def robot_catch(*attr)
##robot_params = attr
self.class_eval do
def robot?(params_hash)
# Input is the params hash, and this function
# will check if the some hashed attributes in this hash
# correspond to the attribute values as expected,
# and return true or false.
end
end
end
end
end
end
ActiveRecord::Base.send :include, Plugin::Base
So, in the controller, this could be done:
class ArticlesController < ApplicationController
...
def create
#article = Article.new(params[:article])
if #article.robot? params
# Do not save this in database, but render
# the page as if it would have succeeded
...
end
end
end
My question is whether if I am right that robot_catch is class method. This function is to be called inside a model, as shown above. I wonder if I am extending the ActiveRecord::Base the right way. The robot? function is an instance method without any doubt.
I am using Rails 3.2.22 and I installed this plugin as a gem in another project where I want to use this functionality.
Right now, it only works if I specifically require the gem in the model. However, I want it the functionality to be included as a part of ActiveRecord::Base without requiring it, otherwise I'd have to require it in every model I want to use it, not particularly DRY. Shouldn't the gem be automatically loaded into the project on Rails start-up?
EDIT: Maybe callbacks (http://api.rubyonrails.org/classes/ActiveSupport/Callbacks/ClassMethods.html) would be a solution to this problem, but I do not know how to use it. It seems a bit obscure.
First, I would suggest you make sure that none of the many many built in Rails validators meet your needs.
Then if that's the case, what you actually want is a custom validator.
Building a custom validator is not as simple as it might seem, the basic class you'll build will have this structure:
class SpecialValidator < ActiveModel::EachValidator
def validate_each(record, attribute, value)
# Fill this with your validation logic
# Add to record.errors if validation fails
end
end
Then in your model:
class Article < ActiveRecord::Base
validates :title, :text, special: true
end
I would strongly suggest making sure what you want is not already built, chances are it is. Then use resources like this or ruby guides resources to continue going down the custom validator route.
Answer
I found out the solution myself. Bundler will not autoload dependencies from a gemspec that my project uses, so I had to require all third party gems in an engine.rb file in the lib/ directory of my app in order to load the gems. Now everything is working as it should.
Second: the robot_catch method is a class method.

Refactored way of titleizing values from a model (Rails)

I have trouble thinking of a way on how to shorten my process on titleizing values upon rendering them in my view.
I did some custom getters for the following attributes that I need to titleize. Here's my example.
user.rb
class User < ActiveRecord::Base
def department
read_attribute(:department).titleize
end
def designation
read_attribute(:designation).titleize
end
end
This method works but it seems a hassle when I want to do this to other models as well.
Is there a more efficient way to handle this which can be used by other models? If you'll mention Draper (since I don't seem to find on how to titleize selected attributes), how can I accomplish using this gem? But, I would prefer not using a gem but instead, create a custom one.
Not tested this, but you could use a Concern with added modules to handle it
--
Modularity
I found a gem called modularity which basically allows you to pass parameters to a concern & other modules. This means if you can pass the params you wish to "titleize", you may be able to pull it off like this:
#Gemfile
gem 'modularity', '~> 2.0.1'
#app/models/concerns/titleize.rb
module Titleize
extend ActiveSupport::Concern
as_trait do |*fields|
fields.each do |field|
define_method("#{field}") do
self[field.to_sym] = field.titleize
end
end
end
end
#app/models/your_model.rb
Class YourModel < ActiveRecord::Base
include Titleize[:your, :params]
end
If you want those value always titleized, what you are doing is fine, but I would actually apply the method on the setters, not on the getters, so you only do it once per record instead of at each read:
def department=(s)
write_attribute(:department, s.to_s.titleize) # The to_s is in case you get nil/non-string
end
If this is purely for presentation (ie, you want the not titleized version in the database, then it can be done in a presenter using Draper:
class UserDecorator < Draper::Decorator
delegate_all
def designation
object.designation.titleize
end
end
(or another rails presenter).

Rails - call to engine_name seems to be ignored

I want to make an Engine that is isolated by two namespaces. That is, let say for example I'd like to make an Engine whose classes all live in:
Car::BMW
And thus, my models for example should be placed in:
app/models/car/bmw/
And my tables should be prefixed by for example:
car_bmw_
I tried to accomplish this by having this code in lib/car/bmw/engine.rb
module Car
module BMW
class Engine < ::Rails::Engine
isolate_namespace Car::BMW # This will call: engine_name 'car_bmw'
end
end
end
With this code whenever I generate a model however, the model is placed in:
app/models/car
And the table is prefixed by:
car_
What am I doing wrong? The version of rails I am using is 4.0.0.beta1
EDIT
I found this method in Rails::Generators::NamedBase
def namespaced_path
#namespaced_path ||= namespace.name.split("::").map {|m| m.underscore }[0]
end
Which, as you can see, takes only the first part of the namespace. Does anyone know why this is?
Is this a bug in Rails or am I not supposed to have my classes doubly namespaced?
This is a quick hack I resorted to, to fix the generators.
require 'rails/generators'
Rails::Generators::NamedBase.class_eval do
protected
def namespaced_class_path
#namespaced_class_path ||= [namespaced_path.split('/')] + #class_path
end
def namespaced_path
#namespaced_path ||= namespace.name.split("::").map {|m| m.underscore }.join('/')
end
def class_name
([file_name]).map!{ |m| m.camelize }.join('::')
end
end
Using a class like this
module Car
module BMW
class Engine < ::Rails::Engine
isolate_namespace Car::BMW # This will call: engine_name 'car_bmw'
paths["app/models"] << "app/models/car/bmw"
end
end
end
would allow you to load models from the subdirectory you specified. I cannot say if this is going to influence the generating process, though.
There's a whole lot more configuration options, see e.g. here. Edit them to match your needs.
I don't see why you would want that. If you want to share functionality between your Car engines, inheritance might not be the way. You could potentially have it inception-style, a BMW engine inside a Car engine if you really want that.
If Car is not a module you want to add functionality to and share across other submodules, then drop it.
If you want car engines like Car::BMW to share functionality then you can have it as an external dependency. All your car engines could require a ActsAsACar gem or something.
I'm still having a hard time figuring out why you'd want to have a double namespacing.

How to overwrite/extend existing spree classes in app/models?

I want to extend Class Role such that I can add more roles to the
roles table in Spree. My application would have different prices based
on roles.
By default roles have: ("admin" and "user") in it. I want to add more
types to the table.
Q1: Can I just extend the Role class in one of my extensions?
Q2: How can I implement (actually extend on app/models/Variant.rb) the
prices based on different roles such that it just grabs price from one
place? So that I dont have to change code in *_html.erb files where
its using price.
If I can get this to work this would be a cool extension to have on
github.
Thanks
To extend classes in Spree, you can use Modules or class_eval. Spree extensions tend to use class_eval. Here's an example for extending User and Variant in a custom extension.
class CustomRoleExtension < Spree::Extension
# main extension method
def activate
# extend User
User.class_eval do
def business?
self.roles.include?("business")
end
def sponsor?
self.roles.include?("sponsor")
end
def developer?
self.roles.include?("developer")
end
end
# extend Variant
Variant.class_eval do
def price_for(role)
# ...
end
end
end
end
To add more roles, I just added a defaults/roles.yml to my extension, with custom yaml blocks:
coach_role:
id: 3
name: coach
trainer_role:
id: 4
name: trainer
graduate_role:
id: 5
name: graduate
Then when you run rake db:bootstrap, it will add all those roles to the database.
Let me know if that works.

Resources