I'm working on a twitter-like app for practice. Users create posts, and I'm adding functionality so that users can tag other users in their posts by putting #email of the user they want to tag at the beginning of their post.
in_reply_to is the id of the user being tagged in the Micropost.
This is in my Micropost_controller.rb
#reply_regex = /(\A#[^# ]+(#)\w+(\.[a-z]{2,3}){1,2}.*\z)/i
def create
#micropost = current_user.microposts.build(params[:micropost])
if #micropost.content =~ #reply_regex
email = #micropost.content.scan(/([^# ]+(#)\w+(\.[a-z]{2,3}){1,2})/i).first.first
#micropost.in_reply_to = User.find_by_email(email).id
end
if #micropost.save
flash[:success] = "Micropost created!"
redirect_to root_path
When I run the email-extracting part on a string in the console it works perfectly and returns the email. But when I create new microposts, the in_reply_to always stays nil.
Something like this:
class C
#a = 11
end
does not create an instance variable named #a for instances of C. When you hit #a = 11, self will be the class itself so #a will be an instance variable for the the object C. If you put the above into irb and look at C.instance_variables, you will see this:
>> C.instance_variables
=> [:a]
but when you look at an instance of C:
>> C.new.instance_variables
=> []
Also, instance variables are automatically created on first use and initialized to be nil.
Combining the above tells us that you have a #reply_regex instance variable in the class object MicropostController but not in the instances. Your def create is an instance method so it will use the #reply_regex instance variable; but you don't have #reply_regex as an instance variable for MicropostController objects so it will be created and initialized as nil inside your if statement. The result is that your if ends up being this:
if #micropost.content =~ nil
and #micropost.content =~ nil will evaluate to nil and, since nil is false in a boolean context, the if block is never entered and #micropost.in_reply_to is never assigned a value.
You can use a class variable for your regex:
##reply_regex = /(\A#[^# ]+(#)\w+(\.[a-z]{2,3}){1,2}.*\z)/i
def create
#micropost = current_user.microposts.build(params[:micropost])
if #micropost.content =~ ##reply_regex
#...
as class variables are visible to instance methods or, better, just use a constant:
REPLY_REGEX = /(\A#[^# ]+(#)\w+(\.[a-z]{2,3}){1,2}.*\z)/i
def create
#micropost = current_user.microposts.build(params[:micropost])
if #micropost.content =~ REPLY_REGEX
#...
As an aside, I think you should move the reply-to checking into your model. You could use a before_validation callback to strip off any leading and trailing whitespace in content and extract and save the reply-to:
class Micropost < ActiveRecord::Base
before_validate :process_content, :if => :content_changed?
#...
private
def process_content
# Strip leading/trailing whitespace from self.content
# Extract the reply-to from self.content and save it in self.in_reply_to
end
end
The advantage here is that if the content is changed or created without going through your controller (such as a migration, by hand in the Rails console, some system task that is notifying the users of something, ...), you still get everything done.
Related
I'm fairly confused and would appreciate some help.
I'm going through the Michael Hartl tutorial and the most complicated aspect (for me) is simply understanding what I perceive to be inconsistencies in syntax (I know I'm wrong in this regard and it's just my perception).
I'm currently on chapter 8, however a simple example from earlier on:
def show
#user = User.find(params[:id])
end
And another:
def create
#user = User.new(user_params)
if #user.save
log_in #user
flash[:success] = "Welcome to the Sample App!"
redirect_to #user
else
render 'new'
end
end
As far as I understand instance variables, they are supposed to be attributes of the object or instance of the class, eg, :name, :email, :password_digest, etc.
In these examples, we haven't written an initialize method for the class as we simply generated a migration with 4/5 columns (which correspond with the attributes specified); the columns in this migration are, as I understand, interpreted by Rails as attributes or instance variables of the instance of the class (the object?)
What then is #user or rather why is an instance variable used in this context? It isn't an attribute (eg, :name) of an instance of the class, but rather appears to be a reference, placeholder or representation of an instance of the class?
n.b. I understand (well, as far as beginners understand) what the code above does, ie, queries the User model - which is also called User - to retrieve a record, however I don't understand why this is being assigned to an instance variable or in what contexts to use instance variables, ie, I thought they were for specifying attributes for an instance of the class, not for operating as a placeholder or referencing an instance of the class (including its attributes).
EDIT: I think my confusion emanates from instance variables being used for both specifying attributes and for, as per Mark's definition below, 'operating as a container for all the attributes of a class.'
The above examples appear to use instance variables as a 'container for attributes', whereas other examples I've read use instance variables to store attribute values.
An example from earlier in the book:
def initialize(attributes = {})
#name = attributes[:name]
#email = attributes[:email]
end
I guess it's both?
From rubyist.net:
An instance variable has a name beginning with #, and its scope is confined to whatever object self refers to. Two different objects, even if they belong to the same class, are allowed to have different values for their instance variables.
So to answer your question: Yes, an instance variable is referring to an instance of a class and is not the same thing as an attribute of a class. More accurately, an instance variable is simply a container for all the attributes of a class (in this case the User class). Whenever you use the #user syntax, you're saying that you want to create your own instance of a user, with separate values for the user's attributes than say, another instance variable.
I could create two instance variables like this:
#user1 = User.new
#user2 = User.new
Now, I can assign attributes to the users separately like this:
#user1.email = "test#exmample.com"
#user2.email = "anotheremail#example.com"
Each instance of this user is unique, and that is the core concept behind instance variables.
For rails, however, any instance variables you declare in your controller will be accessible in the corresponding view, which is why you can access the #user variable in, say, your new.html.erb file.
EDIT
Here's the code you updated your post with:
def initialize(attributes = {})
#name = attributes[:name]
#email = attributes[:email]
end
The initialize method is actually the method that gets called when you create a new object. For example if I created a new user object, I'd do it like this:
#user = User.new
By default, the initialize method for the User class will get called when I do this. Also, you'll notice that the initialize method takes one parameter called attributes. The parameter is defined like this:
def initialize(attributes = {})
This means that if you pass in any arguments to the new method, those arguments will be passed into the attributes parameter. the = {} simply means that if you don't pass in any arguments to the new method method, the attributes parameter will default to a blank hash.
For example, I could initialize a user object like this:
#user = User.new(name: "mark", email: "test#example.com")
the name and email parameters would be passed into the initialized method via the attributes hash. then, your code suddenly makes more sense. This line:
#name = attributes[:name]
simply assigns the name you passed into the new method to be the value of #user.name. So in this sense, the above line is confusing because it appears to be assigning an attribute to an instance variable, but it's really just assigning the value of the name attribute on the #user instance variable.
Therefore, once the initialize method returns, the #user variable would have access to the attributes you assigned in the initialize method.
More reading:
good stack overflow question
object initialization in ruby
Say I have two models, Email and Message, with a boolean read attribute, and to mark them read I add a concern with mark_read and mark_unread patch members that route to ReadablesController.
I'd like to make it so that set_readable is automatic, not requiring me to manually query the params, and instead just work for all models with a read attribute. Is there a simple way to accomplish that?
class ReadablesController < ApplicationController
before_action :set_readable
...
def mark_read
#readable.read = true
#readable.save
flash[:notice] = "#{#readable.class.to_s} marked read."
redirect_to :back
end
def mark_unread
#readable.read = false
#readable.save
flash[:notice] = "#{#readable.class.to_s} marked unread."
redirect_to :back
end
private
def set_readable
throw "error" if params[:email_id].present? && params[:message_id].present?
#readable = Email.find(params[:email_id]) if params[:email_id].present?
#readable = Message.find(params[:message_id]) if params[:message_id].present?
end
end
You can check if a model has read attribute with has_attribute?(:read). From there it is trivial to call your mark_read and mark_unread methods.
#model.mark_read if #model.has_attribute?(:read)
This probably goes to your controller's set_readable method where it still will have to check a relevant param, say, params[:read] to invoke the logic.
I'm trying to monkey patch ActiveRecord::FinderMethods in order to use hashed ids for my models. So for example User.find(1) becomes User.find("FEW"). Sadly my overwritten method doesn't get called. Any ideas how to overwrite the find_one method?
module ActiveRecord
module FinderMethods
alias_method :orig_find_one, :find_one
def find_one(id)
if id.is_a?(String)
orig_find_one decrypt_id(id)
else
orig_find_one(id)
end
end
end
end
Here's an article that discusses how to actually do what you want by overriding the User.primary_key method like:
class User
self.primary_key = 'hashed_id'
end
Which would allow you to call User.find and pass it the "hashed_id":
http://ruby-journal.com/how-to-override-default-primary-key-id-in-rails/
So, it's possible.
That said, I would recommend against doing that, and instead using something like User.find_by_hashed_id. The only difference is that this method will return nil when a result is not found instead of throwing an ActiveRecord::RecordNotFound exception. You could throw this manually in your controller:
def show
#user = User.find_by_hashed_id(hashed_id)
raise ActiveRecord::RecordNotFound.new if #user.nil?
... continue processing ...
end
Finally, one other note to make this easier on you -- Rails also has a method you can override in your model, to_param, to tell it what property to use when generating routes. By default, of course, it users the id, but you would probably want to use the hashed_id.
class User
def to_param
self.hashed_id
end
end
Now, in your controller, params[:id] will contain the hashed_id instead of the id.
def show
#user = User.find_by_hashed_id(params[:id])
raise ActiveRecord::RecordNotFound.new if #user.nil?
... continue processing ...
end
I agree that you should be careful when doing this, but it is possible.
If you have a method decode_id that converts a hashed ID back to the original id, then the following will work:
In User.rb
# Extend AR find method to allow finding records by an encoded string id:
def self.find(*ids)
return super if ids.length > 1
# Note the short-circuiting || to fall-back to default behavior
find_by(id: decode_id(ids[0])) || super
end
Just make sure that decode_id returns nil if it's passed an invalid hash. This way you can find by Hashed ID and standard ID, so if you had a user with id 12345, then the following:
User.find(12345)
User.find("12345")
User.find(encode_id(12345))
Should all return the same user.
I am trying to create persistante variable local to my model but although something that sounds so simple is not working.
I have this in my model:
class Coupon < ActiveRecord::Base
#username = "empty"
#admin = false
def self.setUser(name, isAdmin)
#username = name
#admin = isAdmin
end
def self.get_user (user)#an attempt to access the current_user but did not work i call this from the controller (I understand it is not best practice)
##user = user
self.setUser(user.username,user.admin?)
end
def has_not_occurred
errors.add("property_of","name is not valid:#{#username}") if !validPropertyOf?
end
end
def validProperty_of?
return property_of == #username # || #Admin
end
end
I actually get a "" instead of "empty" or the new value of username in set.user. How do I make these values persist? I have printed the values inside each method so they persist inside the method but not beyond for some reason.
#username is always nil or "" when it gets to has_not_accurred.
Why is this and how do I make it persist? Thank you so much.
I cannont access #user when I set it either (get_user method). I get a nil instance later down at validateProperty_of
I think you forget about database table, you haven't create this one, that's why every variables are non-persistent.
Upgraded Rails now it works. Not sure why.
I have been trying to get my head around render_to but I haven't had much success.
Essentially I have controller methods:
def first
#I want to get the value of VAR1 here
end
def second
VAR1 = ["Hello", "Goodbye"]
render_to ??
end
What I can't figure out is how to accomplish that. Originally I just wanted to render the first.html.erb file but that didn't seem to work either.
Thanks
Edit: I appreciate the answers I have received, however all of them tend to avoid using the render method or redirect_to. Is it basically the case then that a you cannot pass variables from controller to controller? I have to think that there is some way but I can't seem to find it.
It is not a good idea to assign the object to a constant. True this is in a global space, but it is global for everyone so any other user going to this request will get this object. There are a few solutions to this.
I am assuming you have a multi-step form you are going through. In that case you can pass the set attributes as hidden fields.
<%= f.hidden_field :name %>
If there are a lot of fields this can be tedious so you may want to loop through the params[...] hash or column_names method to determine which attributes to pass.
Alternatively you can store attributes in the session.
def first
#item = Item.new(params[:item])
session[:item_attributes] = #item.attributes
end
def second
#item = Item.new(session[:item_attributes])
#item.attributes = params[:item]
end
Thirdly, as Paul Keeble mentioned you can save the model to the database but mark it as incomplete. You may want to use a state machine for this.
Finally, you may want to take a look at the Acts As Wizard plugin.
I usually don't have my controllers calling each other's actions. If you have an identifier that starts with a capital letter, in Ruby that is a constant. If you want to an instance level variable, have it start with #.
#var1 = ["Hello", "Goodbye"]
Can you explain what your goal is?
Have you considered using the flash hash? A lot of people use it solely for error messages and the like, it's explicitly for the sort of transient data passing you might be interested in.
Basically, the flash method returns a hash. Any value you assign to a key in the hash will be available to the next action, but then it's gone. So:
def first
flash[:var] = ["hello", "goodbye"]
redirect_to :action => :second
end
def second
#hello = flash[:var].first
end
way 1
Global variable
(fail during concurrent requests)
way 2
class variable
(fail during concurrent requests)
way 3
Stash the object on the server between requests. The typical way is to save it in the session, since it automatically serializes/deserializes the object for you.
Serialize the object and include it in the form somewhere, and
deserialize it from the parameters in the next request. so you can store attributes in the session.
def first
#item = Item.new(params[:item])
session[:item_attributes] = #item.attributes
end
def second
#item = Item.new(session[:item_attributes])
#item.attributes = params[:item]
end
way 4
The flash provides a way to pass temporary objects between actions. Anything you place in the flash will be exposed to the very next action and then cleared out.
def new
#test_suite_run = TestSuiteRun.new
#tests = Test.find(:all, :conditions => { :test_suite_id => params[:number] })
flash[:someval] = params[:number]
end
def create
#test_suite_run = TestSuiteRun.new(params[:test_suite_run])
#tests = Test.find(:all, :conditions => { :test_suite_id => flash[:someval] })
end
way 5
you can use rails cache.
Rails.cache.write("list",[1,2,3])
Rails.cache.read("list")
But what happens when different sessions have different values?
Unless you ensure the uniqueness of the list name across the session this solution will fail during concurrent requests
way 6
In one action store the value in db table based on the session id and other action can retrieve it from db based on session id.
way 7
class BarsController < UsersController
before_filter :init_foo_list
def method1
render :method2
end
def method2
#foo_list.each do | item|
# do something
end
end
def init_foo_list
#foo_list ||= ['Money', 'Animals', 'Ummagumma']
end
end
way 8
From action sent to view and again from view sent to other actions in controller.