Simple permissions in a single field for rails model - ruby-on-rails

I want add any kind of permissions for my rails models just including one module to the model and defining metadata in one database field. How i can do this?
For example:
Folder < AR::B
#permissions_list = [:is_private, :public_on_negotioation]
#permissions_field = :perms
include Permissions
end
module Permissions
"...?"
end
i want to have methods "is_private?", "is_private", "is_private=" for all items in a #permissions_list variable.
So i can use model in this way:
f = Folder.new
f.is_private = true
f.public_on_negotioation = false
f.save
f.reload
f.is_private?
=> true
f.public_on_negotioation?
=> false
so i wrote next Module:
module Permissions
def self.included(mod)
permissions_list = mod.instance_variable_get(:#permissions_list)
permissions_list.each_with_index do |permission, index|
define_method permission.to_sym do
perms_bits[index] == '1'
end
alias_method (permission.to_s << "?").to_sym, permission.to_sym
end
end
def perms_bits
send(self.class.instance_variable_get(:#permissions_field)).to_i.to_s(2).reverse
end
def set_permission(name, weight, options)
permissions_field = self.class.instance_variable_get(:#permissions_field)
if options[name]
self.send("#{permissions_field}=", self.send(permissions_field).to_i + weight.to_i) unless send(name)
elsif options.has_key?("#{name}_off")
self.send("#{permissions_field}=", self.send(permissions_field).to_i - weight.to_i) if send(name)
end
end
def update_perms(options)
permissions_list = self.class.instance_variable_get(:#permissions_list)
permissions_list.each_with_index do |permission, index|
set_permission(permission.to_sym, 2**index, options)
end
save
end
end
some improvements?

To extend the answer from mdesantis. The way you can wrap up the permissions code for reuse could be something like this (untested):
class Folder < ActiveRecord::Base
include Permissions
end
PERMISSIONS_STRUCT = Struct.new(:is_private, :public_on_negotiation)
module Permissions
def self.included(klass)
klass.class_eval do
serialize :permissions, PERMISSIONS_STRUCT
end
klass.include(InstanceMethods)
end
module InstanceMethods
def is_private?
permissions.is_private
end
def is_private=(is_private)
permissions.is_private = is_private
end
end
end

Take a look at ActiveRecord::serialize:
Folder < AR::B
# Must be costant, otherwise Rails will raise an
# ActiveRecord::SerializationTypeMismatch
PERMISSIONS_STRUCT = Struct.new(:is_private, :public_on_negotiation)
serialize :permissions, PERMISSIONS_STRUCT
def is_private?
permissions.is_private
end
def is_private=(is_private)
permissions.is_private = is_private
end
# The same for public_on_negotiation
end
f = Folder.new
f.is_private = true
f.save
f.reload
f.is_private?
=> true
If you need to dynamically define accessor methods:
Folder < AR::B
[:is_private, :public_on_negotiation].each do |action|
define_method("#{action}?") do
permissions.send action
end
end
# And so on for "#{action}=", ...
end
And remember: refactoring is up to you! :-)

Related

How to show the dependent records that will be deleted before destroying a record

Django admin shows you the dependent records that will be deleted when you delete a record as a confirmation.
Is there a way to do the same on Ruby on Rails?
I have been researching how to do it, but I am still looking for a way.
I couldn't find a gem, so I wrote this concern using association reflections:
module DependentDestroys
extend ActiveSupport::Concern
DEPENDENT_DESTROY_ACTIONS = %i[destroy delete destroy_async]
class_methods do
def dependent_destroy_reflections
#dependent_destroy_reflections ||= reflections.filter_map do |name, r|
r if DEPENDENT_DESTROY_ACTIONS.include?(r.options[:dependent])
end
end
end
def total_dependent_destroys
dependent_destroy_counts.sum { |r| r[1] }
end
def any_dependent_destroys?
dependent_destroy_counts.any?
end
# If you want all affected records...
def dependent_destroy_records
self.class.dependent_destroy_reflections.flat_map do |r|
relation = self.public_send(r.name)
if r.collection?
relation.find_each.to_a
else
relation
end
end
end
# If you only want the record type and ids...
def dependent_destroy_ids
self.class.dependent_destroy_reflections.flat_map do |r|
relation = self.public_send(r.name)
if r.collection?
relation.pluck(:id).map { |rid| [r.klass, rid] }
else
[[r.klass, relation.id]] if relation
end
end.compact
end
# If you only want counts...
def dependent_destroy_counts
self.class.dependent_destroy_reflections.filter_map do |r|
relation = self.public_send(r.name)
if r.collection?
c = relation.count
[r.klass, c] if c.positive?
else
[r.klass, 1] if relation
end
end
end
def dependent_destroy_total_message
"#{total_dependent_destroys} associated records will be destroyed"
end
def dependent_destroy_message
# Using #human means you can define model names in your translations.
"The following dependent records will be destroyed: #{dependent_destroy_ids.map { |r| "#{r[0].model_name.human}/#{r[1]}" }.join(', ')}"
end
def dependent_destroy_count_message
"The following dependent records will be destroyed: #{dependent_destroy_counts.map { |r| "#{r[0].model_name.human(count: r[1])} (#{r[1]})" }.join(', ')}"
end
end
Usage:
class User
include DependentDestroys
belongs_to :company
has_many :notes
has_one :profile
end
user = User.first
user.any_dependent_destroys?
# => true
user.total_dependent_destroys
# => 60
user.dependent_destroy_total_message
# => "60 associated records will be destroyed"
user.dependent_destroy_message
# => "The following dependent records will be destroyed: Note/1, Note/2, ..., Profile/1"
user.dependent_destroy_count_message
# => "The following dependent records will be destroyed: Notes (59), Profile (1)"
You can then use these methods in the controller to deal with the user flow.
With some improvements, options (like limiting it to the associations or modes (destroy, delete, destroy_async) you want) and tests, this could become a gem.

NoMethodError: undefined method `liquidity_manager?' for #<Order:0x007fd9b7a4cf98>

My solution throws NoMethodError. This is what I have done below:
Order.rb
class Order < ActiveRecord::Base
belongs_to :member
end
Member.rb
class Order < ActiveRecord::Base
has_many :orders
def liquidity_manager?
#is_liquidity_manager ||= self.class.liquidity_managers.include?(self.email)
end
def liquidity_managers
ENV['LM_ACCOUNTS'].split(',')
end
end
rake task logic
The logic in lib/update_order_tags.rake
num_counts = (Order.count/10).ceil
num_counts.times do |i|
Order.all.offset(i*10).limit(10).find_each do |g|
if g.tags.blank? # am saving on only the blank fields
if g.liquidity_manager? # The error is here
g.tags = 'LM'
g.save!
else
g.tags = 'Customer'
g.save!
end
end
end
end
How do I reference the liquidity_manager? method to be useful for me in lib?
You defined Order#liquidity_manager? in Member.rb, which is not automatically loaded by Rails. You have to either explicitly load the file Member.rb, or rename that file to /app/models/order.rb.
So from the advise given to me by #sawa in the answer section, here is what I have done to make it work.
num_counts = (Order.count/10).ceil
num_counts.times do |i|
Order.all.offset(i*10).limit(10).find_each do |g|
if g.tags.blank?
if g.member.liquidity_manager? # So this easily gets to Member.rb to reference liquidity_manager? method.
g.tags = 'LM'
g.save!
else
g.tags = 'Customer'
g.save!
end
end
end
end

Disable pagination for relationships

Given 2 resources:
jsonapi_resources :companies
jsonapi_resources :users
User has_many Companies
default_paginator = :paged
/companies request is paginated and that's what I want. But I also want to disable it for relationship request /users/4/companies. How to do this?
The best solution I found will be to override JSONAPI::RequestParser#parse_pagination like this:
class CustomNonePaginator < JSONAPI::Paginator
def initialize
end
def apply(relation, _order_options)
relation
end
def calculate_page_count(record_count)
record_count
end
end
class JSONAPI::RequestParser
def parse_pagination(page)
if disable_pagination?
#paginator = CustomNonePaginator.new
else
original_parse_pagination(page)
end
end
def disable_pagination?
# your logic here
# request params are available through #params or #context variables
# so you get your action, path or any context data
end
def original_parse_pagination(page)
paginator_name = #resource_klass._paginator
#paginator = JSONAPI::Paginator.paginator_for(paginator_name).new(page) unless paginator_name == :none
rescue JSONAPI::Exceptions::Error => e
#errors.concat(e.errors)
end
end

How to duplicate a record in Rails except for one attribute?

In my Rails application I have a method that duplicates an invoice including its items.
class Invoice < ActiveRecord::Base
def duplicate
dup.tap do |new_invoice|
new_invoice.date = Date.today
new_invoice.number = nil <<<--------------
items.each do |item|
new_invoice.items.push item.dup
end
end
end
end
Now what I would like is to not copy the number attribute at all, so a new number can be generated inside my new action (I am not showing that here for brevity).
Right now, I am setting it to nil which is not what I want.
Any Ideas ?
Something along these lines perhaps:
def duplicate
new_invoice = Invoice.new(attributes.except("id", "number"))
items.each do |item|
new_invoice.items.push item.dup
end
new_invoice
end
Or loop over the attributes if you need to get around protected attributes etc. But self.attributes with Hash#except is probably what you want.
Pass the new number as a parameter, it's the most simple and most readable technique
class Invoice < ActiveRecord::Base
def duplicate(new_number = nil)
dup.tap do |new_invoice|
new_invoice.date = Date.today
new_invoice.number = new_number
items.each do |item|
new_invoice.items.push item.dup
end
end
end
end
Then in your controller
class InvoicesController < ApplicationController
def new
...
#new_invoice = #invoice.duplicate(new_number)
end
end
Why not
class Invoice < ActiveRecord::Base
def clone
number = self.number
cloned = super
cloned.number = number
end
end

Semantic-Menu root bahaviour

require 'rubygems'
require 'action_view'
require 'active_support'
class MenuItem
include ActionView::Helpers::TagHelper,
ActionView::Helpers::UrlHelper
attr_accessor :children, :link
cattr_accessor :request
def initialize(title, link, level, link_opts={})
#title, #link, #level, #link_opts = title, link, level, link_opts
#children = []
end
def add(title, link, link_opts={}, &block)
returning(MenuItem.new(title, link, #level +1, link_opts)) do |adding|
#children << adding
yield adding if block_given?
end
end
def to_s
content_tag(:li, content_tag(:div, link_to(#title, #link, #link_opts), :class => "menu_header_level_"+#level.to_s) + child_output, ({:class => 'active'} if active?)).html_safe
end
def level_class
"menu_level_#{#level}"
end
def child_output
children.empty? ? '' : content_tag(:ul, #children.collect(&:to_s).join.html_safe, :class => level_class)
end
def active?
children.any?(&:active?) || on_current_page?
end
def on_current_page?
# set it for current_page? defined in UrlHelper
# current_page?(#link)
false
end
# def request
# ##request
# end
end
class SemanticMenu < MenuItem
def initialize(rq, opts={},&block)
##request = rq
#opts = {:class => 'menu'}.merge opts
#level = 0
#children = []
yield self if block_given?
end
def to_s
content_tag(:ul, #children.collect(&:to_s).join.html_safe, #opts).html_safe
end
end
Hello. I am trying to change the behaviour of the Semantic-Menu root. When I click one of the roots, the menu drops down and displays all the children. What I would like is happen is when I click, it goes to a default page and then display the children. Semantic-menu seems to allow links only to lower levels and not the main ones. Roots links only work when they don't have children.
The code below is the one that is in the plug-in in Ruby. and I think is the one that needs to be modified. There the html code but I don't think it has to do with it.
Can you please tell me what need to be added in other to make to father trigger their links?
Thank you.
I don't know the direct answer to your question, but SemanticMenu seems outdated.
Check out the SimpleNavigation gem: https://github.com/andi/simple-navigation/wiki

Resources