How to overwrite/extend existing spree classes in app/models? - ruby-on-rails

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.

Related

Rails generic method for multiple classes

I have model named Order which belongs to User, Admin, Device etc.
I want to see total of orders for specific object like user.
so I have to write in user.rb model
def total_sales
// there are some dates & status conditions too
orders.sum(:total)
end
but for admin, device etc. I have to write exact same code in admin.rb & device.rb
I want to write code on just one place & write everywhere,
I was thinking to write a generic class like
class Calculate
def initialize(object)
#object = object
end
def total_sales
// there are some dates & status conditions too
#object.orders.sum(:total)
end
end
and than call it like
//sales of user
object = Calculate.new(user)
object.total_sales
//sales of admin
object = Calculate.new(admin)
object.total_sales
But I am not sure if this is standard way,
Whats the better way achieve this.
Use mixin for this, create a module like below.
module CommonMethods
def total_sales
// there are some dates & status conditions too
self.orders.sum(:total)
end
end
include the module in each class like User, Admin, Device etc.
class User
include CommonMethods
end

Rails admin tables with read only access

I am using rails server with rails_admin
I want to add a table to my include_models but I don't want to let write access to one/few of the models:
My admin looks like this:
# frozen_string_literal: true
RailsAdmin.config do |config|
### Popular gems integration
# == Devise ==
config.authenticate_with do
warden.authenticate! scope: :user
end
config.actions do
dashboard # mandatory
index # mandatory
new
export
bulk_delete
show
edit
delete
show_in_app
end
config.included_models = %w[ MyModel1
MyModel2]
end
I would like to add something like
config.read_only_models = %w[MyModel3, MyModel4]
Any suggestion ?
You need to do it on the rails admin model configuration to have access to the current user.
I'm afraid this rules out doing it at the RailsAdmin.config level.
You'll need to do it then field by field.
class MyModel3 < ApplicationRecord
rails_admin do
configure :field do
read_only do
bindings[:view].current_user.admin?
end
end
configure :field2 do
read_only do
bindings[:view].current_user.admin?
end
end
configure :field3 do
read_only do
bindings[:view].current_user.admin?
end
end
# ...
end
end
Not ideal but you'll see that is impossible with the current implementation of the actions by taking a look at the code of the edit action that inherits from base.
I've divided my answer to 2 sections as it may apply to different scenarios:
General case:
General behaviour can be set by adding a readonly? method for the model(s).
If the value is set to true, attempting to update a record will result in an error.
See here for more details.
def readonly?
true
end
ActiveRecord’s underlying persistence will check readonly? before creating or updating any records.
You can also add dynamic content inspection such as:
def readonly?
read_only_list.include? (self.class.name)
end
RailsAdmin specific behaviour (2nd scenario):
If you want to set a specific behaviour for RailsAdmin, you can create a special role and then use
CanCanCan which is an authorization library which restricts what resources a given user is allowed to access.
It can also restrict RailsAdmin & grant access using an Ability class which defines different permissions depending upon the user's role.
See an example how to Use different Ability classes for front-end and admin

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

How should I refactor and extract these methods in fat User model in rails?

I am currently refactoring a User model with over 800 lines of code (with several hundred more that have been added via mixins). Currently there are quite a few methods in the model like the ones below that are simply used to identify a type of user based off of some specific criteria.
def is_a_manager?
# logic to determine manager
end
def is_a_teacher?
# logic to determine teacher
end
def is_a_parent?
# logic to determine parent
end
def is_a_student?
# logic to determine student
end
def is_a_coach?
# logic to determine coach
end
def is_a_employee?
# logic to determine employee
end
What is the best way to refactor this code? Should I just put it in a concern and Include it in the class? Or should I extract all of these methods into its own separate class such as a UserIdentifier.new(user).is_teacher?? Or is this kind of refactor completely unnecessary?
You can use dry concept to check type:
def method_name(user_type)
//logic example
type == user_type
end
call method like:
User.method_name("Teacher")
I hope this help you.

Multi theme support for Rails app

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

Resources