RoR: how to tell which params in a form have changed - ruby-on-rails-3

In a Rails application, I have a particular form with many fields for editing a resource. Since I also want to log what was changed for this particular resource, I need to know which params changed.
Currently in this form, I have duplicated every field in the form with hidden field tags, so in the controller every field is compared to the corresponding hidden field to determine if the value was changed. But it's a LOT of work in the view and in the controller.
Being relatively new to Rails, I'm finding all kinds of Rails "magic" as I go along, so I wonder: does the framework provide a way to do this for me? Or is this pretty much the only way?

ActiveModel has exactly what you're looking for, take a look at the examples in the docs...
http://api.rubyonrails.org/classes/ActiveModel/Dirty.html
person = Person.find_by_name('Uncle Bob')
person.changed? # => false
person.name = 'Bob'
person.changed? # => true
person.name_changed? # => true
person.name_was # => 'Uncle Bob'
person.name_change # => ['Uncle Bob', 'Bob']
person.name = 'Bill'
person.name_change # => ['Uncle Bob', 'Bill']

You can try Dirty attributes for this task.
#post.attributes = params[:post]
#post.changed # or changes if you want to see what values on which was changed
More on that you can find by this link: http://ar.rubyonrails.org/classes/ActiveRecord/Dirty.html
Also if you want to use versioning or auditing, try this gems (best ones are: paper_trail, acts_as_audited, vestal_versions):
https://www.ruby-toolbox.com/categories/Active_Record_Versioning
https://www.ruby-toolbox.com/categories/Active_Record_User_Stamping

Related

Rails3 Autocomplete Gem - Two Different Autocompletes on Same ClassName and Method

I am successfully using the ":scopes =>" option of this gem to return a subset of the rows of the table. Now I want to create a nearly identical autocomplete, on the same ClassName and Method, with a different scope, for a different view.
The problem is, the first two arguments of autocomplete in the controller are ClassName and Method, and those also create the functional name of the autocomplete function, as it is used in the view and route. Therefore, both of my autocompletes would have the same name.
Is there a workaround I can use to assign a different name to each of the similar autocompletes?
Example code:
autocomplete :my_class_name, :my_method, :display_value => :my_formatting_method,
:extra_data => [:id], :scopes => [:active_and_special]
def active_and_special
where("active = ?", true).
where("special = ?", true).
order("name ASC")
end
Then the again for the 2nd one, with the same class-name and method, but a different scope.
Best workaround I've found is to use "column_name" to override the part of the naming which ordinarily refers to the method - like this:
autocomplete :my_class_name, :foo, :column_name => 'my_method',
:display_value => :my_formatting_method, :extra_data => [:id],
:scopes => [:active_and_not_special]
def active_and_not_special
where("active = ?", true).
where("special = ?", false).
order("name ASC")
end
This has route:
get 'my_class_names/autocomplete_my_class_name_foo'
Whereas the original has route:
get 'my_class_names/autocomplete_my_class_name_my_method'
This allows us to generate a 2nd, different autocomplete, on the same Class and Method as another, with its own unique name.
Having an independent name, vs the 'magic' autogenerated one (to save 1 second of typing?) would prevent this trouble - or am I missing something?
I'll leave this open for a bit to see if anyone has a better solution.

Pull mass name list from database or in script?

I need to fill an input box with a first name and last name. The user will press "Randomize" and it will pull a random first and last name and fill the inputs.
My question is I'm not sure if I should put the names in tables (firstNames, lastNames) or just store them in a javascript file and pull straight from that.
I'm trying to follow the Single Responsibility Principle so I'm inclined to choose the former, but then I have two more models, two more seeders, two more tables, and probably a class to pull all that together. And then do I fill from a CSV file or just from a manually populated seeder? It seems like a lot of work and extra files for a 1-time use.
I know I'll get crap for this being an opinion based question but there is no one or where else to ask.
Also if you know of a place to ask these kind of questions that won't get me ripped apart I'd appreciate that.
I would suggest using the Faker PHP library. That way you wouldn't have to create extra tables, models, or have to worry about finding yourself fake data.
To install it in your project, simply add the dependency in your composer.json file. and run a composer update.
"require-dev": {
"fzaninotto/faker": "1.3.*#dev"
},
Then you can use it to create fake first and last names for you (in your controller most likely)
$faker = Faker\Factory::create();
$firstName = $faker->firstName;
$lastName = $faker->lastName;
Edit:
To add your own names you can either edit or override the name provider file located here.
I would like to suggest Fakerino a new fake generator PHP library, with a modern approach, easy to extend with custom data, custom fake data class, or pre-configured groups of data.
https://github.com/niklongstone/Fakerino
<?php
include ('../Fakerino/vendor/autoload.php');
use Fakerino\Fakerino;
$fakerino = Fakerino::create();
echo $fakerino->fake('Surname')->toJson(); //["Donovan"]
echo $fakerino->fake('NameFemale'); //Alice
//with configuration
$fakerino = Fakerino::create('./conf.php');
print_r($fakerino->fake('fake1')->toArray());
/*
Array(
[0] => Arthur
[1] => Doyle
)
*/
//conf.php
<?php
$conf['fake'] = array(
'fake1' => array('NameMale', 'Surname' => null),
'fake2' => array('NameFemale', 'Surname' => null)
);

