I am testing this in local. My ip is 127.0.0.1. The ip_permissions table, is empty. When I browse the site, everything works as expected.
Now, I want to simulate browsing the site with a banned IP. So I add the IP into the ip_permissions table via:
IpPermission.create!(:ip => '127.0.0.1', :note => 'foobar', :category => 'blacklist')
In Rails console, I clear the cache via; Rails.cache.clear. I browse the site. I don't get sent to pages#blacklist.
If I restart the server. And browse the site, then I get sent to pages#blacklist. Why do I need to restart the server every time the ip_permissions table is updated? Shouldn't it fetch it based on cache?
Routes look like:
class BlacklistConstraint
def initialize
#blacklist = IpPermission.blacklist
end
def matches?(request)
#blacklist.map { |b| b.ip }.include? request.remote_ip
end
end
Foobar::Application.routes.draw do
match '/(*path)' => 'pages#blacklist', :constraints => BlacklistConstraint.new
....
end
My model looks like:
class IpPermission < ActiveRecord::Base
validates_presence_of :ip, :note, :category
validates_uniqueness_of :ip, :scope => [:category]
validates :category, :inclusion => { :in => ['whitelist', 'blacklist'] }
def self.whitelist
Rails.cache.fetch('whitelist', :expires_in => 1.month) { self.where(:category => 'whitelist').all }
end
def self.blacklist
Rails.cache.fetch('blacklist', :expires_in => 1.month) { self.where(:category => 'blacklist').all }
end
end
You are initializing BlacklistConstraint in your routes file which is loaded only once at the start. There you call IpPermission.blacklist and store it in an instance variable. Initialize is not called anymore and therefore you check against the same records.
You should load the records on each request if you want them to be updated:
class BlacklistConstraint
def blacklist
IpPermission.blacklist
end
def matches?(request)
blacklist.map { |b| b.ip }.include? request.remote_ip
end
end
Related
I'm trying to think of a best solution for following scenario. I've a model called an 'Article' with an integer field called 'status'. I want to provide class level array of statuses as shown below,
class Article < ActiveRecord::Base
STATUSES = %w(in_draft published canceled)
validates :status, presence: true
validates_inclusion_of :status, :in => STATUSES
def status_name
STATUSES[status]
end
# Status Finders
def self.all_in_draft
where(:status => "in_draft")
end
def self.all_published
where(:status => "published")
end
def self.all_canceled
where(:status => "canceled")
end
# Status Accessors
def in_draft?
status == "in_draft"
end
def published?
status == "published"
end
def canceled?
status == "canceled"
end
end
So my question is if this is the best way to achieve without having a model to store statuses? And secondly how to use these methods in ArticlesController and corresponding views? I'm struggling to understand the use of these methods. To be specific, how to do following?
article = Article.new
article.status = ????
article.save!
or
<% if article.in_draft? %>
<% end %>
I greatly appreciate any sample code example. I'm using rails 4.0.0 (not 4.1.0 which has enum support).
You could define all the methods using define_method, and use a hash instead of an array:
STATUSES = {:in_draft => 1, :published => 2, :cancelled => 3}
# Use the values of the hash, to validate inclusion
validates_inclusion_of :status, :in => STATUSES.values
STATUSES.each do |method, val|
define_method("all_#{method)") do
where(:status => method.to_s)
end
define_method("#{method}?") do
self.status == val
end
end
In that way, you can add statuses in the future without needing to create the methods manually. Then you can do something like:
article = Article.new
article.status = Article::STATUSES[:published]
...
article.published? # => true
I'm having issue with a youtube video being destroyed properly in a nested belongs_to has_one relationship between a sermon and its sermon video when using :dependent => :destroy.
I'm using the youtube_it gem and have a fairly vanilla setup.
The relevant bits below:
the video controller --
def destroy
#sermon = Sermon.find(params[:sermon_id])
#sermon_video = #sermon.sermon_video
if SermonVideo.delete_video(#sermon_video)
flash[:notice] = "video successfully deleted"
else
flash[:error] = "video unsuccessfully deleted"
end
redirect_to dashboard_path
end
the video model --
belongs_to :sermon
def self.yt_session
#yt_session ||= YouTubeIt::Client.new(:username => YouTubeITConfig.username , :password => YouTubeITConfig.password , :dev_key => YouTubeITConfig.dev_key)
end
def self.delete_video(video)
yt_session.video_delete(video.yt_video_id)
video.destroy
rescue
video.destroy
end
the sermon model --
has_one :sermon_video, :dependent => :destroy
accepts_nested_attributes_for :sermon_video, :allow_destroy => true
In the above setup, all local data is removed successfully; however, the video on youtube is not.
I have tried to override the destroy action with a method in the model, but probably due a failing of my understanding, can only get either the video deleted from youtube, or the record deleted locally, never both at the same time (I posted the two variants below and their results).
This only serves to destroy the local record --
def self.destroy
#yt_session ||= YouTubeIt::Client.new(:username => YouTubeITConfig.username , :password => YouTubeITConfig.password , :dev_key => YouTubeITConfig.dev_key)
#yt_session.video_delete(self.yt_video_id)
#sermon_video.destory
end
This only serves to destroy the video on youtube, but not the local resource --
def self.destroy
#yt_session ||= YouTubeIt::Client.new(:username => YouTubeITConfig.username , :password => YouTubeITConfig.password , :dev_key => YouTubeITConfig.dev_key)
#yt_session.video_delete(self.yt_video_id)
end
Lastly, the link I'm using to destroy the sermon, in case it helps --
<%= link_to "Delete", [#sermon.church, #sermon], :method => :delete %>
Thanks for your help, very much appreciated!
It looks as though I have just solved the issue; however, I'll leave it open for a bit in case someone has a more elegant / appropriate solution.
In the sermon video model I added --
before_destroy :kill_everything
def kill_everything
#yt_session ||= YouTubeIt::Client.new(:username => YouTubeITConfig.username , :password => YouTubeITConfig.password , :dev_key => YouTubeITConfig.dev_key)
#yt_session.video_delete(self.yt_video_id)
end
And the key thing, I believe, to have added in the sermon model was this --
accepts_nested_attributes_for :sermon_video, :allow_destroy => true
I am trying to create a unique json data structure, and I have run into a problem that I can't seem to figure out.
In my controller, I am doing:
favorite_ids = Favorites.all.map(&:photo_id)
data = { :albums => PhotoAlbum.all.to_json,
:photos => Photo.all.to_json(:favorite => lambda {|photo| favorite_ids.include?(photo.id)}) }
render :json => data
and in my model:
def as_json(options = {})
{ :name => self.name,
:favorite => options[:favorite].is_a?(Proc) ? options[:favorite].call(self) : options[:favorite] }
end
The problem is, rails encodes the values of 'photos' & 'albums' (in my data hash) as JSON twice, and this breaks everything... The only way I could get this to work is if I call 'as_json' instead of 'to_json':
data = { :albums => PhotoAlbum.all.as_json,
:photos => Photo.all.as_json(:favorite => lambda {|photo| favorite_ids.include?(photo.id)}) }
However, when I do this, my :favorite => lambda option no longer makes it into the model's as_json method.......... So, I either need a way to tell 'render :json' not to encode the values of the hash so I can use 'to_json' on the values myself, or I need a way to get the parameters passed into 'as_json' to actually show up there.......
I hope someone here can help... Thanks!
Ok I gave up... I solved this problem by adding my own array methods to handle performing the operations on collections.
class Array
def to_json_objects(*args)
self.map do |item|
item.respond_to?(:to_json_object) ? item.to_json_object(*args) : item
end
end
end
class Asset < ActiveRecord::Base
def to_json_object(options = {})
{:id => self.id,
:name => self.name,
:is_favorite => options[:favorite].is_a?(Proc) ? options[:favorite].call(self) : !!options[:favorite] }
end
end
class AssetsController < ApplicationController
def index
#favorite_ids = current_user.favorites.map(&:asset_id)
render :json => {:videos => Videos.all.to_json_objects(:favorite => lambda {|v| #favorite_ids.include?(v.id)}),
:photos => Photo.all.to_json_objects(:favorite => lambda {|p| #favorite_ids.include?(p.id)}) }
end
end
I think running this line of code
render :json => {:key => "value"}
is equal to
render :text => {:key => "value"}.to_json
In other words, don't use both to_json and :json.
Orders can have many states. I would like to create named routes for those. I need the state to be passed in to the controller as a param. Here is what I was thinking, but it obviously does not work.
match "order/:state/:id" => "orders#%{state}", as: "%{state}"
So I would like order/address/17 to route to orders#address, with :state and :id being passed in as params. Likewise, order/shipping/17 would route to orders#shipping, again :state and :id would be passed in.
Here is the controller.
class OrdersController < ApplicationController
before_filter :load_order, only: [:address, :shipping, :confirmation, :receipt]
before_filter :validate_state, only: [:address, :shipping, :confirmation, :receipt]
def address
#order.build_billing_address unless #order.billing_address
#order.build_shipping_address unless #order.shipping_address
end
def shipping
#shipping_rates = #order.calculate_shipping_rates
end
def confirmation
end
def receipt
end
private
def load_order
#order = Order.find(params[:id])
end
# Check to see if the user is on the correct action
def validate_state
if params[:state]
unless params[:state] == #order.state
redirect_to eval("#{#order.state}_path(:#{#order.state},#{#order.id})")
return
end
end
end
end
Here is what we ended up going with:
routes.rb
%w(address shipping confirmation receipt).each do |state|
match "order/#{state}/:id", :to => "orders##{state}", :as => state, :state => state
end
orders_controller.rb
def validate_state
if params[:state]
unless params[:state] == #order.state
redirect_to(eval("#{#order.state}_path(#order)"))
return
end
end
end
You aren't going to be able to create dynamic named routes with that sort of syntax, but you're basically just using :state as the :action. If you replace :state with :action and specify the controller manually, it'll work. Obviously, you will have to change your code to look at params[:action] rather than params[:state] (or map that variable in a before_filter), but beyond that it should work fine.
match "order/:action/:id", :controller => "orders"
Be aware that if orders has RESTful resource mappings like create or delete, this route would allow GET requests to them, which would be bad; you may just want to add explicit routes for each action you want to complete. This will let you get params[:state], as well:
%w(address shipping).each do |state|
match "order/#{state}/:id", :to => "orders##{state}", :as => state, :state => state
end
I've a got a method in ActiveRecord::User:
def create_user_from_json(user)
#user=User.new(user)
if #user.save!
#user.activate!
end
end
And I'm trying to call it in a plugin's module method. The plugin is json-rpc-1-1. Here is the relevant code:
class ServiceController < ApplicationController
json_rpc_service :name => 'cme', # required
:id => 'urn:uuid:28e54ac0-f1be-11df-889f-0002a5d5c51b', # required
:logger => RAILS_DEFAULT_LOGGER # optional
json_rpc_procedure :name => 'userRegister',
:proc => lambda { |u| ActiveRecord::User.create_user_from_json(u) },
:summary => 'Adds a user to the database',
:params => [{:name => 'newUser', :type => 'object'}],
:return => {:type => 'num'}
end
The problem is the code in the proc. No matter whether I call ActiveRecord::User.create_user_from_json(u) or ::User.create_user_from_json(u) or User.create_user_from_json(u) I just get undefined method 'create_user_from_json'.
What is the proper way to call a User method from the proc?
I think this needs to be a class method instead of an instance method, declare it like this:
def self.create_user_from_json(user)
...
end