I need to extend an existing module (namely Redmine::MimeType) by adding a few items to a constant MIME_TYPES Hash.
This is what I tried so far and that only gives me a "dynamic constant assignment" error:
module MimeTypePatch
def self.included(base) # :nodoc:
base.extend(ClassMethods)
base.class_eval do
MIME_TYPES_VIDEO = {
'video/x-flv' => 'flv,f4v',
'video/mpeg' => '*.mpeg *.mpg *.mpe',
'video/quicktime' => 'qt,mov',
'video/vnd.vivo' => 'viv,vivo',
'video/x-msvideo' => 'avi'
}.freeze
# merge the new mime types with the existing ones
MIME_TYPES = MIME_TYPES.merge(MIME_TYPES_VIDEO).freeze
EXTENSIONS = MIME_TYPES.inject({}) do |map, (type, exts)|
exts.split(',').each {|ext| map[ext.strip] = type}
map
end
end
end
end
So what am I doing wrong and what might be the correct approach to alter an existing constant in another module? I do see that changing a constant at runtime is a bit off, but I couldn't think of a more elegant approach to achieve what I want (detecting video mime types).
Except for the freezing, you can modify existing constants in many cases rather than trying to redefine them. For instance, in your example using #merge! rather than #merge would alter the constant in place. I'm not sure whether you'd have to take steps to distinguish the definition in the current scope from that in the inherited one (i.e., you don't want to change any ancestry-upstream values of MIME_TYPES if there are any).
MIME_TYPES = {} unless (defined?(MIME_TYPES))
MIME_TYPES.merge!(MIME_TYPES_VIDEO)
Also of interest is the #replace method for Hash, Array, and others:
MIME_TYPES.replace(MIME_TYPES.merge(MIME_TYPES_VIDEO))
Cheers!
you can't assign a value to constant after initial definition, you have to create a new constant which will have the merged hash value. Change this line from
# merge the new mime types with the existing ones
MIME_TYPES = MIME_TYPES.merge(MIME_TYPES_VIDEO).freeze
to
# merge the new mime types with the existing ones
MERGED_MIME_TYPES = MIME_TYPES.merge(MIME_TYPES_VIDEO).freeze
and then you can use this new constant MERGED_MIME_TYPES in further logic
Related
I made some regular expressions for email, bitmessage etc. and put them as constants to
#config/initializers/regexps.rb
REGEXP_EMAIL = /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
REGEXP_BITMESSAGE = /\ABM-[a-zA-Z1-9&&[^OIl]]{32,34}\z/
and use it like
if #user.contact =~ REGEXP_EMAIL
elsif #user.contact =~ REGEXP_BITMESSAGE
Is that's good practice? What's the best way to store them?
It makes sense, that's one of the possible approaches. The only downside of this approach, is that the constants will pollute the global namespace.
The approach that I normally prefer is to define them inside the application namespace.
Assuming your application is called Fooapp, then you already have a Fooapp module defined by Rails (see config/application).
I normally create a fooapp.rb file inside lib like the following
module Fooapp
end
and I drop the constants inside. Also make sure to require it at the bottom of you application.rb file
require 'fooapp'
Lazy-loading of the file will not work in this case, because the Fooapp module is already defined.
When the number of constants become large enough, you can more them into a separate file, for example /lib/fooapp/constants.rb. This last step is just a trivial improvement to group all the constants into one simple place (I tend to use constants a lot to replace magic numbers or for optimization, despite Ruby 2.1 Frozen String literal improvements will probably let me remove several constants).
One more thing. In your case, if the regexp is specific to one model, you can store it inside the model itself and create a model method
class User
REGEXP_EMAIL = /\A([^#\s]+)#((?:[-a-z0-9]+\.)+[a-z]{2,})\z/i
REGEXP_BITMESSAGE = /\ABM-[123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ]{32,34}\z/
def contact_is_email?
contact =~ REGEXP_EMAIL
end
end
I am having some trouble understanding the syntax of variables and symbols in Ruby. I am reading a book called Agile Web Development with Rails 4. I am trying to learn both Ruby and Rails so that I can build websites.
The books and tutorials I have been reading sometimes have variables with the "#" symbol in front of them, and then some variables do not have the # symbol in front of them. What is the difference between them?
Also, I am getting confused with the colon. Sometimes I see variables where the colon is in the front, such as :order, and then I see variables where the colon is at the end, such as colon:. I do not understand what the colon is doing.
Please help me understand the Ruby syntax.
Variables starting with # are instance variables, "properties" in other languages. Whereas 'classic' variables are local to the scope of their method/block, instance variables are local to a specific instance of an object, for example:
class Foo
def initialize(bar)
#bar = bar
end
def bar
#bar # the variable is specific to this instance
end
def buzz
buzz = 'buzz' # this variable is not accessible outside of this method
end
end
You may also see variables starting with ##, which are class variables, and are accessible by every instance of the class and shared with every instance of the subclass. Usage of those variables is usually discouraged, primarily because subclasses share the variable, which can cause a lot of mess.
In Ruby everything is an object, classes are objects (instances of class Class), so you can also have class instance variables:
class Foo
def self.bar
#bar #we are in class Foo's scope, which is an instance of class Class
end
def self.bar=(bar)
#bar = bar
end
def bar
#bar # Foo.new.bar != Foo.bar
end
end
What you call "variables with a colon" are not variables. They are a particular type of string, called a symbol, that is immutable and optimized for quick identification by the interpreter, in fact, those are stored internally as pointers, so that :this == :this is a very quick operation.
This property makes them good candidates for hash keys because they offer quick retrieval or for "flags" to pass to a method; Think of them as a sort of loose constant that "stands for" what they say. Their immutability is also dangerous: All symbols ever created never get garbage collected; It's easy to create a memory-leak by creating thousands of symbols, so use them wisely.
UPDATE since ruby 2.2 symbols may be garbage-collected in certain cases (when no reference is kept and no comparison is needed)
Variables with an # symbol are instance variables. What this means is that they persist as long as the instance of the class they are declared in persists. So if you have a class called Message and each message has a variable called #subject, when you instantiate a new message it will keep that subject variable in memory as long as the message object itself lives. Now if it did not have the # symbol, once the function it was declared in "went out of scope" aka finished, the variable would be "lost" as the function was complete and the memory was reclaimed by the Ruby VM. There are also "class variables" that are prefaced with two # symbols. This means the variable is shared across all instances of a class.
As for the colon, if it is before a variable that means it is a "symbol", which is usually used as an identifer for hashes and other bits of data in Ruby. If it is at the end of a word that means it is the key portion of a hash identifier in Ruby 1.9+ syntax.
Instance Variables: (#foo = '123') An instance variable is defined and keeps its value throughout the current instance of the request. In the rails mvc paradigm, the most common use of instance variables are used to help communicate data from the controller to the view, and allows you ro define things in one part of the controller and use in another.
class ProjectsController < ApplicationController
before_filter :find_project
def show; end
def update
if #project.update_attributes(params[:project])
...
end
end
private
def find_project
#project = Project.find(params[:id])
end
end
In the above code, you can see that there is a before filter that gets ran before every method. In the above case, we find the current project and save it to an instance variable. And because its an instance method, its able to be access anywhere within this class as well as the views used to render the html.
Local Variables: (foo = '123') Pretty much exactly what the name implies, they are only able to be accessed within the current method (def) of where they are defined.
sometimes have variables with the "#" symbol in front of them, and then some variables do not have the # symbol in front of them.
Variables with the "#" symbol are instance variables,which are not preceded by #,can be constants or local variables or global variables. Read Ruby Programming/Syntax/Variables and Constants.
Sometimes I see variables where the colon is in the front, such as :order
They are called symbols.
and then I see variables where the colon is at the end, such as colon:. I do not understand what the colon is doing.
These probably the Hash syntax(as you give us hints,so I would guess),where keys are symbols. Example : {foo: 1} - this is a Hash.
Also read as you requested :
Normal Variables Vs Instance variable in Ruby, Whats the difference?
I was implementing a form that includes a hard-coded dropdown for a collection and I was wondering what would be the best solution, I know both ways exposed below work, still I did as follows:
class Example
# Options for Example.
self.options
[ 'Yes', 'No', 'Not sure' ]
end
end
which is called by Example.options, but I know it is possible to do as follows as well:
class Example
# Options for Example.
OPTIONS = [ 'Yes', 'No', 'Not sure' ]
end
that would be called with Example::OPTIONS.
The question is, is any of these the good way or it just doesn't matter at all?
The latter is better. If it were a method, a new array and new strings will be created every time it is called, which is a waste of resource.
TL;DR: It depends. Are the values meant to be used outside the class? Could they ever become dynamic? Could they change for subclasses?
As #sawa wrote, the downside of the method (written this way) is that a new array and strings are created each time.
A better way to write it would be:
class Example
def self.options
#options ||= ['Yes', 'No', 'Not sure']
end
end
The array is stored in the instance variable #options, to avoid creating a new array each time.
Written this way, the method is very similar to the constant.
One key difference is if Example is subclassed, it will be more natural to refine the options method than the constant OPTIONS:
class Parent < Example
def self.options
#options ||= [*super, 'Extra']
end
end
To do something similar with constants is difficult. Imagine that your list of options is used in a class method, this would look like:
class Example
OPTIONS = ['Yes', 'No', 'Not sure']
def self.foo(arg)
puts "Available options:",
self::OPTIONS # The self:: is needed here
# ...
end
end
class Parent < Example
OPTIONS = [*superclass::OPTIONS, 'Extra']
end
The tricky thing about constants, is that self::OPTIONS and OPTIONS are not the always same, while self.options and options are the same. Constants are usually used without specifying the scope (e.g. OPTIONS instead of self::OPTIONS) and inheritance will simply not work in that case.
Note that the method gives you the opportunity to make the result dynamic (i.e. return different results depending on other circumstances) without changing the API.
Final note: I'd recommend calling freeze on your array, to avoid anyone modifying it.
What I usually do is have a mix of above-mentioned techniques:
class Player
JURISDICTIONS = %i(de uk ru)
def self.jurisdictions
JURISDICTIONS
end
end
It has few advantages:
It provides a clean interface, encapsulating a constant (you call Player.jurisdictions instead of Player::JURISDICTIONS).
Additional logic can be added later just by altering method.
The method can be stubbed in tests.
IMHO, performance does not matter here.
Update:
Constant can bee hidden using private_constant method (http://ruby-doc.org/core-2.3.0/Module.html#method-i-private_constant)
To further refine Artur's suggestion I would go with a class variable in order to hide visibility of the constant.
class Player
##jurisdictions = %i(de uk ru)
def self.jurisdictions
##jurisdictions
end
end
Browsing through the Rails codebase I find numerous references to options.dup.
def to_xml(options = {})
require 'builder' unless defined?(Builder)
options = options.dup
....
end
Obviously options.dup is duplicating the options hash, but why would you wish to do this in this context?
dup clones an object. When you pass an object to a method, anything that changes the internal state of that object will be reflected in the calling scope. For example, try this code:
def replace_two(options)
options[:two] = "hi there"
end
options = { one: "foo", two: "bar" }
replace_two(options)
puts options[:two]
That will print hi there, because replace_two() modified the hash contents.
If you want to avoid changing the passed-in options, you can call .dup on it, and then any changes made to the clone won't be reflected in the calling scope:
def replace_two(options)
options = options.dup
options[:two] = "hi there"
end
options = { one: "foo", two: "bar" }
replace_two(options)
puts options[:two]
Will print bar. This is a common pattern that follows the Principle of Least Astonishment . In Ruby, methods that modify their arguments are usually named with a ! suffix to alert the user that they are destructive/modifying actions. The non-dup version of the method should have been called replace_two! to indicate this side-effect.
dup creates a shallow copy of an object. It's ruby core stuff. Since in ruby objects like Hash and Array are passed by reference, when you change object inside of a function this will change original object. If this is not desired behavior - you create a copy... So that code does.
See ruby-doc
UPDATE
One more thing. Since object are passed by reference, options = options.dup will assign to options variable reference to newly created copy. Reference to original object is lost inside to_xml. But it is still probably referenced in code that invoke to_xml
I am consuming JSON data from a third party API, doing a little bit of processing on that data and then sending the models to the client as JSON. The keys for the incoming data are not named very well. Some of them are acronyms, some just seem to be random characters. For example:
{
aikd: "some value"
lrdf: 1 // I guess this is the ID
}
I am creating a rails ActiveResource model to wrap this resource, but would not like to access these properties through model.lrdf as its not obvious what lrdf really is! Instead, I would like some way to alias these properties to another property that is named better. Something so that I can say model.id = 1 and have that automatically set lrdf to 1 or puts model.id and have that automatically return 1. Also, when I call model.to_json to send the model to the client, I dont want my javascript to have to understand these odd naming conventions.
I tried
alias id lrdf
but that gave me an error saying method lrdf did not exist.
The other option is to just wrap the properties:
def id
lrdf
end
This works, but when I call model.to_json, I see lrdf as the keys again.
Has anyone done anything like this before? What do you recommend?
Have you tried with some before_save magic? Maybe you could define attr_accessible :ldrf, and then, in your before_save filter, assign ldrf to your id field. Haven't tried it, but I think it should works.
attr_accessible :ldrf
before_save :map_attributes
protected
def map_attributes
{:ldrf=>:id}.each do |key, value|
self.send("#{value}=", self.send(key))
end
end
Let me know!
You could try creating a formatter module based on ActiveResource::Formats::JsonFormat and override decode(). If you had to update the data, you'd have to override encode() also. Look at your local gems/activeresource-N.N.N/lib/active_resource/formats/json_format.rb to see what the original json formatter does.
If your model's name is Model and your formatter is CleanupFormatter, just do Model.format = CleanupFormatter.
module CleanupFormatter
include ::ActiveResource::Formats::JsonFormat
extend self
# Set a constant for the mapping.
# I'm pretty sure these should be strings. If not, try symbols.
MAP = [['lrdf', 'id']]
def decode(json)
orig_hash = super
new_hash = {}
MAP.each {|old_name, new_name| new_hash[new_name] = orig_hash.delete(old_name) }
# Comment the next line if you don't want to carry over fields missing from MAP
new_hash.merge!(orig_hash)
new_hash
end
end
This doesn't involve aliasing as you asked, but I think it helps to isolate the gibberish names from your model, which would never have to know those original names existed. And "to_json" will display the readable names.