I have a Ruby on Rails application that uses rails_admin (https://github.com/sferik/rails_admin) as the backend.
I have a model called banner, so a table in the database called banners. The admin can create as many banners as he can and can also delete them. But I want to fix the number of banners in 3. I want to have 3 banners (already created) and I want the admin cannot create nor destroy any banners.
Could someone help me?
Thanks!
class Thing < ActiveRecord::Base
has_many :banners
end
app/controllers/things_controller.rb
def create
#thing = Thing.new
#thing.banners << Banner.new(:name=>'Banner 1',....)
#thing.banners << Banner.new(:name=>'Banner 2',....)
#thing.banners << Banner.new(:name=>'Banner 3',....)
#thing.save
end
Now so long as nowhere else to you call #thing.banners << , you are guaranteed that any thing will always have three banners.
Validations to the rescue:
class Thing < Active Record::Base
has_many :banners
validate :banner_count
private
def banner_count
errors.add(:base, "Banner count must be 3") unless self.banners.count == 3
end
end
#RadBrad makes the point that you could use has_many on another model that represents the set of three banners. Maybe could call it BannerSet or similar. You could either create the three at once like he said, or in the BannerSet validation, you could ensure there are only 3 banners associated.
You could also have even 3 attributes (columns) on the BannerSet model that would have the 3 ids of the banners. If you are sure it will always be 3 banners, then that may be a fine design also.
But, here is how you'd do it if you just had a controller for Banner, which wouldn't be the best way, as you'll see.
First, you could possibly use declarative authorization in the controller:
authorization do
has_permission_on :banners, :to => [:new, :create] do
Banner.count < 3
end
end
To ensure that you still can't add a banner even if it was added after you got to the create screen to add it, also add a validation to the Banner model:
before_create :validate_max_banners
def validate_max_banners
errors.add_to_base("Only 3 banners are allowed.") if Banner.count == 3
errors.empty?
end
Neither will totally ensure you can only have 3 rows in that table though. To do that, you'd need a trigger or similar on the DB side, as described in this q&a. But, for a basic solution, that might be fine.
Note that even though RailsAdmin can be configured and customized pretty easily (see its wiki for documentation), I'd also consider using ActiveAdmin, if you need a lot more customization.
Related
I have Rails api, and I use it to upload photos from the mobile app. Is there a way to set something like a timer from Rails back-end to let say allow one user only upload twice in an hour and/or another user twice in a day? I am a little puzzled with logic of how it can work from the back-end since I don't wanna do it from the front-end. Any advice is appreciated. Thanks!
Simple. Just check how many records the user has created in the alloted time frame. Lets say you have the following associations:
class User
has_many :photos
end
class Photo
belongs_to :user
end
And a current_user method that returns the authenticated user.
To query based on a time frame you use a range:
def create
#photo = current_user.photos.new(photo_params)
unless current_user.photos.where(created_at: (1.hour.ago..Time.now)).count <= 2
#photo.errors.add(:base, 'You have reached the upload limit')
end
# ...
end
Later when you refactor you can move this into the model as a custom validation.
I am facing a design problem with respect to a rails app I am developing for my company product right now. My app allows creation of two classes which are subclasses of a parent class.
class Coupon
include Commonelements
end
class ServiceCenterCoupon < Coupon
end
class DealershipCoupon < Coupon
end
When you go to the view and you want to create a new coupon, you select either of the two and a new coupon is created depending upon the params[:coupon_type]
In the controller:
if params[:coupon_type] == 'dealershipcoupon'
#coupon = DealershipCoupon.new(coupon_params)
if #coupon.save!
redirect_to #coupon
else
render :new
end
elsif params[:coupon_type] == 'servicecentercoupon'
#coupon = ServiceCenterCoupon.new(coupon_params)
if #coupon.save!
redirect_to #coupon
else
render :new
end
end
I wanna give admin users the ability to create new coupon types at the run time as well. Say, someone wants to create Repairshopcoupons class. What changes do I need to bring to the views for example add a new form or what params I need to add to the existing form to be able to create new sub classes of Coupons at run time?
I do understand that using
repairshopcoupon = Class.new()
can work. For example anonymous function like this code in the controller can work:
Repairshopcoupon = Class.new(Coupon) do
include ActiveModel::Validations
validates_presence_of :title
def self.name
"Oil Change"
end
end
#newrepairshopcoupon = Repairshopcoupon.new
#newrepairshopcoupon.save
But I am not sure.
My first questions is: What would be the proper flow if I want users to create new classes from the view. What should controller handle and how will it save?
My second question is: There are few customers who belong to both dealerships and service centers group. Each group has authority over what coupon type they can manage. I want these users who belong to multiple groups to be able to see respective coupon inventory as well as which users downloaded those. I feel the need of changing my data model so that all coupon inventory and download lists belong to exactly one authorized group but I don't have a concrete idea of what would be the best way.
My third question is: What would be the best approach to change my view/UX for creating and managing coupons so that the users of multiple groups would be able to switch between each inventory ? What would be the professional industry standard for UX deign in this case ?
Would really appreciate your help.
Letting the users of an application generate code at runtime is just a really bad idea as the amount of potential bugs and vulnerabilities is mind boggling as your basically allowing untested code to be injected into the app at runtime.
It will also wreck havoc with any class based caching in the application.
It also won't work with cloud platforms like Heroku that use an ephemeral file system which is created from the last code commit.
First off you probably don't actually need different classes for each "type" of coupon. You need to consider if the logic for each class is substantially different.
You can probably get by just by creating a polymorphic association to the issuer (the dealship or service center).
class Coupon < Coupon
belongs_to :issuer, polymorphic: true
end
If you want to avoid polymorphism than just set it up as a standard STI setup:
class Coupon
include Commonelements
end
class ServiceCenterCoupon < Coupon
self.table_name = 'coupons'
belongs_to :service_center
end
class DealershipCoupon < Coupon
self.table_name = 'coupons'
belongs_to :dealership
end
I am building a To Do application but with multiple levels of tasks and subtasks. I have been following Ruby on Rails Tutorial by Michael Hartl and almost everything I have learnt till now is from there.
My app has multiple hierarchy models. What I mean is that at the top is the User, a user can have multiple goals, each goal can have multiple tasks, and each task can have multiple subtasks.
Just to give you a more clear idea, I'll list down the hierarchy
User
Goals (multiple goals per user)
Tasks (multiple tasks per goal)
Subtasks (multiple subtasks per task)
Now, when I created the models, I included a dependency (has_many/belongs_to) between goals and users, but I also did the same for tasks and users and subtasks and users. The reason for this is that I wanted an 'id' column in all tables that corresponds to the user table so that I can list down all subtasks/tasks/goals of a user very easily.
Coming to the front end, I need forms that are capable of adding goals/tasks/subtasks.
The relation between users and goals is not problematic at all. The has_many/belongs_to relation allows me to user something like
def create
#goal = current_user.goals.build(path_params)
if #goal.save
redirect_to current_user
else
.
.
end
end
However, the problem arises when I have 3 levels of hierarchy. User, goals and tasks.
My question is that how exactly do I do this for the task controller?
I tried using something like
#task = current_user.goals.tasks.build(task_params)
but that has the obvious flaw apart from probably being syntactically incorrect : there is no way to detect what particular goal this task is being assigned to.
What I can think of is that the front end form must contain a field (perhaps hidden) that contains the goal ID to which the task is being assigned. But not entirely sure is this is correct as well or how if a front end gimmick like that can actually help in case there are even more levels of hierarchy.
Please tell me where I am going wrong / whats a better way to do this.
I am only a beginner right now so I am very confused on how multiple level model hierarchies are managed.
What I can think of is that the front end form must contain a field (perhaps hidden) that contains the goal ID to which the task is being assigned.
More or less. A better way is to have RESTful resources here. So you'll have this structure of nested resources:
# routes.rb
resources :users
resources :goals do
resources :tasks do
resources :subtasks
end
end
This means that to create a task, you have to send a POST request to
/goals/1/tasks
This way, in your TasksController, you'll have params[:goal_id] and now you can do something like this:
class TasksController
before_action :load_goal
def create
#task = #goal.tasks.build(task_params)
...
end
private
def load_goal
#goal ||= Goal.where(id: params[:goal_id]).first
end
end
Repeat for other levels of hierarchy.
As for the propagating user_id down the line, you can do something like this:
class Task
belongs_to :goal
before_save :copy_user_id_from_parent
def copy_user_id_from_parent
self.user_id = goal.user_id
end
end
I have Customer and each customer has_many Properties. Customers belong to a Company.
I'm trying to add a certain Property to each one of a single Company's Customers. I only want this change to happen once.
I'm thinking about using a migration but it doesn't seem right to create a migration for a change that I only ever want to happen once, and only on one of my users.
Is there a right way to do this?
You can just use rails console.
In rails c:
Company.where(conditions).last.customers.each do |customer|
customer.properties << Property.where(condition)
customer.save!
end
Validation
Depending on how you're changing the Customer model, I'd include a simple vaidation on the before_update callback to see if the attribute is populated or not:
#app/models/Customer.rb
class Customer < ActiveRecord::Base
before_update :is_valid?
private
def is_valid?
return if self.attribute.present?
end
end
This will basically check if the model has the attribute populated. If it does, it means you'll then be able to update it, else it will break
--
Strong_Params
An alternative will be to set the strong_params so that the attribute you want to remain constant will not be changed when you update / create the element:
#app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
...
private
def strong_params
params.require(:model).permit(:only, :attributes, :to, :update)
end
end
It would be much more helpful if you explained the context as to why you need this type of functionality - that will give people the ability to create a real solution, instead of proposing ideas
I've done some searching here and I've not been able to find anything that quite answers what I'm looking for. If I failed in my search I apologize.
Moving on, I am new to Rails and I'm working on an application to test the waters if you will. I'm using Devise for authentication and it's proving quite useful. That said, I've run into a big of a road block with where a certain check for data would go, and how I would go about it.
I have three tables: users, games, and users_games (I read that this was an acceptable naming convention for relational tables, correct me if I'm wrong). On a Games page I would like to display a certain message if the currently logged in User has this Game added to their account (in users_games). I'm unsure of where to perform this check, or if it even matters at all.
As for the actual checking, my initial idea would be something along the lines of:
games_controller.rb
class GamesController < ApplicationController
def index
#games = Game.all
end
def show
#game = Game.find(params[:id])
#user_owns = UsersGames.where(:game_id => #game.id, :user_id => current_user.id)
end
end
Then on the view checking if #user_owns has a value or not.
Thanks in advance for any insight or wisdom you can offer.
What about this way, may be you don't need users_games
if game has_many users and user belongs_to game
def show
#game = Game.find_by_user_id(current_user.id)
end
Then on the view checking if #game has a value or not.
If your Users<->Games relationship is a simple HABTM with no additional attributes on the join table, i.e.
class User < AR::Base
has_and_belongs_to_many :games
class Game < AR::Base
has_and_belongs_to_many :users
you don't need to have a separate model for the join table, provided that you follow the Rails naming convention that requires you to follow the lexicographical order when naming your join table, i.e. in your case it would be games_users, not the other way around like you have it now.
Going back to your original question, I think it can be as simple as this:
def show
#game = Game.find(params[:id])
#game_owned = current_user.games.include? #game
end
You can also make this a method on your User model:
class User < AR::Base
...
def owns_game?(game)
self.games.include?(game)
end
end
and then call current_user.owns_game?(#game) in your controllers/views.