Rails 3: Excluding Results by Default

On my site, moderators can flag spammy comments. When these comments are flagged, they get quarantined so they no longer appear in regular views, though they can still be seen in the administrative control panel. At the moment, I exclude them from regular views like so:
#comments = Comment.where(:flagged => false)
I do this in every controller that has comments in it, of which there are many. I get the feeling that there's a cleaner way to handle this in Rails. Perhaps somewhere in the comments model I can specify that when querying for comments, only retrieve those that aren't flagged. If so, how is that done? And even if that's not possible, is there some other way to dry this code?
u can use a default scope
default_scope where(:flagged => false)
see http://apidock.com/rails/ActiveRecord/Base/default_scope/class
the default scope can be ignored using unscoped. See http://apidock.com/rails/ActiveRecord/Base/unscoped/class
i would prefer using a scope rather a default scope since i dont have to override it when all the records are needed. Depends upon the frequency of fetching all/unflagged records.
Make a scope (named 'clean' for this example):
class Comment < ActiveRecord
scope :clean, where(:flagged => false)
end
Then use:
#comments = Comment.clean
For future-proofing, you may may want to add a class method called default_view which just calls clean and use that instead. As your 'default' needs change, just modify the default_view method.

In Rails 3, is there a difference between = and assign_attributes?

Let's say you're in your user controller and you want to change the name a #user based on some params you have available to you.
I want to know if there is any difference between the following:
#user.name = params[:user][:name]
or
#user.assign_attributes({:name=> params[:user][:name]})
Thanks in advance!
A great way to figure out questions like this is to dive into the source. I found the method in activerecord/lib/active_record/attribute_assignment.rbCheck it out here.
The assign_attributes method will actually just loop through the parameters given and sends the :name= message to your model. However, because you are possibly assigning many attributes, it takes into account mass-assignment precautions. (ie. make sure that the attribute is listed as attr_accessible).
The = (e.g. #user.name = params[:user][:name]) directly calls the attribute setter with no security check. The assign_attributes checks security for the values passed in.
From the Rails API for assign_attributes:
Allows you to set all the attributes for a particular mass-assignment
security role by passing in a hash of attributes with keys matching
the attribute names (which again matches the column names) and the
role name using the :as option.
Source for assign_attributes

Can anyone explain how CDbCriteria->scopes works?

I've just checked the man page of CDbCriteria, but there is not enough info about it.
This property is available since v1.1.7 and I couldn't find any help for it.
Is it for dynamically changing Model->scopes "on-the-fly"?
Scopes are an easy way to create simple filters by default. With a scope you can sort your results by specific columns automatically, limit the results, apply conditions, etc. In the links provided by #ldg there's a big example of how cool they are:
$posts=Post::model()->published()->recently()->findAll();
Somebody is retrieving all the recently published posts in one single line. They are easier to maintain than inline conditions (for example Post::model()->findAll('status=1')) and are encapsulated inside each model, which means big transparency and ease of use.
Plus, you can create your own parameter based scopes like this:
public function last($amount)
{
$this->getDbCriteria()->mergeWith(array(
'order' => 't.create_time DESC',
'limit' => $amount,
));
return $this;
}
Adding something like this into a Model will let you choose the amount of objects you want to retrieve from the database (sorted by its create time).
By returning the object itself you allow method chaining.
Here's an example:
$last3posts=Post::model()->last(3)->findAll();
Gets the last 3 items. Of course you can expand the example to almost any property in the database. Cheers
Yes, scopes can be used to change the attributes of CDbCriteria with pre-built conditions and can also be passed parameters. Before 1.1.7 you could use them in a model() query and can be chained together. See:
http://www.yiiframework.com/doc/guide/1.1/en/database.ar#named-scopes
Since 1.1.7, you can also use scopes as a CDbCriteria property.
See: http://www.yiiframework.com/doc/guide/1.1/en/database.arr#relational-query-with-named-scopes